嵌套结构和c中的严格别名
请考虑以下代码:
typedef struct { int type; } object_t; typedef struct { object_t object; int age; } person_t; int age(object_t *object) { if (object->type == PERSON) { return ((person_t *)object)->age; } else { return 0; } }
这是合法代码还是违反了C99严格别名规则? 请解释为何合法/非法。
严格别名规则是指引用内存中相同位置的两个不同类型的指针(ISO / IEC9899 / TC2) 。 虽然您的示例将object_t object
的地址重新解释为person_t
的地址,但它不会通过重新解释的指针引用object_t
内存位置,因为age
位于object_t
的边界之外。 由于通过指针引用的内存位置不相同,我会说它没有违反严格的别名规则。 FWIW, gcc -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99
似乎同意该评估,并且不会产生警告。
但这还不足以确定它是合法代码:您的示例假设嵌套结构的地址与其外部结构的地址相同。 顺便提一下,根据C99标准,这是一个安全的假设:
6.7.2.1-13。 指向适当转换的结构对象的指针指向其初始成员
上面两个考虑使我认为您的代码是合法的。
严格别名规则限制您访问对象(内存区域)的类型。 代码中可能会出现一些地方: age()
和调用age()
。
在age
,你有权考虑。 ((person_t *)object)
是一个左值表达式,因为它有一个对象类型,它指定一个对象(一个内存区域)。 但是,只有在object->type == PERSON
才会到达分支,因此(大概)对象的有效类型是person_t*
,因此person_t*
不会违反严格的别名。 特别是,严格的别名允许:
- 与对象的有效类型兼容的类型,
调用age()
,您可能会传递一个object_t*
或一个从object_t
下降的类型:一个以object_t
作为第一个成员的结构。 这被允许为:
- 聚合或联合类型,包括其成员中的上述类型之一
此外,严格混叠的要点是允许将加载值优化到寄存器中。 如果一个对象通过一个指针进行了变异,那么假定不兼容类型的指针所指向的任何东西都保持不变,因此不需要重新加载。 代码不会修改任何内容,因此不应受优化的影响。
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
作为接受答案的附加内容,这里是标准的完整引用,重要部分突出显示另一个答案被省略,还有一个:
6.7.2.1-13:在结构对象中,非位字段成员和位域所在的单元具有按声明顺序增加的地址。 指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元), 反之亦然 。 结构对象中可能存在未命名的填充,但不是在其开头。
6.3.2.3-7:指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。 如果结果指针未针对指向类型正确对齐,则行为未定义。 否则,当再次转换回来时,结果应该等于原始指针。 […]
我发现你的例子是一个无效指针的完美位置:
int age(void *object) {
为什么? 因为你的明显意图是为这样的函数提供不同的“对象”类型,并且它根据编码类型获取信息。 在你的版本中,每次调用函数时都需要一个演员: age((object_t*)person);
。 当你给出错误的指针时,编译器不会抱怨,所以无论如何都不涉及类型安全。 然后你也可以使用一个void指针,并在调用函数时避免使用。
或者,您可以使用age(&person->object)
调用函数,当然。 每次你打电话。
标准明确宽恕的一种可接受的方式是使具有相同初始段的结构联合起来,如下所示:
struct tag { int value; }; struct obj1 { int tag; Foo x; Bar y; }; struct obj2 { int tag; Zoo z; Car w; }; typedef union object_ { struct tag; struct obj1; struct obj2; } object_t;
现在,您可以传递一个object_t * p
并检查p->tag.value
而不受惩罚,然后访问所需的union成员。