简体   繁体   English

c中的嵌套结构和严格别名

[英]Nested structs and strict aliasing in c

Please consider the following code: 请考虑以下代码:

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

Is this legal code or is it violating the C99 strict aliasing rule? 这是法律法规还是违反C99严格的别名规则? Please explain why it is legal/illegal. 请解释为什么它是合法/非法的。

Strict aliasing rule is about two pointers of different types referencing the same location in memory (ISO/IEC9899/TC2) . 严格的别名规则是关于两个不同类型的指针,它们引用内存中的同一位置(ISO / IEC9899 / TC2) Although your example reinterprets the address of object_t object as an address of person_t , it does not reference memory location inside object_t through the reinterpreted pointer, because age is located past the boundary of object_t . 尽管您的示例将object_t object的地址重新解释为person_t的地址,但是由于age位于object_t的边界person_t ,因此它没有通过重新解释的指针来引用object_t内部的内存位置。 Since memory locations referenced through pointers are not the same, I'd say that it is not in violation of the strict aliasing rule. 由于通过指针引用的内存位置不相同,因此我想说它并不违反严格的别名规则。 FWIW, gcc -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99 seems to agree with that assessment, and does not produce a warning. FWIW, gcc -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99似乎与该评估一致,并且不会产生警告。

This is not enough to decide that it's legal code, though: your example makes an assumption that the address of a nested structure is the same as that of its outer structure. 但是,这还不足以决定它是否是合法代码:您的示例假设嵌套结构的地址与其外部结构的地址相同。 Incidentally, this is a safe assumption to make according to the C99 standard: 顺便提及,根据C99标准,这是一个安全的假设:

6.7.2.1-13. 6.7.2.1-13。 A pointer to a structure object, suitably converted, points to its initial member 适当转换的指向结构对象的指针指向其初始成员

The two considerations above make me think that your code is legal. 以上两个考虑因素使我认为您的代码是合法的。

http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

As an add-on to the accepted answer, here is the full citation from the standard, with the important part highlighted that the other answer omitted, and one more: 作为已接受答案的补充,以下是标准的完整引用,其中重要的部分突出显示了其他答案,还有一个:

6.7.2.1-13: Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. 6.7.2.1-13:在结构对象中,非位字段成员和位字段所在的单元的地址按照声明的顺序增加。 A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa . 指向经过适当转换的结构对象的指针指向其初始成员(或者,如果该成员是位字段,则指向它所驻留的单元), 反之亦然 There may be unnamed padding within a structure object, but not at its beginning. 结构对象内可能存在未命名的填充,但在其开始处没有。

6.3.2.3-7: A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. 6.3.2.3-7:指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。 If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. 如果结果指针未针对指向的类型正确对齐,则该行为未定义。 Otherwise, when converted back again, the result shall compare equal to the original pointer. 否则,当再次转换回时,结果应等于原始指针。 [...] [...]

I find your example to be a perfect place for a void pointer: 我发现您的示例是空指针的理想之地:

int age(void *object) {

Why? 为什么? Because your obvious intention is to give different "object" types to such a function, and it gets the information according to the encoded type. 因为您明显的意图是给这种函数赋予不同的“对象”类型,然后它根据编码的类型获取信息。 In your version, you need a cast each time you call the function: age((object_t*)person); 在您的版本中,每次调用函数时都需要age((object_t*)person);age((object_t*)person); . The compiler will not complain when you give the wrong pointer to it, so there is no type safety involved, anyway. 当您提供错误的指针时,编译器不会抱怨,因此无论如何都不会涉及类型安全。 Then you can as well use a void pointer and avoid the cast when calling the function. 然后,您也可以使用void指针,并避免在调用函数时进行强制转换。

Alternatively you could call the function with age(&person->object) , of course. 或者,您当然可以使用age(&person->object)调用该函数。 Each time you call it. 每次您调用它。

The strict aliasing rule limits by what types you access an object (a region of memory). 严格的别名规则限制您访问对象(内存区域)的类型。 There are a few places in the code where the rule might crop up: within age() and when calling age() . 在代码中有一些地方可能会出现规则:在age()以及调用age()

Within age , you have object to consider. age范围内,您有要考虑的object ((person_t *)object) is an lvalue expression because it has an object type and it designates an object (a region of memory). ((person_t *)object)是一个左值表达式,因为它具有对象类型并指定一个对象(内存区域)。 However, the branch is only reached if object->type == PERSON , so (presumably) the effective type of the object is a person_t* , hence the cast doesn't violate strict aliasing. 但是,只有在object->type == PERSON才能到达分支,因此(大概)对象的有效类型是person_t* ,因此person_t*不会违反严格的别名。 In particular, strict aliasing allows: 特别地,严格的别名允许:

  • a type compatible with the effective type of the object, 与对象的有效类型兼容的类型,

When calling age() , you will presumably be passing an object_t* or a type that descends from object_t : a struct that has an object_t as the first member. 调用age() ,您大概会传递一个object_t*或一个源自object_t的类型:一个以object_t作为第一个成员的结构。 This is allowed as: 这被允许为:

  • an aggregate or union type that includes one of the aforementioned types among its members 成员中包括上述类型之一的聚合或联合类型

Furthermore, the point of strict aliasing is to allow for optimizing away loading values into registers. 此外,严格混叠的目的是允许优化将值加载到寄存器中。 If an object is mutated via one pointer, anything pointed to by pointers of an incompatible type are assumed to remain unchanged, and thus don't need to be reloaded. 如果一个对象是通过一个指针进行更改的,则假定不兼容类型的指针所指向的所有内容都保持不变,因此不需要重新加载。 The code doesn't modify anything, so shouldn't be affected by the optimization. 该代码不会修改任何内容,因此不应受到优化的影响。

One acceptable way that is explicitly condoned by the standard is to make a union of structs with identical initial segment, like so: 该标准明确允许的一种可接受的方法是使具有相同初始段的结构的并 ,如下所示:

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;

Now you can pass an object_t * p and examine p->tag.value with impunity, and then access the desired union member. 现在,您可以传递object_t * p并检查p->tag.value不受惩罚,然后访问所需的并集成员。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM