Строгое правило алиасинга в C - PullRequest
0 голосов
/ 17 февраля 2019

Я пытаюсь понять правило строгого псевдонима, как определено в 6.5(p6):

Если значение сохраняется в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который не являетсясимвольный тип, тогда тип lvalue становится эффективным типом объекта для этого доступа и для последующих доступов, которые не изменяют сохраненное значение.

и 6.5(p7):

Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов: 88)

- тип, совместимый с эффективным типом объекта

Рассмотрим следующий пример:

struct test_internal_struct_t{
    int a;
    int b;
};

struct test_struct_t{
    struct test_internal_struct_t tis;
};

int main(){
    //alocated object, no declared type
    struct test_struct_t *test_struct_ptr = malloc(sizeof(*test_struct_ptr)); 

    //object designated by the lvalue has type int
    test_struct_ptr->tis.a = 1; 

    //object designated by the lvalue has type int
    test_struct_ptr->tis.b = 2; 

    //VIOLATION OF STRICT ALIASING RULE???
    struct test_internal_struct_t tis = test_struct_ptr->tis; 
    return 0;
}

У malloc(sizeof(*test_struct_ptr)) нет объявленного типа, поскольку он выделен как сноска 87:

87) Для выделенных объектов не объявленотип

Объекты, получающие доступ через test_struct_ptr->tis.a и test_struct_ptr->tis.b, имеют эффективный тип , равный int.Но объект test_struct_ptr->tis не имеет эффективного типа, поскольку он выделен.

ВОПРОС: Является ли struct test_internal_struct_t tis = test_struct_ptr->tis; нарушением строгого алиасинга?Объект, обозначенный test_struct_ptr->tis, не имеет действующего типа, но lvalue имеет тип struct test_internal_struct_t.

1 Ответ

0 голосов
/ 17 февраля 2019

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, используемым для доступа.

...