Разрешают ли строгие правила псевдонимов в C++11 доступ к uint64_t через char *, char(&)[N], даже std:: array<char, N>& с -fstrict-aliasing -Wstrict-aliasing=2?
Согласно этому стеку потока ответ о строгих правилах псевдонимов C++ 11/14:
Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено:
динамический тип объекта,
cv-квалифицированная версия динамического типа объекта,
- тип, подобный (как определено в 4.4) динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных субагрегата или содержащего объединения),
- тип, который является (возможно, квалифицированным cv) типом базового класса динамического типа объекта,
char
или жеunsigned char
тип.
можем ли мы получить доступ к хранилищу другого типа, используя
(1) char *
(2) char(&)[N]
(3) std::array<char, N> &
без зависимости от неопределенного поведения?
constexpr uint64_t lil_endian = 0x65'6e'64'69'61'6e;
// a.k.a. Clockwise-Rotated Endian which allocates like
// char[8] = { n,a,i,d,n,e,\0,\0 }
const auto& arr = // std::array<char,8> &
reinterpret_cast<const std::array<char,8> &> (lil_endian);
const auto& carr = // char(&)[8]>
reinterpret_cast<const char(&)[8]> (lil_endian);
const auto* p = // char *
reinterpret_cast<const char *>(std::addressof(lil_endian));
int main()
{
const auto str1 = std::string(arr.crbegin()+2, arr.crend() );
const auto str2 = std::string(std::crbegin(carr)+2, std::crend(carr) );
const auto sv3r = std::string_view(p, 8);
const auto str3 = std::string(sv3r.crbegin()+2, sv3r.crend() );
auto lam = [](const auto& str) {
std::cout << str << '\n'
<< str.size() << '\n' << '\n' << std::hex;
for (const auto ch : str) {
std::cout << ch << " : " << static_cast<uint32_t>(ch) << '\n';
}
std::cout << '\n' << '\n' << std::dec;
};
lam(str1);
lam(str2);
lam(str3);
}
все лямбда-вызовы производят:
endian
6
e : 65
n : 6e
d : 64
i : 69
a : 61
n : 6e
https://gcc.godbolt.org/g/cdDTAM (включить -fstrict-aliasing -Wstrict-aliasing=2)
2 ответа
char(&)[N]
случай и std::array<char, N>
оба случая приводят к неопределенному поведению. Причина уже указана вами. Обратите внимание, ни char(&)[N]
ни std::array<char, N>
тот же тип, что и char
,
Я не уверен в char
случай, потому что текущий стандарт явно не говорит, что объект может рассматриваться как массив узких символов (см. здесь для дальнейшего обсуждения).
В любом случае, если вы хотите получить доступ к базовым байтам объекта, используйте std::memcpy
, как прямо сказано в стандарте в [basic.types] / 2:
Для любого объекта (кроме подобъекта базового класса) тривиально копируемого типа T, независимо от того, содержит ли объект допустимое значение типа T, базовые байты ([intro.memory]), составляющие объект, могут быть скопированы в массив символов char, unsigned char или
std::byte
([Cstddef.syn]). Если содержимое этого массива копируется обратно в объект, объект должен впоследствии сохранить свое первоначальное значение. [ Пример:#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value std::memcpy(buf, &obj, N); // between these two calls to std::memcpy, obj might be modified std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
- конец примера]
Строгое правило наложения псевдонимов на самом деле очень просто: два объекта с перекрывающимся временем жизни не могут иметь перекрывающуюся область хранения, если один не является подобъектом другого.(*)
Тем не менее, разрешено читать представление объекта в памяти. Представление объекта в памяти представляет собой последовательностьunsigned char
[Basic.types]/4:
Объектное представление объекта типа T является последовательностью N
unsigned char
объекты, занятые объектом типа T, где N равноsizeof(T)
, Представление значения объекта - это набор битов, которые содержат значение типа T.
Соответственно в вашем примере:
lam(str1)
UB (неопределенное поведение);lam(str2)
UB (массив и его первый элемент не являются взаимозаменяемыми по указателю);lam(str3)
не указано как UB в стандарте, если вы заменитеchar
отunsigned char
Можно утверждать, что вы читаете представление объекта. (он также не определен, но он должен работать на всех компиляторах)
Таким образом, используя третий случай и изменяя объявление p
в const unsigned char*
должен всегда давать ожидаемый результат. В остальных двух случаях он может работать с этим простым примером, но может сломаться, если код более сложный или в более новой версии компилятора.
(*) Из этого правила есть два исключения: одно для членов профсоюзов с общей последовательностью инициализации; и один для массива unsigned char
или же std::byte
это обеспечивает хранение для другого объекта.