17

请考虑以下代码:

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 严格的别名规则?请解释为什么它是合法/非法的。

4

4 回答 4

17

严格的别名规则是关于引用内存中相同位置的两个不同类型的指针(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。指向结构对象的指针,经过适当转换,指向其初始成员

上面的两个考虑让我认为你的代码是合法的。

于 2011-12-07T14:42:47.437 回答
3

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)。每次调用它。

于 2011-12-07T16:59:06.257 回答
2

严格的别名规则限制了您访问对象(内存区域)的类型。代码中有几个地方可能会出现规则:在调用内age()和调用时age()

age,你必须object考虑。((person_t *)object)是一个左值表达式,因为它有一个对象类型并且它指定一个对象(一个内存区域)。但是,仅在 if 时才到达分支object->type == PERSON,因此(可能)对象的有效类型是 a person_t*,因此强制转换不会违反严格的别名。特别是,严格的别名允许:

  • 与对象的有效类型兼容的类型,

调用 时age(),您可能会传递一个object_t*或 继承自 的类型object_t:一个以 anobject_t作为第一个成员的结构。这是允许的:

  • 在其成员中包含上述类型之一的聚合或联合类型

此外,严格别名的目的是允许优化将值加载到寄存器中。如果一个对象通过一个指针发生变异,则假定由不兼容类型的指针指向的任何内容都保持不变,因此不需要重新加载。该代码不会修改任何内容,因此不应受到优化的影响。

于 2011-12-07T14:54:26.680 回答
0

标准明确允许的一种可接受的方式是创建具有相同初始段的结构的联合,如下所示:

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,然后访问所需的工会成员。

于 2011-12-07T14:43:30.993 回答