Пуля служит для двух целей.Прежде всего, если кто-то признает, что доступ к lvalue, который визуально основан или может быть основан на lvalue определенного типа, должен быть распознан как lvalue или возможное lvalue последнего типа, то ему присваивается что-то вроде:
union U {int x[10]; float y[10];} u;
lvalue, который явно получен из u
, будет разрешен доступ ко всем содержащимся в нем объектам.Диапазон ситуаций, в которых реализация распознает, что lvalue основано на другом, является проблемой качества реализации, поскольку некоторые качественные компиляторы, такие как icc, могут распознавать, учитывая что-то вроде:
int load_array_element(int *array, int i) { return array[i]); }
...
int test(int i) { return load_array_element(&u.x, i); }
что все, что определенный вызов load_array_element
может сделать с *array
, будет сделано с u
(ему в конце концов присваивается адрес lvalue, непосредственно сформированный из u
), и другиекомпиляторы, такие как clang и gcc, неспособны распознать даже такую конструкцию, как *(u.x+i)
, как lvalue, основанную на u
.
Вторая цель пули - предложить, что даже если компилятор слишком примитивен для сохраненияотслеживая вывод lvalue в прямолинейном коде, он должен признать, что данные декларации:
int *p,i;
struct foo { int x;} foo;
, если он видит *p=1; i=foo.x;
, не обращая никакого внимания на то, откуда пришел p
, он должен убедиться, чтозапись в *p
выполняется до чтения foo.x
.Даже если это действительно необходимо только в тех случаях, когда компилятор, который потрудился обратить внимание, смог бы увидеть, что p
был сформирован из foo
, описание вещей в этих терминах увеличило бы кажущуюся сложность компилятора по сравнениюс предоставлением доступа к foo.x
принудительное завершение любых ожидающих записей в целевые указатели целых чисел.
Обратите внимание, что если кто-то заинтересован только в тех случаях, когда доступ к элементу структуры или объединения осуществляется через только что полученный указатель, нет необходимости включать общее разрешение для доступа к структуре или объекту объединения через lvalue типа члена.Учитывая последовательность кодов: foo.x = 1; p = &foo.x; i=*p;
, принятие адреса foo.x
должно привести к тому, что компилятор завершит любые ожидающие записи в foo.x
, прежде чем запускать любой код, который может использовать адрес (компилятор, который не имеет представления о том, что ниже).код будет делать с адресом может просто завершить запись немедленно).Если бы кодовая последовательность была foo.x = 1; i = *p;
, то обращение к foo.x
через lvalue foo
означало бы, что любой существующий указатель, который мог бы идентифицировать это хранилище, был бы «устаревшим», и, таким образом, компилятор не был бы обязан распознатьчто такой указатель может идентифицировать то же хранилище, что и foo.x
.
. Обратите внимание, что, несмотря на сноску 88, в которой четко сказано, что цель "правила строгого алиасинга" состоит в том, чтобы указать, когда объектам разрешается псевдоним, интерпретацияgcc и clang интерпретируют правило как оправдание для игнорирования случаев, когда к объектам получают доступ lvalue, которые довольно явно получены из них.Возможно, в ретроспективе авторам стандарта следовало бы включить положение «Обратите внимание, что это правило не делает попыток запретить некачественным компиляторам вести себя тупо, но не имеет намерения поощрять такое поведение», но авторы C89 не имели никаких основанийожидать, что правило будет интерпретировано так, как оно есть, и авторы clang и gcc почти наверняка наложат вето на любое предложение добавить такой язык сейчас.