programing

C에서 구조물 확장

goodcopy 2022. 7. 19. 21:24
반응형

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를 사용하다

네, 크로스 플랫폼에서도 동작하지만 그렇다고(a) 해서 반드시 좋은 생각이라고는 할 수 있습니다.

C 은 모두 인용)에 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) 일반적인 의미에서.물론 코드 조각은 다음과 같습니다.

B *b;
((A*)b)->x = 10;

되지 않은 .b이치노하지만 이건 당신의 질문을 설명하기 위한 예시 코드라고 가정하겠습니다.우려하는 사람이 있다면, 대신 다음과 같이 생각하십시오.

B b, *pb = &b;
((A*)pb)->x = 10;

(b) 제 아내가 당신에게 말하듯이, 자주 그리고 별로 재촉하지 않고 :-)

츠미야누군가가 와서 구조 B의 전면에 다른 필드를 삽입하면 프로그램이 폭발합니다.그리고 뭐가 문제죠?b.a.x

나는 위험을 무릅쓰고 @paxdiablo에 반대한다.좋은 아이디어라고 생각합니다.대규모의 생산품질 코드에서는 매우 일반적입니다.

이는 기본적으로 C에서 상속 기반 객체 지향 데이터 구조를 구현하는 가장 명확하고 좋은 방법입니다.을 시작합니다.struct B「 」의 struct A는 "A의 를 의미합니다. "B" 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#도 정기적으로 "c.b.a"와 같은 구성 요소를 노출하지만 문제가 무엇인지 알 수 없습니다.시뮬레이트 하는 것이 오브젝트 지향의 동작이라면 오브젝트 지향의 언어(C++ 등)를 사용하는 것을 검토해야 합니다.그것은, 제안 방법의 「확장 구조」는 캡슐화나 런타임 폴리모피즘을 제공하지 않기 때문입니다(단, (A*)b는 「다이나믹 캐스트」와 비슷하다고 주장할 수도 있습니다).

매크로를 사용하여 이 기능을 구현하고 함수 또는 필드를 매크로에 재사용하는 것을 고려해 볼 수 있습니다.

죄송하지만 이 시스템은 표준 C에 준거하고 있지 않습니다.같은 장소를 동시에 가리키는 타입이 다른2개의 포인터는 사용할 수 없습니다.이것을 에일리어싱이라고 부릅니다.C99 및 기타 많은 표준에서는 엄밀한 에일리어싱 규칙에 의해 허용되지 않습니다.이렇게 하면 깔끔하게 보이지 않는 인라인 게터 기능을 사용할 수 있습니다.아니면 이게 조합의 일일까요?몇 가지 유형 중 하나를 보유할 수 있지만, 다른 수많은 단점도 있습니다.

즉, C의 표준에서는 컴파일러에서 동작하는 것처럼 보인다고 해서 이러한 종류의 더러운 캐스팅이 허용 가능한 것은 아닙니다.이것이 허용되지 않는 이유와 높은 최적화 수준의 컴파일러가 이러한 규칙을 따르지 않는 코드를 해독할 수 있는 이유는 여기를 참조하십시오.http://en.wikipedia.org/wiki/Aliasing_%28computing%29#Conflicts_with_optimization

네, 잘 될 거예요.그리고 C를 이용한 객체 지향의 핵심 원리 중 하나입니다.확장(예: 상속)에 대한 자세한 예는 이 답변 'C의 객체 방향'을 참조하십시오.

이것은 완벽하게 합법적이고, 내 생각에, 꽤 우아하다.실가동 코드의 예에 대해서는, 다음의 GObject 문서를 참조해 주세요.

이러한 간단한 조건 덕분에 다음 작업을 수행하여 모든 객체인스턴스의 유형을 검출할 수 있습니다.

B *b;
b->parent.parent.g_class->g_type

또는 보다 신속하게:

B *b;
((GTypeInstance*)b)->g_class->g_type

개인적으로 나는 조합이 추악하고 거대하게 이끄는 경향이 있다고 생각한다.switch스테이트먼트라고 하는 것은, OO 코드를 쓰는 것으로 회피해 온 것의 큰 부분을 차지합니다.저는 이런 스타일로 상당히 많은 양의 코드를 직접 씁니다--일반적으로, 첫 번째 멤버입니다.struct에는 해당 유형의 vtable과 같이 기능하도록 설정할 수 있는 함수 포인터가 포함되어 있습니다.

나는 이것이 어떻게 작동하는지 알 수 있지만 나는 이것을 좋은 관행이라고 부르지 않을 것이다.이는 각 데이터 구조의 바이트가 메모리에 배치되는 방법에 따라 달라집니다.복잡한 데이터 구조(즉, 구조)를 다른 구조(구조)에 캐스팅할 때는 특히 두 구조의 크기가 같지 않은 경우에는 그다지 좋은 생각이 아닙니다.

나는 OP와 많은 해설자들이 그 코드가 구조를 확장시키고 있다는 것을 알아챘다고 생각한다.

그것은 아니다.

이것이 작문의 예시입니다.매우 편리합니다.(typedefs를 삭제하면 다음 예시를 참조해 주세요.

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

적절하게 구분된 고정 크기의 버퍼가 있습니다.거대한 단일 계층 구조를 능가합니다.

struct double_order {
  struct order order;
  struct item extra_items[MAX_ITEMS];
  struct accessory extra_accessories[MAX_ACCESSORIES];

};

이제 명시적 캐스트를 가진 첫 번째 구조와 똑같이 처리할 수 있는 두 번째 구조를 갖게 되었습니다.

struct double_order d;
fn((order *)&d);

이렇게 하면 더 작은 구조에서 작동하도록 작성된 코드와의 호환성이 유지됩니다.Linux 커널(http://lxr.free-electrons.com/source/include/linux/spi/spi.h(구조 spi_device 참조)과 bsd 소켓 라이브러리(http://beej.us/guide/bgnet/output/html/multipage/sockaddr_inman.html)) 모두 이 방법을 사용합니다.커널과 소켓의 경우 코드의 일반 섹션과 차별화된 섹션을 모두 통해 실행되는 구조가 있습니다.상속의 사용 사례와 크게 다르지 않습니다.

나는 단지 가독성을 위해 그런 구조를 쓰는 것을 제안하지 않을 것이다.

Postgres도 코드 중 일부에서 이 작업을 수행한다고 생각합니다.그것이 좋은 생각이 되는 것은 아니지만, 그것이 얼마나 널리 받아들여지는지를 말해준다.

언급URL : https://stackoverflow.com/questions/22218604/extending-a-struct-in-c

반응형