IT

포인터를 전달하는 대신 C에서 값으로 구조체를 전달하는 데 단점이 있습니까?

lottoking 2020. 6. 9. 07:49
반응형

포인터를 전달하는 대신 C에서 값으로 구조체를 전달하는 데 단점이 있습니까?


포인터를 전달하는 대신 C에서 값으로 구조체를 전달하는 데 단점이 있습니까?

구조체가 큰 경우 많은 양의 데이터를 복사하는 성능 측면이 분명히 있지만 작은 구조체의 경우 기본적으로 여러 값을 함수에 전달하는 것과 동일해야합니다.

반환 값으로 사용하면 더 재미있을 것입니다. C에는 함수의 단일 반환 값만 있지만 종종 여러 값이 필요합니다. 따라서 간단한 해결책은 구조체에 넣고 반환하는 것입니다.

이에 대한 이유가 있습니까?

내가 여기서 말하는 것에 대해 모든 사람에게 분명하지 않을 수 있으므로 간단한 예를 들어 보겠습니다.

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가 struct blob에 대한 포인터를 갖도록 만들 것이라고 생각하지만 그 이유는 모르겠습니다. 여전히 포인터와 정수를 얻습니다. 모두 함께한다는 것이 훨씬 분명합니다. 그리고 get_data의 경우 길이에 대한 입력 값이 없으며 반환 된 길이가 있어야하기 때문에 앞에서 설명한 방식으로 엉망이 될 수 없습니다.


작은 구조체 (예 : 점, rect)의 경우 값으로 전달하는 것이 완벽하게 허용됩니다. 그러나 속도와는 별도로 큰 구조체를 값으로 신중하게 전달 / 반환해야하는 또 다른 이유는 스택 공간입니다.

많은 C 프로그래밍은 임베디드 시스템을위한 것이며, 메모리가 부족하고 스택 크기는 KB 또는 바이트로 측정 될 수 있습니다. 값으로 구조체를 전달하거나 반환하면 해당 구조체의 복사본이 배치됩니다. 이 사이트의 이름을 딴 상황 발생할 가능성 이 있습니다 ...

과도한 스택 사용을 가진 것으로 보이는 응용 프로그램을 보면 값으로 전달되는 구조체가 내가 가장 먼저 찾는 것 중 하나입니다.


언급되지 않은 이것을하지 않는 한 가지 이유는 바이너리 호환성이 중요한 문제를 야기 할 수 있기 때문입니다.

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

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

-fpcc-struct-return

Freg-struct-return

두 컴파일러가 동의하지 않으면 문제가 발생할 수 있습니다. 이 작업을 수행하지 않는 주요 이유는 스택 소비 및 성능 이유입니다.


이 질문에 실제로 답하기 위해서는 의회 땅을 깊이 파고 들어야합니다.

(다음 예제는 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()하나보다 적은 명령을 내린다give_two_doubles()

What's interesting is that we notice the compiler has been able to optimize mov into the faster SSE2 variants movapd and movsd. Furthermore, give_two_doubles() actually moves data in and out from memory, which makes things slow.

Apparently much of this may not be applicable in embedded environments (which is where the playing field for C is most of the time nowdays). I'm not an assembly wizard so any comments would be welcome!


Simple solution will be return an error code as a return value and everything else as a parameter in the function,
This parameter can be a struct of course but don't see any particular advantage passing this by value, just sent a pointer.
Passing structure by value is dangerous, you need to be very careful what are you passing are, remember there is no copy constructor in C, if one of structure parameters is a pointer the pointer value will be copied it might be very confusing and hard to maintain.

Just to complete the answer (full credit to Roddy ) the stack usage is another reason not pass structure by value, believe me debugging stack overflow is real PITA.

Replay to comment:

Passing struct by pointer meaning that some entity has an ownership on this object and have a full knowledge of what and when should be released. Passing struct by value create a hidden references to the internal data of struct (pointers to another structures etc .. ) at this is hard to maintain (possible but why ?) .


I think that your question has summed things up pretty well.

One other advantage of passing structs by value is that memory ownership is explicit. There is no wondering about if the struct is from the heap, and who has the responsibility for freeing it.


I'd say passing (not-too-large) structs by value, both as parameters and as return values, is a perfectly legitimate technique. One has to take care, of course, that the struct is either a POD type, or the copy semantics are well-specified.

Update: Sorry, I had my C++ thinking cap on. I recall a time when it was not legal in C to return a struct from a function, but this has probably changed since then. I would still say it's valid as long as all the compilers you expect to use support the practice.


One thing people here have forgotten to mention so far (or I overlooked it) is that structs usually have a padding!

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

Every char is 1 byte, every short is 2 bytes. How large is the struct? Nope, it's not 6 bytes. At least not on any more commonly used systems. On most systems it will be 8. The problem is, the alignment is not constant, it's system dependent, so the same struct will have different alignment and different sizes on different systems.

Not only that padding will further eat up your stack, it also adds the uncertainty of not being able to predict the padding in advance, unless you know how your system pads and then look at every single struct you have in your app and calculate the size for it. Passing a pointer takes a predictable amount of space -- there is no uncertainty. The size of a pointer is known for the system, it is always equal, regardless of what the struct looks like and pointer sizes are always chosen in a way that they are aligned and need no padding.


Here's something no one mentioned:

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
}

Members of a const struct are const, but if that member is a pointer (like char *), it becomes char *const rather than the const char * we really want. Of course, we could assume that the const is documentation of intent, and that anyone who violates this is writing bad code (which they are), but that's not good enough for some (especially those who just spent four hours tracking down the cause of a crash).

The alternative might be to make a struct const_blob { const char *c; size_t l } and use that, but that's rather messy - it gets into the same naming-scheme problem I have with typedefing pointers. Thus, most people stick to just having two parameters (or, more likely for this case, using a string library).


Page 150 of PC Assembly Tutorial on http://www.drpaulcarter.com/pcasm/ has a clear explanation about how C allows a function to return a struct:

C also allows a structure type to be used as the return value of a func- tion. Obviously a structure can not be returned in the EAX register. Different compilers handle this situation differently. A common solution that compilers use is to internally rewrite the function as one that takes a structure pointer as a parameter. The pointer is used to put the return value into a structure defined outside of the routine called.

I use the following C code to verify the above statement:

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;
}

Use "gcc -S" to generate assembly for this piece of C code:

    .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

The stack before call create:

        +---------------------------+
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)           |
        +---------------------------+

The stack right after calling create:

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

I just want to point one advantage of passing your structs by value is that an optimizing compiler may better optimize your code.

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

반응형