Каждая версия Стандарта рассматривала поддержку многих конструкций псевдонимов как проблему качества реализации, поскольку было бы практически невозможно написать правила, которые поддерживали бы все полезные конструкции, не блокировали бы какие-либо полезные оптимизации и могли бы поддерживаться всеми компиляторами. без существенной доработки. Рассмотрим следующую функцию:
struct foo {int length; int *dat; };
int test1(struct foo *p)
{
int *ip = &p->length;
*ip = 2;
return p->length;
}
Я думаю, довольно ясно, что любой качественный компилятор должен обрабатывать возможность того, что объект типа struct foo
может быть затронут присваиванием *ip
. С другой стороны, рассмотрим функцию:
void test2(struct foo *p)
{
int i;
for (i=0; i < p->length; i++)
p->dat[i] = 0;
}
Если от компилятора требуется учитывать возможность того, что запись в p->dat[i]
может повлиять на значение p->length
, например. путем перезагрузки значения p->length
хотя бы после первой итерации цикла?
Я думаю, что некоторые члены Комитета, возможно, намеревались потребовать, чтобы компиляторы допускали такое допущение, но я не думаю, что все они это сделали, и написанные правила не требуют этого, поскольку они перечисляют типы lvalue, которые могут использоваться для получить доступ к объекту типа struct foo
, а int
среди них нет. Некоторые люди могут подумать, что упущение было случайным, но я думаю, что оно было основано на ожидании, что компиляторы интерпретируют правило как требующее, чтобы к объектам, к которым обращаются как к определенному типу в некотором контексте, обращались lvalues, которые имеют видимую ассоциацию с объектом одного из перечисленных типов в этом контексте. Вопрос о том, что составляет видимую ассоциацию, остался за пределами юрисдикции Стандарта как вопрос QoI, но ожидалось, что авторы компиляторов приложат разумные усилия для распознавания ассоциаций, когда это целесообразно.
В такой функции, как test1
, lvalue типа p
используется для получения ip
, а p
никаким другим образом не используется для доступа к p->length
между формированием ip
и его последним использованием. Таким образом, компиляторы должны без труда распознать, что сохранение в *ip
не может быть переупорядочено при последующем чтении в p->length
, даже без общего правила, дающего полное разрешение на использование указателей типа int*
для доступа к int
элементам несвязанных структур. Однако внутри test2
нет никаких видимых средств, с помощью которых адрес p->length
мог бы использоваться при вычислении указателя p->dat
, и, таким образом, для оптимизирующих компиляторов, предназначенных для наиболее распространенных целей, было бы разумно поднять чтение p->length
перед циклом. в ожидании, что его значение не изменится.
Вместо того, чтобы пытаться распознать типы объектов, из которых получен указатель, clang и gcc вместо этого предпочитают вести себя так, как будто Стандарт дает общее разрешение на доступ к членам структуры (но не объединения!) с использованием указателей их типов. Это допустимо, но не требуется стандартом (соответствующая, но некачественная реализация может обрабатывать test1
произвольным бессмысленным образом), но слепота к получению указателя без необходимости ограничивает диапазон конструкций, доступных программистам, и заставляет отказаться от того, что должно быть полезные оптимизации, такие как примеры test2()
.
В целом, правильный ответ почти на любой вопрос, связанный с алиасингом в C, заключается в том, что это вопрос качества реализации. Наблюдения за тем, что делают clang и gcc, могут быть полезны для людей, которым нужно умиротворить режим -fstrict-aliasing
этих компиляторов, но которые имеют мало общего с тем, что на самом деле говорит Стандарт.
person
supercat
schedule
20.02.2021
someAggregate.array[i]
с пользой, даже если Стандарт не требует этого. - person supercat   schedule 20.02.2021