C 2018 6.5 6 определяет эффективный тип , используя фразу «сохранено… через lvalue», но никогда не определяет эту фразу:
Если значение сохраняется в объекте, имеющемнет объявленного типа через lvalue, имеющий тип, который не является символьным типом, тогда тип lvalue становится эффективным типом объекта для этого доступа и для последующих обращений, которые не изменяют сохраненное значение.
Так что читателям оставалось их интерпретировать.Рассмотрим этот код:
struct a { int x; };
struct b { int x; };
void copy(int N, struct a *A, struct b *B)
{
for (int n = 0; n < N; ++n)
A[n].x = B[n].x;
}
Если компилятор знает, что различные объекты A[n]
не перекрывают различные объекты B[n]
, то он может оптимизировать этот код, выполнив загрузку нескольких B[n]
водну инструкцию (например, AVX или другую инструкцию с несколькими данными [SIMD] для одной команды) и сохранение нескольких A[n]
в одной команде.(Для этого может потребоваться дополнительный код для обработки проблем фрагмента цикла и выравнивания. Это нас здесь не касается.) Если возможно, что некоторые A[n]->x
могут ссылаться на тот же объект, что и B[n]->x
, для другого значения n
то компилятор может не использовать такие многоэлементные загрузки и сохранения, поскольку это может изменить наблюдаемое поведение программы.Например, если бы ситуация состояла в том, что память содержала десять int
со значениями от 0 до 9 и B
, указывающими на 0, в то время как A
указывал на 2:
B A
0 1 2 3 4 5 6 7 8 9
Тогда цикл какзаписано, учитывая N
= 4, необходимо копировать элементы по одному, производя:
0 1 0 1 0 1 6 7 8 9
Если компилятор оптимизировал это для четырехэлементных загрузок и сохранений, он мог бы загрузить 0 1 2 3 изатем сохраните 0 1 2 3, получив:
0 1 0 1 2 3 6 7 8 9
Однако C говорит нам, что struct a
и struct b
несовместимы, даже если они выложены одинаково.Когда типы X
и Y
несовместимы, это говорит нам о том, что X
не является Y
.Важной целью системы типов является различение типов объектов.
Теперь рассмотрим выражение A[n]->x = B[n]->x
.В этом:
A[n]
является lvalue для struct a
. - Поскольку
A[n]
является левым операндом .
, он не преобразуется взначение. A[n].x
обозначает и является lvalue для элемента x
из A[n]
. - Значение правого операнда заменяет значение в
A[n].x
.
Итак, прямой доступ к объекту, который хранит значение, находится исключительно в int
, который является членом A[n].x
.Значение l10ue A[n]
появляется в выражении, но оно не является значением lvalue, используемым непосредственно для хранения значения.Каков эффективный тип памяти в &A[n]
?
Если мы интерпретируем эту память как просто int
, то единственное ограничение на доступ к объектам состоит в том, что все B[n].x
являются int
и все A[n].x
равны int
, поэтому некоторые или все A[n].x
могут обращаться к той же памяти, что и некоторые или все из B[n].x
, и компилятору не разрешено выполнять оптимизацию, описанную выше.
Это не служит целям системы типов для различения struct a
и struct b
, и поэтому не может быть правильной интерпретацией.Чтобы включить запланированную оптимизацию, необходимо, чтобы память, хранящаяся в A[n].x
, содержала struct a
объектов, а память, доступная для B[n].x
, содержала struct b
объектов.
Следовательно, «сохраняется ... черезlvalue »должен включать выражения, в которых lvalue используется для получения членов структур, но сам не является окончательным lvalue, используемым для доступа.