C中的结构和铸造

我在想:

如果我有结构定义,例如,像这样:

struct Base { int foo; }; struct Derived { int foo; // int foo is common for both definitions char *bar; }; 

我可以这样做吗?

 void foobar(void *ptr) { ((struct Base *)ptr)->foo = 1; } struct Derived s; foobar(&s); 

例如,当其类型实际为Derived *时,将void指针强制转换为Base *来访问它的foo

许多真实世界的C程序假设您显示的构造是安全的,并且对C标准(特别是“公共初始序列”规则,C99§6.5.2.3p5)进行了解释,在该标准下它是符合的。 不幸的是,在我最初回答这个问题的五年中,我可以很容易地得到的所有编译器(即GCC和Clang)都集中在对常见的初始序列规则的不同的,更窄的解释上,在此规则下你所展示的构造引起了挑衅未定义的行为。 具体来说,试验这个程序:

 #include  #include  typedef struct A { int x; int y; } A; typedef struct B { int x; int y; float z; } B; typedef struct C { A a; float z; } C; int testAB(A *a, B *b) { b->x = 1; a->x = 2; return b->x; } int testAC(A *a, C *c) { c->ax = 1; a->x = 2; return c->ax; } int main(void) { B bee; C cee; int r; memset(&bee, 0, sizeof bee); memset(&cee, 0, sizeof cee); r = testAB((A *)&bee, &bee); printf("testAB: r=%d bee.x=%d\n", r, bee.x); r = testAC(&cee.a, &cee); printf("testAC: r=%d cee.x=%d\n", r, cee.ax); return 0; } 

在启用优化的情况下进行编译(并且没有-fno-strict-aliasing )时,GCC和Clang都会假设testAB的两个指针参数不能指向同一个对象 ,所以我得到的输出就像

 testAB: r=1 bee.x=2 testAC: r=2 cee.x=2 

他们没有为testAC做出这样的假设,但是 – 之前一直认为testAB需要被编译,好像它的两个参数可以指向同一个对象 – 我对自己对标准的理解不再有足够的信心了说是否保证继续工作。

你应该做

 struct Base { int foo; }; struct Derived { struct Base base; char *bar; }; 

避免破坏严格的别名; C允许指针类型的任意转换是一种常见的误解:尽管它在大多数实现中都会按预期工作,但它是非标准的。

这也避免了由于使用pragma指令而导致的任何对齐不兼容性。

这将适用于这种特殊情况。 两个结构和命中的第一个成员中的foo字段具有相同的类型。 但是,在结构中的字段(不是第一个成员)的一般情况下,情况并非如此。 对齐和包装等项目可以以微妙的方式打破这种局面。

因为您似乎瞄准CI中的面向对象编程可以建议您查看以下链接:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

它详细介绍了在ANSI C中处理oop原则的方法。

在特殊情况下,这可能有效,但总的来说 – 不,因为结构对齐。

您可以使用不同的#pragma来使(实际上,尝试)对齐相同 – 然后,是的,这将起作用。

如果您正在使用microsoft visual studio,您可能会发现本文很有用。

还有一件小事可能对您正在做的事情有所帮助或相关。

 #define SHARED_DATA int id; typedef union base_t { SHARED_DATA; window_t win; list_t list; button_t button; } typedef struct window_t { SHARED_DATA; int something; void* blah; } typedef struct window_t { SHARED_DATA; int size; } typedef struct button_t { SHARED_DATA; int clicked; } 

现在你可以将共享属性放入SHARED_DATA并通过打包到联合中的“超类”来处理不同的类型。你可以使用SHARED_DATA来存储一个“类标识符”或存储一个指针。无论哪种方式,它都很方便在某些时候我可以对事件类型进行通用处理。 希望我不要偏离这个主题

我知道这是一个老问题,但在我看来,还有更多可以说的,其他一些答案是不正确的。

首先,这个演员:

 (struct Base *)ptr 

…是允许的,但仅在满足对齐要求时才允许。 在许多编译器上,您的两个结构将具有相同的对齐要求,并且在任何情况下都很容易validation。 如果你超越了这个障碍,那么下一个是强制转换的结果大部分是未指定的 – 也就是说,在C标准中没有要求指针一旦转换仍然引用同一个对象(仅在将其转换回原始对象之后)它必然会这样做)。

但是,在实践中,通用系统的编译器通常会使指针转换的结果引用同一个对象。

(C99标准和最近的C11标准的第6.3.2.3节都涉及指针演员。我相信两者的规则基本相同)。

最后,你有所谓的“严格别名”规则来应对(C99 / C11 6.5第7段); 基本上,不允许您通过另一种类型的指针访问一种类型的对象(某些例外,在您的示例中不适用)。 请参阅“什么是严格别名规则?” ,或者进行非常深入的讨论,阅读我关于这个主题的博客文章 。

总之,您在代码中尝试的内容无法保证正常运行。 它可能保证总是与某些编译器(以及某些编译器选项)一起使用,并且它可能偶然地与许多编译器一起工作,但它肯定会根据C语言标准调用未定义的行为。

可以做的是:

 *((int *)ptr) = 1; 

…即,因为你知道结构的第一个成员是一个int ,你只需直接转换为int ,它绕过了别名问题,因为这两种类型的struct实际上在这个地址都包含一个int 。 您依赖于了解编译器将使用的结构布局,并且您仍然依赖于指针转换的非标准语义,但实际上,这对您提出问题的可能性要小得多。

关于C的好处/坏处是你可以投出任何东西 – 问题是,它可能不起作用。 :)但是,在你的情况下,它将*,因为你有两个结构,其第一个成员都是相同的类型; 看一下这个程序的例子。 现在,如果struct derived的第一个元素有不同的类型 – 例如, char *bar – 那么不,你会得到奇怪的行为。

我认为,我应该用“几乎总是”来certificate这一点; 那里有很多不同的C编译器,所以有些可能有不同的行为。 但是,我知道它会在海湾合作委员会中发挥作用。