4

我听说过关于 C 标准在多大程度上保证结构布局一致性的相互矛盾的事情。有限范围的争论提到了严格的别名规则。例如,比较这两个答案:https ://stackoverflow.com/a/3766251/1306666和https://stackoverflow.com/a/3766967/1306666

在下面的代码中,我假设所有结构foo,barstruct { 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没有错误。

4

1 回答 1

2

是的,您的程序中存在多个与别名相关的问题。使用与底层对象的类型不匹配的匿名结构类型的左值会导致未定义的行为。它可以通过以下方式修复:

*(char**)((char *)either + offsetof(struct { ... char *id; ... }, id))

如果您知道id成员在所有这些中的偏移量相同(例如,它们都共享相同的前缀)。但是在您的特定情况下,它是您可以执行的第一个成员:

*(char**)either

因为将指向结构的指针转换为指向其第一个成员(并返回)的指针总是有效的。

另一个问题是您对联合的使用是错误的。最大的问题是它假设struct foo *struct bar *void *都具有相同的大小和表示,这是无法保证的。此外,可以说访问除先前存储的成员之外的联合成员是未定义的,但由于缺陷报告中的解释,可以肯定地说它相当于“重新解释演员表”。但这会让你回到错误地假设相同大小/表示的问题。

您应该只删除联合,使用void *成员,并将(而不是重新解释位)转换为正确的指针类型,以访问指向的结构(struct foo *struct bar *)或其初始 id 字段(char *)。

于 2018-03-25T03:28:21.740 回答