将匿名结构体指针转型后解除引用是否违反严格别名规则?

4
我听到了有关C标准保证结构布局一致性程度的不同说法。认为其程度有限的论点提到了严格的别名规则。例如,比较这两个答案:https://dev59.com/xG865IYBdhLWcg3wivGi#3766251https://dev59.com/xG865IYBdhLWcg3wivGi#3766967
在下面的代码中,我假设所有结构体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 没有报错。


“cast to an anonymous structure pointer” 中的“cast”在哪里?我看到的是初始化和转换(从 void *struct list *)。 - chux - Reinstate Monica
find_id() 中,@chux ((struct { char *id; } *)(l->ptr.either))->id - nebuch
@chux 很难看到,仔细看。 - user3386109
@chux,它的形式为(type) object,根据C89标准的§3.3.4,这是一个强制类型转换;请参见http://port70.net/~nsz/c/c89/c89-draft.html#3.3.4。 - nebuch
@user3386109:没有必要移动 id。将其保留在 foobar 开头完全可以正常工作。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论
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 *`)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接