Рассмотрим следующую структуру:
struct s {
int a, b;
};
Обычно 1 , эта структура будет иметь размер 8 и выравнивание 4.
Что если мы создадим два struct s
объекта (точнее, мы запишем в выделенное хранилище два таких объекта), причем второй объект будет перекрывать первый?
char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4
// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};
printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);
Есть ли в этой программе неопределенное поведение? Если так, то где это становится неопределенным? Если это не UB, всегда ли печатается следующее:
o2.a=3
o2.b=4
o1.a=1
o1.b=3
В частности, я хочу знать, что происходит с объектом, на который указывает o1
когда o2
, который перекрывает его, записывается. Разрешено ли получить доступ к неблокированной части ( o1->a
)? Доступ к засоренной части - o1->b
это то же самое, что и доступ o2->a
?
Как эффективный тип применяется здесь? Правила достаточно ясны, когда вы говорите о неперекрывающихся объектах и указателях, которые указывают на то же местоположение, что и в последнем хранилище, но когда вы начинаете говорить об эффективном типе частей объектов или перекрывающихся объектов, это менее ясно.
Изменится ли что-нибудь, если вторая запись будет другого типа? Если члены говорят , int
и short
вместо двух int
лет?
Вот крестик, если вы хотите поиграть с ним там.
1 Этот ответ применим к платформам, где это не так: например, некоторые могут иметь размер 4 и выравнивание 2. На платформе, где размер и выравнивание были одинаковыми, этот вопрос не будет применяться, поскольку выровненные перекрывающиеся объекты будут быть невозможным, но я не уверен, есть ли какая-либо платформа, подобная этой.
Ответы:
В основном это все серые области в стандарте; строгое правило псевдонимов определяет основные случаи и оставляет читателю (и поставщикам компилятора) подробные сведения.
Были попытки написать лучшее правило, но пока они не привели ни к какому нормативному тексту, и я не уверен, каково это состояние для C2x.
Как упоминалось в моем ответе на ваш предыдущий вопрос, наиболее распространенная интерпретация заключается в том, что
p->q
средство(*p).q
и эффективный тип применимы ко всем*p
, даже если мы затем продолжим их применять.q
.Согласно этой интерпретации, это
printf("o1.a=%d\n", o1->a);
может привести к неопределенному поведению, поскольку эффективный тип местоположения*o1
отсутствуетs
(поскольку его часть была перезаписана).Обоснование этой интерпретации можно увидеть в такой функции:
С этой интерпретацией можно оптимизировать последнюю строку
puts("5");
, но без нее компилятору пришлось бы учитывать, что вызов функции мог бытьf(o1, o2);
и, следовательно, потерять все преимущества, которые якобы предусмотрены строгим правилом псевдонимов.Аналогичный аргумент применяется к двум несвязанным типам структур, у которых оба
int
элемента имеют разное смещение.источник
f(s* s1, s* s2)
, безrestrict
, компилятор не может предполагатьs1
иs2
являются разными указателями. Я думаю , опять же, без этогоrestrict
, он даже не может предположить, что они не частично перекрываются. IAC, я не вижу, что поf()
аналогии озабоченность OP хорошо продемонстрирована . Удачи неослабный. УФ для первой половины.s1 == s2
будет разрешено, но не частичное перекрытие. (Оптимизация в моем примере кода все еще может быть выполнена, еслиs1 == s2
)int
вместо структур (и системы с_Alignof(int) < sizeof(int)
).p->q
и(*p).q
. Это может быть верно для интерпретации типа, как вы заявляете, но это не так с эксплуатационной точки зрения. Для одновременного доступа к той же структуре важно, чтобы доступ члена не подразумевал доступ любого другого члена.E1.E2
выражении не выполняет доступ (я имею в виду целоеE1
выражение. Некоторые из его подвыражений могут выполнять доступ. Т.е. еслиE1
есть(*p)
, то чтение значения указателя при вычисленииp
является доступом, но оценка*p
или(*p)
не выполняет какой-либо доступ). Строгое правило псевдонимов не применяется в случае отсутствия доступа.