Нарушает ли разыменование приведение к анонимному указателю структуры строго псевдонимы?

Я слышал противоречивые мнения о том, в какой степени стандарты C гарантируют согласованность структуры структуры. Аргументы в ограниченной степени упоминают строгие правила наложения имен. Например, сравните эти два ответа: /questions/31635095/privedenie-odnogo-ukazatelya-strukturyi-k-drugomu-c/31635109#31635109 и /questions/31635095/privedenie-odnogo-ukazatelya-strukturyi-k-drugomu-c/31635103#31635103.

В следующем коде я предполагаю во всех структурах 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 ответ

Решение

Да, в вашей программе есть несколько связанных с алиасами проблем. Использование lvalue с анонимным типом структуры, который не соответствует типу базового объекта, приводит к неопределенному поведению. Это может быть исправлено с помощью чего-то вроде:

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

если вы знаете id элемент во всех них имеет одинаковое смещение (например, все они имеют одинаковый префикс). Но в вашем конкретном случае, когда это первый член, вы можете просто сделать:

*(char**)either

потому что всегда можно преобразовать указатель на структуру в указатель на его первый член (и обратно).

Отдельная проблема заключается в том, что вы используете профсоюз неправильно. Самая большая проблема заключается в том, что это предполагает struct foo *, struct bar *, а также void * все имеют одинаковый размер и представление, что не гарантируется. Кроме того, возможно, не определен доступ к члену объединения, отличному от того, который был ранее сохранен, но в результате интерпретаций в отчетах о дефектах, вероятно, можно с уверенностью сказать, что это эквивалентно "переинтерпретации приведения". Но это возвращает вас к проблеме ошибочного принятия того же размера / представления.

Вы должны просто удалить союз, используйте void * член, и преобразовать значение (а не переинтерпретировать биты) в правильный тип указателя для доступа к структуре, на которую указывает указатель (struct foo * или же struct bar *) или его начальное поле идентификатора (char *).

Другие вопросы по тегам