programing

포인터를 전달하는 것이 아니라 C의 값으로 구조물을 통과하는 단점이 있습니까?

goodsources 2022. 8. 16. 23:40
반응형

포인터를 전달하는 것이 아니라 C의 값으로 구조물을 통과하는 단점이 있습니까?

포인터를 전달하는 것이 아니라 C의 값으로 구조물을 통과하는 단점이 있습니까?

구조가 크면 분명히 많은 데이터를 복사하는 퍼포먼스 측면도 있지만, 작은 구조의 경우 기본적으로 함수에 여러 값을 전달하는 것과 같아야 합니다.

반환값으로 사용하면 더 흥미로울 수 있습니다.C에는 함수의 반환값은 1개뿐이지만, 많은 경우 여러 개가 필요합니다.그래서 간단한 해결책은 그것들을 구조물에 넣고 그것을 돌려주는 것입니다.

이에 대한 반대 또는 반대 이유가 있습니까?

제가 여기서 말하는 것이 모두에게 분명하지 않을 수도 있기 때문에, 간단한 예를 들어 보겠습니다.

C에서 프로그래밍하는 경우, 조만간 다음과 같은 함수를 작성하기 시작할 것입니다.

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

이건 문제가 아니야.유일한 문제는 파라미터의 순서를 동료와 합의해야 하기 때문에 모든 기능에서 동일한 규칙을 사용해야 한다는 것입니다.

하지만 같은 종류의 정보를 반환하려면 어떻게 해야 할까요?일반적으로 다음과 같은 결과가 나타납니다.

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

이것은 잘 작동하지만 훨씬 더 문제가 있습니다.이 구현에서는 반환값이 아닌 경우를 제외하고 반환값은 반환값입니다.위에서 get_data 함수는 len이 가리키는 것을 볼 수 없습니다.또한 컴파일러가 해당 포인터를 통해 실제로 값이 반환되었는지 확인할 수 있는 것은 없습니다.그래서 다음 달에는 다른 사람이 제대로 이해하지 못하고 코드를 수정하면(그가 문서를 읽지 않았기 때문에?), 아무도 모르게 코드가 고장나거나 랜덤으로 크래쉬하기 시작합니다.

그래서 제가 제안하는 해결책은 간단한 구조입니다.

struct blob { char *ptr; size_t len; }

예는 다음과 같이 고쳐 쓸 수 있습니다.

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

어떤 이유에서인지 대부분의 사람들은 본능적으로 exam_data를 구조체의 blob에 포인터 붙이도록 만들 것이라고 생각합니다만, 그 이유는 알 수 없습니다.포인터와 정수를 얻을 수 있습니다.이것들이 함께 가는 것이 훨씬 더 명확합니다.그리고 get_data 케이스에서는 길이의 입력값이 없고 반환되는 길이가 있기 때문에 앞에서 설명한 것과 같이 혼란스러울 수 없습니다.

작은 구조물(예: 점, 직경)의 경우 값으로 전달하는 것은 완벽하게 허용됩니다.그러나 속도 외에도 큰 구조물을 값별로 통과/반환하는 데 주의해야 하는 다른 이유가 있습니다.스택 공간

많은 C 프로그래밍은 메모리가 프리미엄인 임베디드 시스템을 위한 것으로 스택 사이즈는 KB 또는 바이트 단위로 측정됩니다.값을 기준으로 구조체를 전달하거나 반환하는 경우 해당 구조체의 복사본이 스택에 배치되므로 이 사이트의 이름이 다음과 같이 지정될 수 있습니다.

과도한 스택 사용률을 보이는 응용 프로그램이 있는 경우 값으로 전달된 구조가 가장 먼저 검색되는 항목 중 하나입니다.

여기 사람들이 지금까지 언급하는 것을 잊은 한 가지는 (혹은 제가 간과한) 구조에는 대개 패딩이 있다는 것입니다!

struct {
  short a;
  char b;
  short c;
  char d;
}

각 문자는 1바이트, 각 문자는 2바이트입니다.그 구조물은 얼마나 큰가요?아니, 6바이트가 아니야.적어도 일반적으로 사용되는 시스템에는 없습니다.대부분의 시스템에서는 8이 됩니다.문제는 정렬이 일정하지 않고 시스템에 의존하기 때문에 같은 구조가 시스템마다 정렬과 크기가 다르다는 것입니다.

이 패딩은 스택을 더욱 잠식할 뿐만 아니라 시스템 패드가 어떻게 되어 있는지 알고 앱에 있는 모든 구조를 보고 크기를 계산하지 않는 한 패딩을 미리 예측할 수 없는 불확실성을 더합니다.포인터를 전달하려면 예측 가능한 공간이 필요합니다.불확실성은 없습니다.포인터의 크기는 시스템에 대해 알려져 있으며 구조가 어떻게 보이든 항상 동일하며 포인터 크기는 항상 정렬되어 패딩이 필요하지 않은 방식으로 선택됩니다.

이렇게 하지 않는 이유 중 하나는 바이너리 호환성이 중요한 문제가 발생할 수 있기 때문입니다.

사용하는 컴파일러에 따라 컴파일러 옵션/실장에 따라 스택 또는 레지스터를 통해 구조를 전달할 수 있습니다.

참조: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-contract-return

-freg-frec-return(반환)

두 컴파일러가 일치하지 않으면 모든 것이 폭발할 수 있습니다.이를 하지 않는 주된 이유가 스택 소비와 퍼포먼스라는 것은 말할 필요도 없습니다.

여기 아무도 언급하지 않은 것이 있습니다.

void examine_data(const char *c, size_t l)
{
    c[0] = 'l'; // compiler error
}

void examine_data(const struct blob blob)
{
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime
}

const structconst, 그 : " " )인 경우:char *가 됩니다.char *constconst char *정말 갖고 싶어요.아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아.const의도의 문서이며, 이것을 위반하는 사람은 모두 나쁜 코드를 쓰고 있는 것(그들)입니다만, 일부(특히 4시간 밖에 걸리지 않은 사람)에게는 충분하지 않습니다.

다른 은 ', 하다, 하다, 하다, 하다, 하다, 하다,struct const_blob { const char *c; size_t l }걸 짓기 요.그것은 내가 가지고 있는 명명 체계와 같은 문제가 됩니다.typedef포인터를 입력하다.따라서 대부분의 사용자는 2개의 파라미터(또는 이 경우 문자열 라이브러리 사용)만을 사용합니다.

이 질문에 답하기 위해서는 조립지를 깊이 파고들어야 합니다.

(다음 예제에서는 x86_64에서 gcc를 사용합니다.MSVC, ARM 등의 아키텍처 추가는 누구나 환영입니다.)

예를 들어 보겠습니다.

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

완전 최적화로 컴파일

gcc -Wall -O3 foo.c -o foo

어셈블리를 확인합니다.

objdump -d foo | vim -

다음과 같은 결과를 얻을 수 있습니다.

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

<고객명>님 제외nopl ★★★★★give_two_doubles() 반면 27 바이트는give_point()29번입니다. 반,는give_point()보다 1개 적은 명령어를 얻을 수 있습니다.give_two_doubles()

점은 가 컴파일러를 최적화할 수 입니다.mov더 빠른 SSE2 변종과movsdgive_two_doubles()실제로 데이터를 메모리 안팎으로 이동하기 때문에 속도가 느려집니다.

이 중 상당수는 임베디드 환경에서는 적용되지 않을 수 있습니다(현재는 C의 경쟁 분야가 대부분입니다).저는 조립 마법사가 아니기 때문에 어떤 코멘트라도 환영합니다!

은 에러 로 반환하는 입니다.
이 파라미터는 물론 구조체일 수 있지만 포인터를 전송했을 뿐 값별로 전달되는 특별한 장점은 없습니다.
값을 기준으로 구조체를 전달하는 것은 위험합니다. 전달되는 위치에 매우 주의해야 합니다. C에는 복사 생성자가 없습니다. 구조 매개 변수 중 하나가 포인터일 경우 포인터 값이 복사되므로 매우 혼란스럽고 유지하기가 어려울 수 있습니다.

답변(Roddy에 대한 완전한 신용)을 완성하기 위해 스택 사용률도 값별로 구조를 통과하지 않는 또 다른 이유입니다.디버깅 스택오버플로는 실제 PITA입니다

댓글 재생:

포인터로 구조를 전달한다는 것은 일부 엔티티가 이 개체에 대한 소유권을 가지고 있으며 해제 대상과 시기에 대해 완전히 알고 있다는 것을 의미합니다.값을 기준으로 구조를 전달하면 유지하기가 어렵습니다(가능하지만 왜일까요?).

(너무 크지 않은) 구조체를 매개 변수와 반환 값 모두 값으로 전달하는 것은 완전히 합법적인 기술입니다.물론 구조가 POD 유형이거나 복사 의미론이 잘 지정되어 있는지 주의해야 합니다.

업데이트: 죄송합니다.C++ 씽킹 캡을 쓰고 있었습니다.C에서는 함수에서 구조물을 반환하는 것이 합법적이지 않았던 때가 생각납니다만, 그 이후는 아마 이것이 바뀌었을 것입니다.사용하시는 컴파일러가 모두 이 방식을 지원하는 한 유효하다고 생각합니다.

질문 내용이 잘 정리된 것 같습니다.

값을 기준으로 구조를 전달하는 또 다른 장점은 메모리 소유권이 명시적이라는 것입니다.구조물이 힙에서 나왔는지, 누가 구조물을 해방시킬 책임이 있는지에 대해서는 의심의 여지가 없습니다.

http://www.drpaulcarter.com/pcasm/ PC Assembly Tutorial의 150페이지에는 C 함수가 구조체를 반환하는 방법에 대해 명확하게 설명되어 있습니다.

또한 C는 구조 유형을 함수의 반환값으로 사용할 수 있도록 한다.구조물은 EAX 레지스터에서 반환할 수 없습니다.컴파일러에 따라 이 상황은 다르게 처리됩니다.컴파일러가 사용하는 일반적인 솔루션은 구조 포인터를 매개 변수로 사용하는 함수로 함수를 내부적으로 다시 쓰는 것입니다.포인터는 반환값을 호출된 루틴 외부에 정의된 구조에 넣기 위해 사용됩니다.

위의 내용을 확인하기 위해 다음 C코드를 사용합니다.

struct person {
    int no;
    int age;
};

struct person create() {
    struct person jingguo = { .no = 1, .age = 2};
    return jingguo;
}

int main(int argc, const char *argv[]) {
    struct person result;
    result = create();
    return 0;
}

"gcc - S"를 사용하여 이 C 코드의 일부를 위한 어셈블리를 생성합니다.

    .file   "foo.c"
    .text
.globl create
    .type   create, @function
create:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    8(%ebp), %ecx
    movl    $1, -8(%ebp)
    movl    $2, -4(%ebp)
    movl    -8(%ebp), %eax
    movl    -4(%ebp), %edx
    movl    %eax, (%ecx)
    movl    %edx, 4(%ecx)
    movl    %ecx, %eax
    leave
    ret $4
    .size   create, .-create
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $20, %esp
    leal    -8(%ebp), %eax
    movl    %eax, (%esp)
    call    create
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

콜 작성 전 스택:

        +---------------------------+
ebp     | saved ebp                 |
        +---------------------------+
ebp-4   | age part of struct person | 
        +---------------------------+
ebp-8   | no part of struct person  |
        +---------------------------+        
ebp-12  |                           |
        +---------------------------+
ebp-16  |                           |
        +---------------------------+
ebp-20  | ebp-8 (address)           |
        +---------------------------+

create를 호출한 직후 스택:

        +---------------------------+
        | ebp-8 (address)           |
        +---------------------------+
        | return address            |
        +---------------------------+
ebp,esp | saved ebp                 |
        +---------------------------+

가치로 구조를 넘기는 것의 장점 중 하나는 최적화 컴파일러가 코드를 더 잘 최적화할 수 있다는 것입니다.

언급URL : https://stackoverflow.com/questions/161788/are-there-any-downsides-to-passing-structs-by-value-in-c-rather-than-passing-a

반응형