C에서 구조체 확장
최근에 다음과 같은 동료의 코드를 발견했습니다.
typedef struct A {
int x;
}A;
typedef struct B {
A a;
int d;
}B;
void fn(){
B *b;
((A*)b)->x = 10;
}
그의 설명은 이래로 struct A
의 첫 번째 멤버 struct B
이므로 b->x
와 동일 b->a.x
하고 더 나은 가독성을 제공합니다.
이건 말이되지만 좋은 습관으로 간주 되나요? 그리고 이것이 여러 플랫폼에서 작동할까요? 현재 이것은 GCC에서 잘 실행됩니다.
예, 크로스 플랫폼에서 작동하지만 반드시 좋은 생각 은 아닙니다 .
ISO C 표준 (아래의 모든 인용은 C11에서 가져온 6.7.2.1 Structure and union specifiers /15
것임) 에 따라 구조의 첫 번째 요소 앞에 패딩을 넣을 수 없습니다.
또한 다음과 6.2.7 Compatible type and composite type
같이 명시합니다.
두 유형은 유형이 동일하면 호환 가능한 유형이 있습니다.
그리고이 있는지 다툼된다 A
및 A-within-B
유형이 동일하다.
이는 A
필드에 대한 메모리 액세스가 A
및 B
유형 모두에서 동일 할 것임을 b->a.x
의미 하며, 향후 유지 관리 가능성에 대한 우려가있는 경우 사용해야 하는 것이 더 현명 할 수 있습니다.
그리고 일반적으로 엄격한 유형 앨리어싱에 대해 걱정해야하지만 여기에는 적용되지 않는다고 생각합니다. 그것은 이다 별칭 포인터 불법하지만 표준은 특정 예외가있다.
6.5 Expressions /7
각주와 함께 이러한 예외 중 일부를 설명합니다.
이 목록의 목적은 객체가 별칭을 지정하거나 지정하지 않을 수있는 상황을 지정하는 것입니다.
나열된 예외는 다음과 같습니다.
a type compatible with the effective type of the object
;- 여기서 우리와 관련 될 필요가없는 다른 예외들; 과
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union)
.
이는 다음 구문을 포함하여 위에서 언급 한 구조체 패딩 규칙과 결합됩니다.
적절하게 변환 된 구조 객체에 대한 포인터는 초기 멤버를 가리 킵니다.
이 예가 특별히 허용되었음을 나타내는 것 같습니다. 여기서 우리가 기억해야 할 핵심 포인트는 식의 유형이다 ((A*)b)
이다 A*
,하지 B*
. 따라서 무제한 별칭 지정을 위해 변수가 호환됩니다.
그것은 표준의 관련 부분에 대한 내가 읽은 것입니다. (a) 전에는 틀렸지 만이 경우에는 의심 스럽습니다.
그래서, 당신이 이것을 정말로 필요로 한다면 , 그것은 잘 작동 할 것입니다. 그러나 나는 미래에 물리지 않도록 구조에 매우 가까운 코드의 제약을 문서화 할 것입니다.
(a) 아내가 당신에게 말하듯이, 자주 그리고 많은 자극없이 :-)
나는 팔다리로 가서 이것 에 대해 @paxdiablo 에 반대 할 것입니다. 좋은 아이디어라고 생각하며 대규모 프로덕션 품질 코드에서 매우 일반적입니다.
이것은 기본적으로 C에서 상속 기반 객체 지향 데이터 구조를 구현하는 가장 분명하고 좋은 방법입니다. struct B
인스턴스로 선언을 시작하면 struct A
"B는 A의 하위 클래스입니다"를 의미합니다. 첫 번째 구조 멤버가 구조 시작부터 0 바이트로 보장된다는 사실은 구조가 안전하게 작동하도록하는 이유이며 제 생각에는 경계선이 아름답습니다.
GTK + 사용자 인터페이스 툴킷 및 GNOME 데스크탑 환경과 같은 GObject 라이브러리 기반 코드에서 널리 사용되고 배포됩니다 .
물론, "당신이 무엇을하는지 알아야합니다",하지만 그것은 일반적으로 C에서 복잡한 타입 관계를 구현할 때 항상 그렇습니다. :)
GObject 및 GTK +의 경우이를 지원하는 많은 지원 인프라와 문서가 있습니다. 잊어 버리기 매우 어렵습니다. 이는 새 클래스를 만드는 것이 C ++ 에서처럼 빠르게 수행하는 작업이 아니라는 것을 의미 할 수 있지만 C에서 클래스에 대한 기본 지원이 없기 때문에 예상 할 수 있습니다.
유형 검사를 우회하는 것은 일반적으로 피해야합니다. 이 해킹은 선언의 순서에 의존하며 캐스트 나이 순서는 컴파일러에 의해 강제 될 수 없습니다.
크로스 플랫폼에서 작동해야하지만 좋은 습관이라고 생각하지 않습니다.
정말로 깊이 중첩 된 구조가 있다면 (왜 그런지 궁금 할 수도 있습니다), 임시 지역 변수를 사용하여 필드에 액세스해야합니다.
A deep_a = e->d.c.b.a;
deep_a.x = 10;
deep_a.y = deep_a.x + 72;
e->d.c.b.a = deep_a;
또는 a
함께 복사하지 않으려면 :
A* deep_a = &(e->d.c.b.a);
deep_a->x = 10;
deep_a->y = deep_a->x + 72;
이것은 어디에서 왔는지 보여주고 a
캐스트가 필요하지 않습니다.
Java와 C #은 또한 "cba"와 같은 구조를 정기적으로 노출합니다. 문제가 무엇인지 모르겠습니다. 시뮬레이션하려는 것이 객체 지향 동작이라면 객체 지향 언어 (예 : C ++)를 사용하는 것이 좋습니다. 제안하는 방식의 "구조 확장"은 캡슐화 나 런타임 다형성을 제공하지 않기 때문입니다. ((A *) b)는 "동적 캐스트"와 유사합니다).
끔찍한 생각입니다. 누군가가 와서 구조체 B 앞에 다른 필드를 삽입하자마자 프로그램이 폭발합니다. 그리고 무엇이 그렇게 잘못 b.a.x
되었습니까?
I am sorry to disagree with all the other answers here, but this system is not compliant to standard C. It is not acceptable to have two pointers with different types which point to the same location at the same time, this is called aliasing and is not allowed by the strict aliasing rules in C99 and many other standards. A less ugly was of doing this would be to use in-line getter functions which then do not have to look neat in that way. Or perhaps this is the job for a union? Specifically allowed to hold one of several types, however there are a myriad of other drawbacks there too.
In short, this kind of dirty casting to create polymorphism is not allowed by most C standards, just because it seems to work on your compiler does not mean it is acceptable. See here for an explanation of why it is not allowed, and why compilers at high optimization levels can break code which does not follow these rules http://en.wikipedia.org/wiki/Aliasing_%28computing%29#Conflicts_with_optimization
Yes, it will work. And it is one of the core principle of Object Oriented using C. See this answer 'Object-orientation in C' for more examples about extending (i.e inheritance).
This is perfectly legal, and, in my opinion, pretty elegant. For an example of this in production code, see the GObject docs:
Thanks to these simple conditions, it is possible to detect the type of every object instance by doing:
B *b; b->parent.parent.g_class->g_type
or, more quickly:
B *b; ((GTypeInstance*)b)->g_class->g_type
Personally, I think that unions are ugly and tend to lead towards huge switch
statements, which is a big part of what you've worked to avoid by writing OO code. I write a significant amount of code myself in this style --- typically, the first member of the struct
contains function pointers that can be made to work like a vtable for the type in question.
I can see how this works but I would not call this good practice. This is depending on how the bytes of each data structure is placed in memory. Any time you are casting one complicated data structure to another (ie. structs), it's not a very good idea, especially when the two structures are not the same size.
I think the OP and many commenters have latched onto the idea that the code is extending a struct.
It is not.
This is and example of composition. Very useful. (Getting rid of the typedefs, here is a more descriptive example ):
struct person {
char name[MAX_STRING + 1];
char address[MAX_STRING + 1];
}
struct item {
int x;
};
struct accessory {
int y;
};
/* fixed size memory buffer.
The Linux kernel is full of embedded structs like this
*/
struct order {
struct person customer;
struct item items[MAX_ITEMS];
struct accessory accessories[MAX_ACCESSORIES];
};
void fn(struct order *the_order){
memcpy(the_order->customer.name, DEFAULT_NAME, sizeof(DEFAULT_NAME));
}
You have a fixed size buffer that is nicely compartmentalized. It sure beats a giant single tier struct.
struct double_order {
struct order order;
struct item extra_items[MAX_ITEMS];
struct accessory extra_accessories[MAX_ACCESSORIES];
};
So now you have a second struct that can be treated (a la inheritance) exactly like the first with an explicit cast.
struct double_order d;
fn((order *)&d);
This preserves compatibility with code that was written to work with the smaller struct. Both the Linux kernel (http://lxr.free-electrons.com/source/include/linux/spi/spi.h (look at struct spi_device)) and bsd sockets library (http://beej.us/guide/bgnet/output/html/multipage/sockaddr_inman.html) use this approach. In the kernel and sockets cases you have a struct that is run through both generic and differentiated sections of code. Not all that different than the use case for inheritance.
I would NOT suggest writing structs like that just for readability.
I think Postgres does this in some of their code as well. Not that it makes it a good idea, but it does say something about how widely accepted it seems to be.
Perhaps you can consider using macros to implement this feature, the need to reuse the function or field into the macro.
참고URL : https://stackoverflow.com/questions/22218604/extending-a-struct-in-c
'code' 카테고리의 다른 글
축은 numpy의 배열에서 어떻게 인덱싱됩니까? (0) | 2020.11.30 |
---|---|
부트 스트랩 테이블 행 호버 (0) | 2020.11.30 |
교리 캐스케이드 연산 이해 (0) | 2020.11.30 |
Laravel 5 Eloquent where 및 또는 in 절 (0) | 2020.11.30 |
Scala 배열을 Scala vararg 메서드에 전달하는 방법은 무엇입니까? (0) | 2020.11.30 |