请考虑以下代码:
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严格别名规则?请解释为何合法/非法。
答案 0 :(得分:16)
严格别名规则是指引用内存(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。指向适当转换的结构对象的指针指向其初始成员
上述两个考虑使我认为您的代码是合法的。
答案 1 :(得分:3)
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)
调用该函数。每次你打电话。
答案 2 :(得分:2)
严格别名规则限制您访问对象(内存区域)的类型。代码中可能会出现一些可能出现规则的地方:age()
内和调用age()
时。
在age
内,您需要考虑object
。 ((person_t *)object)
是一个左值表达式,因为它有一个对象类型,它指定一个对象(一个内存区域)。但是,仅在object->type == PERSON
时才会到达分支,因此(可能)对象的有效类型为person_t*
,因此强制转换不会违反严格别名。特别是,严格的别名允许:
- 与对象的有效类型兼容的类型
调用age()
时,您可能会传递object_t*
或来自object_t
的类型:具有object_t
作为第一个成员的结构。允许这样做:
- 聚合或联合类型,其成员中包含上述类型之一
此外,严格混叠的要点是允许将加载值优化到寄存器中。如果一个对象通过一个指针进行了变异,那么假定不兼容类型的指针所指向的任何东西都保持不变,因此不需要重新加载。代码不会修改任何内容,因此不应受优化的影响。
答案 3 :(得分:0)
标准明确宽恕的一种可接受的方法是使具有相同初始段的结构的 union ,如下所示:
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
而不受惩罚,然后访问所需的工会成员。