По существу, если у меня есть
typedef struct {
int x;
int y;
} A;
typedef struct {
int h;
int k;
} B;
и у меня есть A a
, гарантирует ли стандарт C, что ((B*)&a)->k
совпадает с a.y
?
По существу, если у меня есть
typedef struct {
int x;
int y;
} A;
typedef struct {
int h;
int k;
} B;
и у меня есть A a
, гарантирует ли стандарт C, что ((B*)&a)->k
совпадает с a.y
?
Являются ли C-структуры с одинаковыми типами членов, которые имеют одинаковый макет в памяти?
Почти так. Достаточно близко для меня.
Из n1516, раздел 6.5.2.3, пункт 6:
... если объединение содержит несколько структур, которые имеют общую начальную последовательность..., и если объект объединения в настоящее время содержит одну из этих структур, разрешается проверять общую начальную часть любого из них в любом месте, где видна декларация завершенного типа объединения. Две структуры имеют общую начальную последовательность, если соответствующие члены имеют совместимые типы (и для бит-полей, одинаковые ширины) для последовательности из одного или нескольких начальных элементов.
Это означает, что если у вас есть следующий код:
struct a {
int x;
int y;
};
struct b {
int h;
int k;
};
union {
struct a a;
struct b b;
} u;
Если вы назначаете u.a
, стандарт говорит, что вы можете прочитать соответствующие значения из u.b
. Он растягивает границы правдоподобия, чтобы предположить, что struct a
и struct b
могут иметь разный макет, учитывая это требование. Такая система была бы крайне патологической.
Помните, что стандарт также гарантирует, что:
Структуры никогда не являются ловушками.
Адреса полей в увеличении структуры (a.x
всегда перед a.y
).
Смещение первого поля всегда равно нулю.
Вы перефразировали вопрос,
означает ли стандарт C, что
((B*)&a)->k
совпадает с a.y?
Нет! И он очень четко заявляет, что они не то же самое!
struct a { int x; };
struct b { int x; };
int test(int value)
{
struct a a;
a.x = value;
return ((struct b *) &a)->x;
}
Это нарушение псевдонимов.
Ответы на другие ответы с предупреждением о разделе 6.5.2.3. По-видимому, есть некоторые споры о точной формулировке anywhere that a declaration of the completed type of the union is visible
, и по крайней мере GCC не реализует ее как написанную. Есть несколько тангенциальных отчетов о дефектах С Р здесь и здесь с последующими комментариями от комитета.
Недавно я попытался выяснить, как другие компиляторы (в частности, GCC 4.8.2, ICC 14 и clang 3.4) интерпретируют это, используя следующий код из стандарта:
// Undefined, result could (realistically) be either -1 or 1
struct t1 { int m; } s1;
struct t2 { int m; } s2;
int f(struct t1 *p1, struct t2 *p2) {
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g() {
union {
struct t1 s1;
struct t2 s2;
} u;
u.s1.m = -1;
return f(&u.s1,&u.s2);
}
GCC: -1, clang: -1, ICC: 1 и предупреждает о нарушении псевдонимов
// Global union declaration, result should be 1 according to a literal reading of 6.5.2.3/6
struct t1 { int m; } s1;
struct t2 { int m; } s2;
union u {
struct t1 s1;
struct t2 s2;
};
int f(struct t1 *p1, struct t2 *p2) {
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g() {
union u u;
u.s1.m = -1;
return f(&u.s1,&u.s2);
}
GCC: -1, clang: -1, ICC: 1, но предупреждает о нарушении псевдонимов
// Global union definition, result should be 1 as well.
struct t1 { int m; } s1;
struct t2 { int m; } s2;
union u {
struct t1 s1;
struct t2 s2;
} u;
int f(struct t1 *p1, struct t2 *p2) {
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g() {
u.s1.m = -1;
return f(&u.s1,&u.s2);
}
GCC: -1, clang: -1, ICC: 1, без предупреждения
Конечно, без строгой оптимизации сглаживания все три компилятора возвращают ожидаемый результат каждый раз. Поскольку clang и gcc не имеют выдающихся результатов в любом из случаев, единственная реальная информация исходит из отсутствия ICC диагностики на последнем. Это также согласуется с примером, данным комитетом по стандартам в первом отчете о дефектах, упомянутом выше.
Другими словами, этот аспект C является настоящим минным полем, и вам нужно быть осторожным, чтобы ваш компилятор делал правильные вещи, даже если следовать стандарту письма. Все хуже, поскольку он интуитивно понятен, что такая пара структур должна быть совместима в памяти.
Для такого типа сглаживания требуется тип union
. C11 §6.5.2.3/6:
Для упрощения использования союзов существует одна специальная гарантия: если объединение содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объект объединения в настоящее время содержит одну из этих структур, it разрешено проверять общую начальную часть любого из них в любом месте, где видна декларация завершенного типа объединения. Две структуры имеют общую начальную последовательность, если соответствующие члены имеют совместимые типы (и для бит-полей, одинаковые ширины) для последовательности одного или нескольких начальных элементов.
В этом примере:
Ниже приведен неверный фрагмент (поскольку тип объединения не является видимый внутри функции f):
struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union { struct t1 s1; struct t2 s2; } u; /* ... */ return f(&u.s1, &u.s2);} }
Требования, по-видимому, состоят в том, что 1. алиас объекта хранится внутри union
и 2. что определение этого типа union
находится в области видимости.
Для того, что стоит, соответствующее соотношение начальных и подпоследовательностей в С++ не требует union
. И вообще, такая зависимость union
была бы чрезвычайно патологическим поведением для компилятора. Если каким-то образом существование типа объединения может повлиять на модель памяти concerete, возможно, лучше не пытаться ее отобразить.
Я предполагаю, что намерение заключается в том, что верификатор доступа к памяти (думаю, Valgrind на стероидах) может проверить потенциальную ошибку псевдонимов на эти "строгие" правила.