Comments 24
А что же все-таки делает std::launder
?
ps:
alignas(alignof(int)) char arr[4] = { 0x0F, 0x0, 0x0, 0x00 };
int x = *reinterpret_cast<int*>(arr);
Этот код вообще будет странно работать при 16-битных char и sizeof(int)=2
stdint.h
?gcc с использованием флагов -fstrict-aliasing и -Wstrict-aliasing может отлавливать некоторые случаи, хотя и не без ложных срабатываний/неприяностей.Реальность, к сожалению, такова, что очень много кода ломается при строгом алиасинге разными неочевидными способами. Любую более-менее сложную программу, задействующую какие-либо преобразования типов, не стоит собирать без ключа
-fno-strict-aliasing
. Во многих операционных системах он включен по умолчанию (часть дефолтных C[XX]FLAGS
, например, во FreeBSD), Linux тоже с ним собирается и т.д.1) подумайте, как переписать код, чтобы обойтись без него (интересно, как много людей у которых задачи вынуждают писать приведение указателя на флоаты в указатели на инты ?)
2) напишите отдельную функцию на С для вашей платформы, с нужными ключами компиляции и т.д., и линкуйтесь с ней.
как много людей, у которых задачи вынуждают писать приведение указателя на флоаты в указатели на инты?Очень немного. Проблема в том, что большинство вообще не видит проблемы в тайпкастах (любых), они их нимало не напрягают; я уже устал объяснять почему это плохо, и что типы это друзья программиста, насильно везде включать
-Wcast-align
и пр. Очень рад, что в плюсах синтаксис кастов нарочно сделали таким уродским; надеюсь, в грядущих стандартах C++ сишные касты вообще, наконец, запретят.Ой, прошу прощения, слегка промахнулся веткой. :-(
И принудительно его добавлять его в школьных\студенческих лабах и у стажеров. (ОК, для матолимпиад и кода мидл+ — можно без него)
И чтоб оно автоматически уровень пишущего код определяло!
struct A { double x; };
struct B : A { double y; };
void cpp() {
A* a=new B[2];
a[1].x=1.0;
delete[] a;
}
stdсломается, его нельзя использовать без него. Да что там, даже простейшие веши могут являться неявными преобразованиями, только никьо об этом не думает. const char* s = "Hello!"
это неявное преобразования. Используете string - уже два.
Мне вот требуется сериализация флоатов в хмл. При том нужна полная точность, как записал так и прочитал, поэтому я не делаю перевод в десятичную запись и обратно, а кастую в (std::uint32_ t)&f, потом пишу его в шестнадцатиричным текстом в хмл. Подскажете более элегантный- strict'ный способ это сделать?
memcpy(3)
с ассертом, что размеры типов именно такие, как вы ожидаете.ieee754 не регулирует порядок байт )
а кастую в (std::uint32_ t)&f
Это UB на ровном месте.
Type aliasing
Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:
AliasedType and DynamicType are similar.
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
AliasedType is std::byte (since C++17), char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.
Отсюда. Там дальше разъясняется, что такое «similar», если кратко — uint32_t и float не similar. Вам везет, что у вас это ПОКА работает.
В кэр и в байт можно, при условии что обратно сделаете то же самое.
На этом собственно основная содержательная история со strict aliasing заканчивается, и к взаимному кастингу указателей она имеет лишь опосредованное отношение.
При этом компилятор действует достаточно умно: например если он видит что программист приводит указатель являющийся входным параметром функции к другому типу, то он переносит этот новый тип на сам входной параметр и компилирует всю функцию как если бы она имела соответствующую сигнатуру. Например если в функции
int foo( float *f, int *i ) {
*i = 1 ;
*f = 0.f ;
return *i ;
}
добавить int foo( float *f, int *i ) {
*i = 1 ;
*f = 0.f ;
char *c = reinterpret_cast<char*>(i);
*c = 2;
return *i ;
}
то компилятор будет обрабатывать код как если бы сигнатура была int foo( float *f, char *i )
Т.е. согласно strict aliasing rule эти два указателя — входных параметра могут ссылаться на один и тот же объект.Ещё не всегда очевидный пример такого же рода:
struct s1 { float d1; };
struct s2 { float d2; float d3; };
struct s3 { char* p3; };
float bar(s1 *p1, s2 *p2, uintptr_t p) {
p1->d1 = 10;
p2->d2 = 20;
s3* r = reinterpret_cast<s3*>(p);
*r->p3 = 30;
return p1->d1;
}
Есть также нюанс с перекрытием объектов одного и того же типа при расположении в памяти — частичное перекрытие позволяет с помощью разных указателей (одного и того же типа) по разным относительным смещениям (например с помощью обращения к разным полям структуры) обратиться к одному и тому же участку памяти. В этом случае несмотря на то, что эти два указателя одинакового типа очевидно могут ссылаться на один и тот же объект, и поэтому оптимизация для работающего с ними кода должна быть выключена, факт обращения к разным полям структуры определяется как доступ к строго различным участкам памяти и компилятор оптимизирует данный случай, что приводит к UB.
Не до конца понимаю. Компилятор считает, что char* может перекрываться с чем угодно. Тогда зачем для задачи
Но что, если мы хотим реализовать каламбур массива unsigned char в серию unsigned int и затем выполнить операцию с каждым значением unsigned int? Мы можем использовать memcpy, чтобы превратить массив unsigned char во временный тип unsinged int.
использовать memcpy?
Символьный тип. Типам char*, signed char*, или unsigned char* спецификация разрешает в особом порядке указывать на всё что угодно. Это значит, что они могут указывать на любую область памяти.
Т.е. компилятор должен предполагать, что unsigned char* и unsigned int* прекрываются и не оптимизировать там ничего
В стандартном Си каламбур через объединение тоже недопустим. Был он допустим в версии Ричи, где объект определялся очень смешно - область памяти определенного размера, занимаемая данными любого типа такого же размера. Гнус по умолчанию такой какламбур поддерживает, а вообще есть понятие активного члена объединения. Только с Си++ добавляется инициализация - создание объекта
Что такое Strict Aliasing и почему нас должно это волновать? Часть 2