매개 변수 또는 반환 값으로 C 구조체를 초기화해야합니까? [닫은] 내가 일하는 회사는 다음과 같은 초기화 기능을

내가 일하는 회사는 다음과 같은 초기화 기능을 통해 모든 데이터 구조를 초기화하고 있습니다.

//the structure
typedef struct{
  int a,b,c;  
} Foo;

//the initialize function
InitializeFoo(Foo* const foo){
   foo->a = x; //derived here based on other data
   foo->b = y; //derived here based on other data
   foo->c = z; //derived here based on other data
}

//initializing the structure  
Foo foo;
InitializeFoo(&foo);

나는 다음과 같이 내 구조체를 초기화하려고 시도했다.

//the structure
typedef struct{
  int a,b,c;  
} Foo;

//the initialize function
Foo ConstructFoo(int a, int b, int c){
   Foo foo;
   foo.a = a; //part of parameter input (inputs derived outside of function)
   foo.b = b; //part of parameter input (inputs derived outside of function)
   foo.c = c; //part of parameter input (inputs derived outside of function)
   return foo;
}

//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);

다른 것보다 장점이 있습니까?
어느 것을해야합니까? 더 나은 방법으로 어떻게 정당화 할 수 있습니까?



답변

두 번째 접근 방식에서는 절대 초기화되지 않은 Foo가 없습니다. 모든 구조를 한곳에 두는 것이 더 합리적이고 명백한 장소입니다.

그러나 … 첫 번째 방법은 그렇게 나쁘지 않으며 많은 분야에서 자주 사용됩니다 (1 차 방법과 같은 속성 주입 또는 2 차와 같은 생성자 주입 중 가장 좋은 의존성 주입 방법에 대한 토론도 있습니다) . 둘 다 잘못이 아닙니다.

따라서 어느 쪽도 잘못이 아니고 회사의 나머지 부분이 접근법 # 1을 사용하는 경우 기존 코드베이스에 적합해야하며 새로운 패턴을 도입하여 엉망으로 만들지 않아야합니다. 이것은 실제로 여기서 가장 중요한 요소이며, 새로운 친구들과 즐겁게 놀고 다른 일을하는 특별한 눈송이가되지 마십시오.


답변

두 방법 모두 초기화 코드를 단일 함수 호출로 묶습니다. 여태까지는 그런대로 잘됐다.

그러나 두 번째 방법에는 두 가지 문제가 있습니다.

  1. 두 번째 것은 실제로 결과 객체를 구성하지 않고 스택의 다른 객체를 초기화 한 다음 최종 객체로 복사합니다. 이것이 두 번째 접근법이 약간 열등한 것으로 보는 이유입니다. 이 외부 사본으로 인해 푸시 백이 발생했을 수 있습니다.

    당신이 클래스 파생 때 심지어 더 나쁜 Derived에서을 Foo(구조체는 주로 C에서 객체 지향에 사용되는) : 두 번째 방법으로, 함수가 ConstructDerived()호출 것 ConstructFoo(), 그 결과 임시 복사본 Fooa의 슈퍼 클래스 슬롯에 걸쳐 객체를 Derived객체; Derived객체 의 초기화를 완료하십시오 . 결과 객체를 반환 할 때 다시 복사해야합니다. 세 번째 레이어를 추가하면 모든 것이 완전히 어리 석습니다.

  2. 두 번째 접근 방식을 사용하면 ConstructClass()기능이 건설중인 객체의 주소에 액세스 할 수 없습니다. 따라서 콜백을 위해 객체를 다른 객체에 등록해야 할 때 필요하므로 생성 중에 객체를 연결할 수 없습니다.


마지막으로, 모두 structs가 본격적인 수업이 아닙니다 . 일부 structs는 이러한 변수 값에 대한 내부 제한없이 여러 변수를 효과적으로 묶습니다. typedef struct Point { int x, y; } Point;이것의 좋은 예가 될 것입니다. 이러한 경우 초기화 기능을 사용하는 것은 과도한 것으로 보입니다. 이 경우 복합 리터럴 구문이 편리 할 수 ​​있습니다 (C99 임).

Point = { .x = 7, .y = 9 };

또는

Point foo(...) {
    //other stuff

    return (Point){ .x = n, .y = n*n };
}


답변

구조의 내용과 사용중인 특정 컴파일러에 따라 두 가지 접근 방식이 더 빠를 수 있습니다. 일반적인 패턴은 특정 기준을 충족하는 구조가 레지스터로 반환 될 수 있다는 것입니다. 다른 구조 유형을 리턴하는 함수의 경우 호출자는 임시 구조를위한 공간을 어딘가에 (일반적으로 스택에) 할당하고 주소를 “숨겨진”매개 변수로 전달해야합니다. 함수의 반환 값이 외부 코드에 의해 주소가 유지되지 않는 로컬 변수에 직접 저장되는 경우 일부 컴파일러는 해당 변수의 주소를 직접 전달할 수 있습니다.

구조 유형이 함수 리턴을 갖는 레지스터 (예를 들어, 하나의 기계어보다 크거나 정확히 두 개의 기계어를 채우는 것)에 반환되어야하는 특정 구현의 요구 사항을 충족하는 경우 구조는 구조의 주소를 전달하는 것보다 빠를 수 있습니다. 변수의 주소를 외부 코드에 노출하면 복사본을 유지할 수 있으므로 유용한 최적화가 불가능할 수 있습니다. 형식이 이러한 요구 사항을 충족하지 않으면 구조체를 반환하는 함수에 대해 생성 된 코드는 대상 포인터를 허용하는 함수의 코드와 유사합니다. 포인터를 사용하는 양식의 경우 호출 코드가 더 빠를 수 있지만 해당 양식은 일부 최적화 기회를 잃습니다.

너무 나쁘다 C는 함수가 전달 된 포인터 (C ++ 참조와 비슷한 의미)의 사본을 유지하는 것이 금지되어 있다고 말하는 수단을 제공하지 않습니다. 왜냐하면 제한된 포인터를 전달하면 전달의 직접적인 성능 이점을 얻을 수 있기 때문입니다. 기존 객체에 대한 포인터이지만 동시에 변수의 주소를 “노출 된”것으로 간주하도록 컴파일러에 요구하는 의미 론적 비용을 피하십시오.


답변

“output-parameter”스타일을 선호하는 한 가지 주장은 함수가 오류 코드를 반환 할 수 있다는 것입니다.

struct MyStruct {
    int x;
    char *y;
    // ...
};

int MyStruct_init(struct MyStruct *out) {
    // ...
    char *c = malloc(n);
    if (!c) {
        return -1;
    }
    out->y = c;
    return 0;  // Success!
}

관련 구조체 세트를 고려할 때 초기화가 실패하면 일관성을 위해 모든 매개 변수를 매개 변수 외부 스타일로 사용하는 것이 좋습니다.


답변

나는 건설 인수가 제공되는 방식의 불일치가 아니라 출력 매개 변수를 통한 초기화 대 반환을 통한 초기화에 중점을두고 있다고 가정합니다.

첫 번째 접근 방식은 Foo불투명 할 수 있으며 (현재 사용하는 방식이 아님에도 불구하고) 일반적으로 장기적인 유지 관리에 바람직합니다. 예를 들어, Foo초기화하지 않고 불투명 한 구조체 를 할당하는 함수를 고려할 수 있습니다. 또는 Foo이전에 다른 값 으로 초기화 된 구조체 를 다시 초기화해야 할 수도 있습니다 .


답변