Строгий псевдоним и ссылки на C-массивы во время компиляции - PullRequest
7 голосов
/ 16 октября 2019

Учитывая следующий код

#include <cassert>
#include <climits>
#include <cstdint>
#include <iostream>

static_assert(CHAR_BIT == 8, "A byte does not consist of 8 bits");

void func1(const int32_t& i)
{
    const unsigned char* j = reinterpret_cast<const unsigned char*>(&i);
    for(int k = 0; k < 4; ++k)
        std::cout << static_cast<int>(j[k]) << ' ';
    std::cout << '\n';
}

void func2(const int32_t& i)
{
    const unsigned char (&j)[4] = reinterpret_cast<const unsigned char (&)[4]>(i);
    for(int k = 0; k < 4; ++k)
        std::cout << static_cast<int>(j[k]) << ' ';
    std::cout << '\n';
}

int main() {
    func1(-1);
    func2(-1);
}

Из правил языка ясно, что func1 хорошо, так как указатели на unsigned char могут иметь псевдоним любого другого типа. Мой вопрос: распространяется ли это на C ++ ссылки на C-массивы с известной длиной? Интуитивно я бы сказал, да. func2 четко определено или это вызывает неопределенное поведение?

Я пытался скомпилировать приведенный выше код с использованием Clang и GCC с каждой возможной комбинацией -Wextra -Wall -Wpedantic и UBSAN, и не получал предупреждений и всегдатот же вывод. Очевидно, это не означает, что UB отсутствует, но я не смог вызвать ни одной из обычных ошибок оптимизации типа строго псевдонимов.

1 Ответ

5 голосов
/ 16 октября 2019

Это неопределенное поведение.

По смыслу reinterpret_cast здесь мы имеем [expr.reinterpret.cast]

11 Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано в тип «указатель на T2» с помощью reinterpret_cast. Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом. [Примечание: то есть для l-значений эталонное приведение reinterpret_cast (x) имеет тот же эффект, что и преобразование * reinterpret_cast (& x) со встроенными операторами & и * (и аналогично для reinterpret_cast (x)). - примечание конца] Временное создание не производится, копирование не производится, а конструкторы или функции преобразования не вызываются.

Это говорит о том, что приведение типа int func2 действительно до тех пор, пока reinterpret_cast<const unsigned char (*)[4]>(&i)действует. Здесь нет шока. Но суть в том, что вы не можете получить ничего значимого из этого преобразования указателя. По этому вопросу у нас это в [basic.compound] :

4 Два объекта a и b являются взаимозаменяемыми по указателю, если:

  • это один и тот же объект, или
  • один - это объект объединения стандартной компоновки, а другой - нестатический член данных этого объекта ([class.union]), или
  • один - это объект класса стандартной компоновки, а другой - первый не статический член данных этого объекта, или, если у объекта нет нестатических членов данных, первый подобъект базового класса этого объекта ([class.mem]), или
  • существует объект c, такой, что a и c являются взаимозаменяемыми по указателю, а c и b являются взаимозаменяемыми по указателю.

Если два объектаявляются взаимозаменяемыми указателями, тогда они имеют один и тот же адрес, и можно получить указатель на один из указателя на другой через reinterpret_­cast. [Примечание: объект массива и его первый элемент не являются взаимозаменяемыми по указателю, даже если они имеют один и тот же адрес. - конец примечания]

Это исчерпывающий список значимых преобразований указателей. Таким образом, нам не разрешено получать адрес массива подобным образом, и поэтому он не является допустимым массивом glvalue. Поэтому дальнейшее использование вами результата каста не определено.

...