정보 숨기기
함수의 return 문에서 전체 구조를 반환하는 대신 구조체에 대한 포인터를 반환하면 어떤 이점이 있습니까?
가장 일반적인 것은 정보 숨기기 입니다. C는 struct
개인의 필드를 만들 수있는 능력을 가지고 있지 않으며, 액세스 할 수있는 방법을 제공 하지는 않습니다 .
따라서 개발자가와 같이 포인트의 내용을보고 조작 할 수 FILE
없도록하려면, 포인터를 포인트 크기가 불투명 한 포인터로 포인터를 처리하여 정의에 노출되지 않도록하는 유일한 방법이 있습니다. 외부 세계에 대한 정의는 알려져 있지 않다. 그러면의 정의는 FILE
정의와 같은 정의를 요구하는 오퍼레이션을 구현하는 사용자에게만 표시되는 fopen
반면, 구조 선언 만 공개 헤더에 표시됩니다.
이진 호환성
구조 정의를 숨기면 dylib API에서 이진 호환성을 유지할 수있는 호흡 공간을 제공 할 수 있습니다. 이를 통해 라이브러리 구현자는 라이브러리를 사용하는 사용자와 바이너리 호환성을 유지하면서 불투명 구조의 필드를 변경할 수 있습니다. 코드의 특성상 구조의 크기 나 필드가 아니라 구조로 수행 할 수있는 작업 만 알면되기 때문입니다. 그렇습니다.
예를 들어, 오늘날 Windows 95 시대에 만들어진 일부 고대 프로그램을 실제로 실행할 수 있습니다 (항상 완벽하지는 않지만 놀랍게도 많은 것이 여전히 작동합니다). 고대 바이너리에 대한 일부 코드는 Windows 95 시대에서 크기와 내용이 변경된 구조에 대해 불투명 한 포인터를 사용했을 가능성이 있습니다. 그러나 프로그램은 이러한 구조의 내용에 노출되지 않았기 때문에 새로운 버전의 창에서 계속 작동합니다. 이진 호환성이 중요한 라이브러리에서 작업 할 때 일반적으로 클라이언트에 노출되지 않는 항목은 이전 버전과의 호환성을 유지하면서 변경 될 수 있습니다.
능률
NULL 인 전체 구조를 반환하는 것은 더 어렵거나 덜 효율적입니다. 이것이 유효한 이유입니까?
일반적으로 malloc
이미 할당 된 가변 크기 할당 자 풀링 메모리보다는 고정 크기 크기보다 고정 된 크기의 메모리 할당자가 씬보다 훨씬 덜 일반적으로 사용되지 않는 한 유형에 실제로 적합하고 스택에 할당 될 수 있다고 가정하면 일반적으로 효율성이 떨어집니다 . 이 경우 라이브러리 개발자가와 관련된 불변 (개념적 보증)을 유지할 수있게하는 것이 안전 트레이드 오프입니다 FILE
.
적어도 성능 관점에서 fopen
포인터를 반환하는 것은 정당한 이유가 아닙니다. 반환되는 유일한 이유 NULL
는 파일을 열지 못했기 때문입니다. 그것은 모든 일반적인 실행 경로를 느리게하는 대신에 탁월한 시나리오를 최적화하는 것입니다. 경우에 따라 디자인을 좀 더 직관적으로 만들어서 NULL
일부 사후 조건에서 반환 될 수 있도록 포인터를 반환하도록하는 유효한 생산성 이유가있을 수 있습니다 .
파일 작업의 경우 파일 작업 자체에 비해 오버 헤드가 상대적으로 매우 작 fclose
으므로 수동 으로 피할 수 없습니다. 따라서 힙 할당을 피하기 위해 파일 작업 자체의 상대적 비용을 감안할 때 정의를 노출하고 FILE
값으로 반환 fopen
하거나 성능을 크게 향상 시켜 클라이언트를 자원 확보 (닫기) 번거 로움을 덜 수있는 것은 아닙니다. .
핫스팟 및 수정
그러나 다른 경우에는 핫스팟이 malloc
있고 불필요한 캐시 캐시 누락이 있는 레거시 코드베이스에서 많은 낭비적인 C 코드를 프로파일 링했습니다. 이 방법은 불투명 포인터로 너무 자주 사용하고 너무 많은 것을 힙에 불필요하게 할당 한 결과 큰 루프.
내가 대신 사용하는 다른 방법은 클라이언트가 변조를 의도하지 않더라도 명명 규칙 표준을 사용하여 다른 사람이 필드를 만지면 안된다는 의사 소통을 통해 구조 정의를 공개하는 것입니다.
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
};
struct Foo foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_something(struct Foo* foo);
미래에 바이너리 호환성 문제가 있다면 미래의 목적을 위해 여분의 공간을 여분으로 예약하는 것이 충분하다는 것을 알았습니다.
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
/* reserved for possible future uses (emergency backup plan).
currently just set to null. */
void* priv_reserved;
};
예약 된 공간은 약간 낭비 적이지만 나중에 Foo
라이브러리를 사용하는 바이너리를 깨지 않고 더 많은 데이터를 추가해야 할 경우 생명을 구할 수 있습니다 .
내 의견으로는 정보 숨기기 및 이진 호환성은 일반적으로 가변 길이 구조체 이외의 구조의 힙 할당 만 허용하는 유일한 이유입니다 (항상 필요하거나 클라이언트가 할당 해야하는 경우 적어도 사용하기가 조금 어색합니다) VLS를 할당하기 위해 VLA 방식으로 스택의 메모리). 큰 구조체조차도 소프트웨어가 스택의 핫 메모리로 훨씬 더 많이 작동한다는 것을 의미한다면 값으로 반환하는 것이 더 저렴합니다. 그리고 그들이 창조 할 때 가치로 돌아 오는 것이 더 저렴하지 않더라도 간단하게 이것을 할 수 있습니다.
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
Foo
불필요한 사본을 만들지 않고 스택에서 초기화 합니다. 또는 클라이언트가 Foo
어떤 이유로 원하는 경우 힙에 자유롭게 할당 할 수도 있습니다 .