关于c标准在多大程度上保证了结构布局的一致性,我听到过一些相互矛盾的说法。有限范围的参数提到了严格的别名规则。例如,比较这两个答案:https://stackoverflow.com/a/3766251/1306666https://stackoverflow.com/a/3766967/1306666
在下面的代码中,我假设在所有结构foobarstruct { 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没有错误。

最佳答案

是的,程序中存在多个与别名相关的问题。使用匿名结构类型的左值(与基础对象的类型不匹配)会导致未定义的行为。可以用以下方法来修复:

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

如果您知道id成员在所有成员中处于相同的偏移量(例如,它们共享相同的前缀)。但在您的具体情况下,如果它是第一个成员,您可以只做:
*(char**)either

因为将指向结构的指针转换为指向其第一个成员(和后面)的指针总是有效的。
另一个问题是你使用工会是错误的。最大的问题是它假设struct foo *struct bar *void *都具有相同的大小和表示,这是不保证的。此外,可以说,除了先前存储的成员之外,访问联盟成员是未定义的,但是由于缺陷报告中的解释,可以肯定地说,它相当于“重新解释的类型”。但这让你回到错误地假设相同大小/表示的问题上。
您应该移除联合,使用void *成员,并将值(而不是重新解释位)转换为右指针类型,以访问指向结构(struct foo *struct bar *)或其初始id字段(char *)。

10-01 05:35
查看更多