我听说C标准在多大程度上保证了结构布局的一致性。有限范围内的参数提到了严格的别名规则。例如,比较以下两个答案:https://stackoverflow.com/a/3766251/1306666和https://stackoverflow.com/a/3766967/1306666。
在下面的代码中,我假设foo
,bar
和struct { char *id; }
char *id
位于同一位置的所有结构#include <string.h>
struct foo {
char *id;
int a;
};
struct bar {
char *id;
int x, y, z;
};
struct list {
struct list *next;
union {
struct foo *foop;
struct bar *barp;
void *either;
} ptr;
};
struct list *find_id(struct list *l, char *key)
{
while (l != NULL) {
/* cast to anonymous struct and dereferenced */
if (!strcmp(((struct { char *id; } *)(l->ptr.either))->id, key))
return l;
l = l->next;
}
return NULL;
}
,如果它们之间可以安全施放,它是唯一访问过的成员。
无论演员表是否会导致错误,是否违反了严格的别名规则?
gcc -o /dev/null -Wstrict-aliasing test.c
gcc
注意{{1}}没有错误。
答案 0 :(得分:1)
是的,程序中存在多个与别名相关的问题。使用具有匿名结构类型的左值与底层对象的类型不匹配会导致未定义的行为。它可以通过以下方式修复:
*(char**)((char *)either + offsetof(struct { ... char *id; ... }, id))
如果您知道id
成员在所有成员中处于相同的偏移量(例如,它们都共享相同的前缀)。但在你的特定情况下,你可以做的第一个成员:
*(char**)either
因为将指向结构的指针转换为指向其第一个成员(并返回)的指针总是有效的。
另一个问题是你对联盟的使用是错误的。最大的问题是它假设struct foo *
,struct bar *
和void *
都具有相同的大小和表示形式,这是无法保证的。此外,除了先前存储的工会成员之外,访问工会成员可能是未定义的,但是由于缺陷报告中的解释,可以说它相当于“重新解释演员”。但这会让你回到错误地假设相同大小/代表性的问题。
您应该删除联合,使用void *
成员,并将值(而不是重新解释位)转换为正确的指针类型以访问指向的结构( struct foo *
或struct bar *
)或其初始ID字段(char *
)。