Pull to refresh

Comments 18

Спасибо за перевод! Эту серию статей давно надо было перевести.
Я думаю, вы можете даже им отправить перевод для публикации на сайте: у них уже есть перевод на японский.
Я не уверен, что это будет интересно Крису Латтнеру.
Не знал, что преобразование int* во float* неопределено. А float* в int*? А double* в int* и обратно?
Ни одно из них не определено, об этом недавно писали на хабре и вот есть ссылка на эту тему: youtu.be/ACW-7dktyDk
Правильно делать через memcpy, оптимизирующий компилятор сам удалит ненужный вызов функции.
Не знал, что преобразование int* во float* неопределено.
Отлично оно определено, не надо наездов! Другое дело, что перед использованием нужно обратно из float* в Int* преобразовать.

Ну и зачем тогда оно такое нужно? Проще считать что оно не определено само по себе, меньше wtfpm в коде будет.

Ну и зачем тогда оно такое нужно?
А зачем, скажем, void* нужен? Для тех же целей можно и int* использовать. Например если у вас функция принимает int* и потом передаёт его в callback — то можно передать указатель на float*, сконвертировав его в нужный тип — а потом сконвертировать обратно.

На каламбуре типизации свет клином не сошёлся, в языке много других фич есть…
Вот затем void* и нужен, чтобы принимать его и передавать в callback. А необходимость пропихивать float* сквозь int* — это архитектурный просчет.
С этим никто не спорит. Разумеется это просчёт. Но возможность его легко и просто «заткнуть» не вызывая UB — бывает весьма полезна.
Не все-таки не пойму при чем здесь memcpy?
int a=1;
int* pa = &a;
float* pfa = (float*)pa;

Тут ни одного копирования нет. Куда тут memcpy сунуть? Наверное, в статье имелось ввиду что-то другое.

Думаю, подразумевалось следующее:


int a = 1;
float b;
int *pa = &a;
float *pb = &b;
memcpy(pb, pa, sizeof(a));
Результат этой операции: (float*)pa; не определён в соответствии со стандартом.
Правильно так, например:
int i = 1;
float res;
memcpy(&res, &i, sizeof i);
Результат этой операции: (float*)pa; не определён в соответствии со стандартом.
На самом деле определён. Не определено разименование этого указателя.

Записали int читайте int. Записали float — читайте тоже float.

Тест для само-проверки: представьте, что работа с floatами выполняется отдельным [со]процессором (а когда-то так и было — что 68881, что 8087 — это отдельные железки) и он работает параллельно с основным процессором. Соответственно если мы не вставим явную инструкцию синхронизации никто вам не гарантирует, что данные, которые вы хотите прочитать уже будут готовы к тому моменту, когда вы соберёсь их читать.

Таким образом описанный в известной статье код работает только на достаточно узком наборе железа и сопутствующих компилфторов: в 1980е — железо не позволяло ему правильно работать, а уже в нашем веке — компиляторы.

Но вот там и тогда, когда оно использовалось — да, оно работало…
В соответствии со стандартом результат этой операции не определён, если он имеет некорректное выравнивание. Если же мы каким-либо образом удостоверились, что выравнивание корректно, то компилятор обязан обеспечить ожидаемое поведение
Как я уже сказал преобразование указателей — это ерунда. Вот использование указателей — это уже неопределённое поведение. На stack overflow есть подробное обсуждение. С неформальным обсуждением и ссылками на пункты стандарта.
Маленькая поправка:
Для беззнаковых переменных ничего не стоит гарантировать переполнение по модулю 2 (заворачивание)


В оригинале написано
It is worth noting that unsigned overflow is guaranteed ...
— «стоит отметить, что беззнаковое переполнение гарантировано...»
Sign up to leave a comment.

Articles