[c] 포인터를 전달하는 대신 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()

흥미로운 점은 컴파일러가 mov더 빠른 SSE2 변형 movapd및 로 최적화 할 수 있다는 점 movsd입니다. 또한 give_two_doubles()실제로 메모리에서 데이터를주고 받음으로써 속도가 느려집니다.

분명히이 중 많은 부분이 임베디드 환경 (C의 경기장이 현재 대부분의 시간)에 적용되지 않을 수 있습니다. 나는 어셈블리 마법사가 아니므로 의견을 환영합니다!


답변

간단한 해결책은 오류 코드를 반환 값으로 반환하고 그 밖의 모든 것을 함수의 매개 변수로 반환합니다.
이 매개 변수는 구조체 일 수는 있지만 값으로 전달하는 특별한 이점을 보지 못하고 포인터를 보냈습니다.
값으로 구조를 전달하는 것은 위험합니다. 전달하는 것이 매우 신중해야합니다 .C에 복사 생성자가 없다는 것을 기억하십시오. 구조 매개 변수 중 하나가 포인터 인 경우 포인터 값이 복사되면 매우 혼란스럽고 어려울 수 있습니다 유지하십시오.

단지에 대한 답 (전체 신용 완료 로디을 ) 스택 사용은 값으로 구조를 전달하지 않는 또 다른 이유입니다. 스택 오버플로 디버깅이 실제 PITA라고 생각합니다.

댓글 재생 :

포인터로 구조체를 전달하면 일부 엔티티 가이 객체에 대한 소유권을 가지고 있으며 언제 그리고 언제 릴리스되어야 하는지를 완전히 알고 있습니다. 값으로 구조체를 전달하면 구조체의 내부 데이터에 대한 숨겨진 참조 (다른 구조 등을 가리키는 포인터 등)가 유지 관리하기가 어렵습니다 (가능한 이유는 무엇입니까?).


답변

여기 사람들이 지금까지 언급 한 것을 잊어 버린 한 가지 사실은 구조체에 보통 패딩이 있다는 것입니다!

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

모든 문자는 1 바이트이고 모든 짧은 문자는 2 바이트입니다. 구조체는 얼마나 큽니까? 아니요, 6 바이트가 아닙니다. 적어도 더 일반적으로 사용되는 시스템에는 없습니다. 대부분의 시스템에서는 8입니다. 문제는 정렬이 일정하지 않고 시스템에 따라 다르므로 동일한 구조체는 시스템마다 다른 정렬 및 크기를 갖습니다.

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


답변

나는 당신의 질문이 일들을 잘 요약했다고 생각합니다.

값으로 구조체를 전달할 때의 또 다른 이점은 메모리 소유권이 명시 적이라는 것입니다. 구조체가 힙에서 왔는지 누가 풀어야 할 책임이 있는지 궁금하지 않습니다.


답변

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

업데이트 : 죄송합니다. C ++ 사고 제한이 있습니다. C에서 함수에서 구조체를 반환하는 것이 합법적이지 않은 시간을 회상하지만 그 이후로 아마도 변경되었을 것입니다. 사용하려는 모든 컴파일러가 연습을 지원하는 한 여전히 유효하다고 말하고 싶습니다.