请考虑以下代码:
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
的边界之外。因为通过指针引用的内存位置是不一样的,我想说这并没有违反严格的混叠规则。据我所知,gcc -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99
似乎同意这一评估,并没有发出警告。
这并不足以决定它是合法的代码,但是:你的例子假设嵌套结构的地址与其外部结构的地址相同。顺便提一下,根据C99标准,这是一个安全的假设:
以上两点使我认为你的代码是合法的6.7.2.1-13。结构体对象的指针经过适当转换后,指向其初始成员
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
作为被接受的答案的补充,这里是标准的完整引用,突出了重要的部分,省略了其他答案,还有一个:
6.7.2.1-13:在结构对象中,非位域成员和哪些位域的地址按顺序递增它们是被声明的。指向结构对象的指针转换后,指向其初始成员(或者如果该成员是,反之亦然。在结构对象中可以有未命名的填充,但在其开始。
6.3.2.3-7:指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果结果指针未正确对齐指向类型,行为是未定义的。否则,当再次转换回来时,结果应与原始指针比较相等。[…]
我发现你的例子是一个void指针的完美地方:
int age(void *object) {
为什么?因为你明显的意图是给予不同的"对象"。类型赋给这样的函数,它根据编码的类型获取信息。在您的版本中,每次调用函数age((object_t*)person);
时都需要强制转换。当你给编译器错误的指针时,编译器不会报错,所以无论如何也没有涉及到类型安全。那么在调用函数时也可以使用void指针并避免强制类型转换。
age(&person->object)
调用该函数。
严格的混叠规则限制了访问对象(内存区域)的类型。代码中有几个地方可能会出现规则:在age()
内和调用age()
时。
在age
中,您还需要考虑object
。((person_t *)object)
是一个左值表达式,因为它有一个对象类型,它指定一个对象(一个内存区域)。然而,只有当object->type == PERSON
时才会到达分支,因此(推测)对象的有效类型是person_t*
,因此强制转换不会违反严格混叠。特别是,严格混叠允许:
- 与对象的有效类型兼容的类型,
当调用age()
时,您可能会传递object_t*
或object_t
的派生类型:一个以object_t
作为第一个成员的结构体。
- 在其成员中包含上述类型之一的聚合或联合类型
此外,严格混叠的重点是允许优化加载值到寄存器。如果一个对象通过一个指针发生了变化,则假定不兼容类型的指针所指向的任何对象都保持不变,因此不需要重新加载。代码没有修改任何东西,所以不应该受到优化的影响。
标准明确允许的一种可接受的方法是对具有相同初始段的结构体进行联合,如下所示:
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
而不受惩罚,然后访问所需的联合成员