Нарушает ли разыменование приведение к анонимному указателю структуры строго псевдонимы?
Я слышал противоречивые мнения о том, в какой степени стандарты 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 *
).