Pull to refresh

Comments 21

Спасибо за статью. Вот только написать
Пусть мы решили написать функцию, которая меняет знак у float, меняя 31-й бит бинарного представления float
и не сказать о том, что так лучше не делать — может быть чревато.
Если не вдаваться в ккаие-то теоретические дебри стандарта и чисто гипотетические случаи, то это ничем не чревато. Если используется стандарт IEEE754, то эта функция изменит знак числа. Я проверил генерацию кода на gcc и clang для основных платформ (x86 и ARM) и всё компилируется правильно.
Если вы про gcc4.4.7 -m32 -strict-aliasing, то эта проблема проявляется исключительно в этой конкретной версии компилятора, и нигде более. Вот тут я немного написал про это: 32bit-me.livejournal.com/174869.html
Причем здесь проявляется или не проявляется? Вам ли не понимать, к чему может привести UB в коде. Если флаг -strict-aliasing указан то все, приехали.
Но его можно не указывать!
На самом деле, для микроконтроллеров и других устройств без fpu операции манипулирования битами могут сильно оптимизировать скорость исполнения, и отказываться от них не следует. Подобные ограничения могут сильно усложнить дело.
Я не предлагаю отказываться полностью. Я предлагаю лишь не замалчивать потенциальные проблемы.

Мир контроллеров это особый мир, со своими правилами и проблемами, и к нему уж точно не стоит апеллировать в статье на общую тему.

Те, кому надо выжать из камня максимум, рано или поздно сами придут к битовым операциям, но уже с полным осознанием последствий.
Вставил в статью ссылку на ваше видео по алиасингу.
>> Но его можно не указывать!
Есть стандартный способ кастить float в его бинарное представление через union. И не надо отключать strict aliasing.
union { int dst; float src; } x;
«Множество оговорок» про доступ к элементам union через указатели так себе аргумент. Ведь union и используется для того чтобы избежать использования указателей.
У меня для кастов есть темплейтная функция и проблем с ней не замечено на различных компиляторах и платформах.
Ведь union и используется для того чтобы избежать использования указателей.
Union — это средство экономии памяти. Точка. Запись в одно поле union'а, а чтение из другого — это неопределённое поведение со всеми вытекащими (вызовом невызываемой фукнции и форматированием винчестера).

GCC даёт некоторые, очень ограниченные гарантии, что программа, которая такое делает не сломается сразу же.

Вот так:
union a_union {
  int i;
  double d;
};

int f() {
  union a_union t;
  t.d = 3.0;
  return t.i;
}
работает. Вот так:
int f() {
  double d = 3.0;
  return ((union a_union *) &d)->i;
}
уже нет.

Clang не делает даже этого. Единственный гарантированно работающий способ — это bit_cast (ну или ручная реализация оного через char*).

Дискусия на тему как бы это можно было разрешить делать в C++20 без memcpy — всё ещё продолжается.

У меня для кастов есть темплейтная функция и проблем с ней не замечено на различных компиляторах и платформах.
Вам очень хочется дождаться того, чтобы у заказчика отформатировался случайно винчестер? Дождётесь.

Мы говорили с разработчиками clang'а по поводу этого расширенного ипользования union'ов, разрешённого GCC. Их ответ был «ну, мы, по возможности, стараемся не ломать такой код, но если не уследим — поводом срочно выпускать фикс это не будет».

Потому и в документации ничего нет.
Это гарантируется стандартами C99 и C11. так что для C-кода всё норм, достаточно затребовать поддержку стандарта.
Вы «затребовали поддержку стандарта», разработчики компиляторов сказали нет, нет и не будет. Ваш следующий шаг? Нанять других разработчиков и сделать другой компилятор?

Если вы работаете с указателями и компилятор «не видит» вашего union'а — то он будет нарушать эту часть стандарта — можете убедиться. А если вы хотите скопировать данные в union, дальше использовать его для преобразования типов, потом скопировать обратно (что работает в GCC и обычно работает в clang) — так проще уж использовать memcpy и построенный поверх него bitcast.

Что делать с type punning'ом через union'ы в стандарте C (где он таки действительно определён) — сейчас обсуждается (скорее всего в следующей версии либо сильно обкорнают, либо выкинут), но на практике компиляторы его уже не поддерживают.
Я просто не пойму зачем провоцировать людей на подобное трюкачество? Вот такая функция точно так же будет преобразована в одну инструкцию fneg на тех компиляторах, которые это умеют:

float negate(float num) {
    return num * -1;
}

Только, в отличие от примера в статье, данная функция:
  • Абсолютно понятна читающему
  • Ни при каких условиях не провоцирует неопределенное поведение
  • Не работает с указателями — больше простора для оптимизации
  • На кривых компиляторах все равно отработает как надо
В данном случае проще написать
return -num;

Основные компиляторы для основных платформ с этим справятся, это действительно так. Но в целом, есть случаи, когда нам необходимо сделать преобразование float в его битовое придставление, и работать непосредственно с ним.
Код приведен просто для примера, чтобы показать, что компилятор на самом деле довольно умный.
Список оптимизаций для впечатлительных
-targetlibinfo
-tti
-tbaa
-scoped-noalias
-assumption-cache-tracker
-profile-summary-info
-forceattrs
-inferattrs
-ipsccp
-globalopt
-domtree
-mem2reg
-deadargelim
-domtree
-basicaa
-aa
-instcombine
-simplifycfg
-basiccg
-globals-aa
-prune-eh
-always-inline
-functionattrs
-domtree
-sroa
-basicaa
-aa
-memoryssa
-early-cse-memssa
-speculative-execution
-domtree
-basicaa
-aa
-lazy-value-info
-jump-threading
-lazy-value-info
-correlated-propagation
-simplifycfg
-domtree
-basicaa
-aa
-instcombine
-libcalls-shrinkwrap
-loops
-branch-prob
-block-freq
-pgo-memop-opt
-domtree
-basicaa
-aa
-tailcallelim
-simplifycfg
-reassociate
-domtree
-loops
-loop-simplify
-lcssa-verification
-lcssa
-basicaa
-aa
-scalar-evolution
-loop-rotate
-licm
-loop-unswitch
-simplifycfg
-domtree
-basicaa
-aa
-instcombine
-loops
-loop-simplify
-lcssa-verification
-lcssa
-scalar-evolution
-indvars
-loop-idiom
-loop-deletion
-loop-unroll
-memdep
-memcpyopt
-sccp
-domtree
-demanded-bits
-bdce
-basicaa
-aa
-instcombine
-lazy-value-info
-jump-threading
-lazy-value-info
-correlated-propagation
-domtree
-basicaa
-aa
-memdep
-dse
-loops
-loop-simplify
-lcssa-verification
-lcssa
-aa
-scalar-evolution
-licm
-postdomtree
-adce
-simplifycfg
-domtree
-basicaa
-aa
-instcombine
-barrier
-basiccg
-rpo-functionattrs
-globals-aa
-float2int
-domtree
-loops
-loop-simplify
-lcssa-verification
-lcssa
-basicaa
-aa
-scalar-evolution
-loop-rotate
-loop-accesses
-lazy-branch-prob
-lazy-block-freq
-opt-remark-emitter
-loop-distribute
-branch-prob
-block-freq
-scalar-evolution
-basicaa
-aa
-loop-accesses
-demanded-bits
-lazy-branch-prob
-lazy-block-freq
-opt-remark-emitter
-loop-vectorize
-loop-simplify
-scalar-evolution
-aa
-loop-accesses
-loop-load-elim
-basicaa
-aa
-instcombine
-latesimplifycfg
-domtree
-basicaa
-aa
-instcombine
-loops
-loop-simplify
-lcssa-verification
-lcssa
-scalar-evolution
-loop-unroll
-instcombine
-loop-simplify
-lcssa-verification
-lcssa
-scalar-evolution
-licm
-alignment-from-assumptions
-strip-dead-prototypes
-domtree
-loops
-branch-prob
-block-freq
-loop-simplify
-lcssa-verification
-lcssa
-basicaa
-aa
-scalar-evolution
-branch-prob
-block-freq
-loop-sink
-lazy-branch-prob
-lazy-block-freq
-opt-remark-emitter
-instsimplify
-simplifycfg
-verify
UFO just landed and posted this here
О, если я буду работать с cuda 8 + msvc14, то обязательно это учту :)
Кстати, вы могли бы привести пример того, что при этом происходит.
Не понял, почему автор считает, что неинициализированный указатель должен быть равен null?
a variable with static storage duration (3.7.1) or thread storageduration (3.7.2) is zero-initialized (8.6)
Sign up to leave a comment.

Articles