1. Си / Говнокод #20518

    −47

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    float *quaternions;
    
    for(size_t i = 0; i < 4; i++) {
        ((int32_t *)&quaternions)[i] = __builtin_bswap32(((int32_t *)&quaternions)[i]);
    }
    
    /**GCC: "какая-то магия с приведением и разворотом. Меня всё устраивает, всё ок." **/
    
    А если:
    
    ((int32_t *)&quaternions)[0] = __builtin_bswap32(((int32_t *)&quaternions)[0]);
    
    /**GCC: "warning: dereferencing type-punned pointer will break strict-aliasing rules" **/

    Запостил: MiD, 11 Августа 2016

    Комментарии (60) RSS

    • по этой причине кучи проектов strict-aliasing и отключают.

      это, и различия cast vs conversion в прошлом (пока С и С++ не выровняли) были главными граблями почему С/С++ код ломался если его компилить как C++/C. у меня в одной либе, в С режиме, со стрикт алиасингом, компилятор несколько функций де/сериализации данных в лоб выкидывал: патаму чта поынтеры разных типов, и эти инлайн функции очевидно не имеет ни-ка-ко-го эффекта!!!
      Ответить
      • если проблемных мест мало, то его лучше не совсем отключать, а для "избранных" файлов
        Ответить
        • как ГК сверху демонстрирует, отличить "избраные файлы" от остальных, это нетривиальная задача. если не ошибаюсь, в моем случае компилер (то ли SunStudio, то ли HP aCC; проблема не уникальна для GCC) показывал кучи ворнингов - но в нескольких "избраных" местах и не моргнули. мне тогда просто повезло что в тех местах сразу все валилось с segmentation fault когда до туда исполнение доходило.
          Ответить
          • Какой багор )))
            Ответить
          • А как так получается, что цикл варнинг прячет?
            Ответить
            • я что на гения похож? подожди какого борманда или lisp-o-haskel team - они больше любят загадки.

              PS моя догадка потому что во втором случае он заменяет `[0]` на обычное `*` - указатель используется напрямую. в первом случае, кастнутый указатель напрямую не используется - он используется как указатель на массив.
              Ответить
              • В доке написано, что он по-дефолту на бекенде проверку делает. Так что, походу, если ничего не выбросил - то и не замечает...
                Ответить
            • Потому что предупреждение о стрикт алиасинге - всего лишь эвристика. Там несколько режимов даже есть. И все из них - хуёвые.

              Так что либо отключай strict aliasing на весь проект, либо вычищай все говнокасты из кода (и молись, что они нигде не затесались).
              Ответить
              • я так понимаю за подробностями - курить gcc?)
                Ответить
                • > курить gcc
                  А смысл? Задачка то в принципе неразрешимая, и gcc никогда не найдёт все эти баги...

                  З.Ы. Походу, единственное решение без UB'ов при включенном strict aliasing выглядит примерно так:
                  uint32_t tmp;
                  static_assert(sizeof(tmp) == sizeof(quaternion[i]), "size mismatch");
                  memcpy(&tmp, &quaternion[i], sizeof(tmp));
                  tmp = __builtin_bswap32(tmp);
                  memcpy(&quaternion[i], &tmp, sizeof(tmp));
                  Ответить
                  • а если так:
                    union {
                    float *a;
                    int32_t *b;
                    } convert;

                    convert.a = quaternions;

                    for(size_t i = 0; i < 4; i++) {
                    convert.b[i] = __builtin_bswap32(convert.b[i]);
                    }
                    Ответить
                    • должно работать, IMO. грабли со стрикт алиасингом заключаются в том что компилятор "не замечает" что значение переменной поменялось, если оно менялось через кастнутый указатель на другой тип. если значения доходят до памяти - то скорее всего будет все в порядке (если чтение происходит в другой функции/этц). в моем случае грабли были с локальными переменными, когда данные читались/писались из/в void* буффера в/из локальные char/short/int переменные. компилер просто в лоб игнорировал только что произведенную конверсию и брал старое значение переменной (или мусор), закэшированое в регистре.
                      Ответить
                      • > должно работать
                        Вроде только так (gcc'шники обещают в примере к опции -fstrict-aliasing, что будет работать):
                        union {
                            float a;
                            uint32_t b;
                        } convert;
                        static_assert(sizeof(float) == sizeof(uint32_t), "size mismatch");
                        
                        for(size_t i = 0; i < 4; i++) {
                            convert.a = quaternions[i];
                            convert.b = __builtin_bswap32(convert.b);
                            quaternions[i] = convert.a;
                        }
                        А с указателями внутри юниона - тот же самый UB.

                        З.Ы. Причём работа с указателями на поля юниона - тоже UB...
                        convert.a = 4.0f;
                        uint32_t*p = &convert.b;
                        x = *p; // UB!
                        Ответить
                        • как страшно жить...
                          Ответить
                          • ты понял что страшно жить не тогда, когда нашел перевернутые флоаты, а только сейчас?
                            Ответить
                            • ну я то нашёл перевернутый uint32_t и даже обрадовался...сперва.
                              Ответить
                        • > З.Ы. Причём работа с указателями на поля юниона - тоже UB...

                          не может быть. я вычитывал это на гцц мэйл листах - и в последствии этим пользовался. в моем случае, для конверсии я вместо каста поинтера, передавал указатель на одно поле юниона, а потом читал конвертнутое значение из другого поля юниона.

                          как я понял, union это официальный способ сказать компилеру о том что данные алиясятся. потому что иначе, если указатели разного типа (исключая char*) то они никогда не алиясятся.
                          Ответить
                          • > не может быть
                            Ну прочти ман по опции -fstrict-aliasing ;) Я его так поняо, что они гарантируют только алиасинг полей. И только если к ним обращаются через сам union. Т.е. не через перекастованный указатель на юнион, не через указатели на его поля, а только как u.a и u.b.

                            З.Ы. И твой пример про "передавал указатель, а потом читал из другого" там приведён в качестве примера говнокода :3
                            Ответить
                            • хез.

                              пример из гцц доки:

                              int f() {
                              	a_union t;
                              	int* ip;
                              	t.d = 3.0;
                              	ip = &t.i;
                              	return *ip;
                              }


                              такого изврата я не делал, но простое `return t.i` работало на ура.

                              в моем случае, я делал для конверсии локальные юнионы типа `union { int i; char raw[sizeof(int)]; }` что бы типизированые данные читать/писать из потока.
                              Ответить
                              • > такого изврата я не делал
                                А чем передача указателя на t.raw в read() отличается от их примера? :)
                                Ответить
                                • хез. тем что работало и работает? ;-)
                                  Ответить
                                  • ... До того момента, как оно сломается после изменения какого-то кода в совершенно непричастном файле.
                                    Ответить
                          • Ты можешь пихать в юнион официально указатели разных типов, если не собираешься их потом разименовывать, так-то. Тащем-то Borm and прав.
                            Ответить
                    • Нет. Так нельзя. Это тот же самый UB - конпелятор вправе считать, что два указателя на разные типы всегда указывают в разные места (если один из них не char*). Т.е. convert.a[i] и convert.b[i] не имеют друг к другу никакого отношения и конпелятор имеет полное право выкинуть твой бсвап на мороз...

                      Через юнион можно только значения кастовать (если разрабы конкретного компилятора это гарантируют, само собой).
                      Ответить
                      • > Через юнион можно только значения кастовать (если разрабы конкретного компилятора это гарантируют, само собой).

                        еще раз, на всякий случай: юнион это официальный способ в C как разраб сообщает компилеру что данные алиясятся. просто в коммерческих компиляторах больше эвристик чем в гцц, почему проблемы на гцц происходят чаще.

                        ЗЫ я думаю что с указателями на самом деле UB, потому что это всего лишь говорит что значения указателей алиясятся - это не говорит о том что данные на которые они указывают алиясятся. что как по мне было бы не и таким плохим предположением для компилятора.
                        Ответить
                        • > коммерческих компиляторах
                          Ну майкрософт вон вообще забил на strict aliasing, иначе пришлось бы кучу индусов разогнать...
                          Ответить
                        • Да, через юнион. Не через указатели на члены юниона. Поэтому никакие эвристики не нужны — у тебя либо есть юнион и всё легально, либо указатели разных типов и это нелегально.

                          union foo { int a, float b} u;
                          u.a = 42;
                          print(u.b);

                          Легально, но
                          union foo { int a, float b} u;
                          int* x = &u.a; float* y = &u.b;
                          *x = 42;
                          print(*y);

                          это UB
                          Ответить
                          • > никакие эвристики не нужны

                            эвристики нужны что бы вместо gcc-шного "UB! сам дурак!!", делать то что подавляющее большинство разрабов ожидает. в конце концов, если ты кучу бабла за навороченый компилер отвалил, а он тебе у виска пальцем крутит... то кастомеры могут и разбежатся.

                            на самом деле это просто убогие баги компилера. в те времена, с тем конвертором, генерёный асм был конкретным примером этого бага: на пустом месте (где должен был быть код из static inline конвертора) из eax бралось значение (там был мусор от предыдущих операций) и просто писался в вывод. раскажи мне в какой реальности это осмысленая кодогенерация.
                            Ответить
                            • > эвристики нужны что бы вместо gcc-шного "UB! сам дурак!!", делать то что подавляющее большинство разрабов ожидает

                              Всегда можно написать код, который никакие эвристики не возьмут, а кто-то будет обижаться что код не работает, как хочется. Поэтому нужно делать ровно наоборот.

                              > кастомеры могут и разбежаться
                              Поэтому нужно делать постепенно — раз какой-то запутанный код перестал работать, а тебя тыкают носом в стандарт, потом ещё пара строчек сломалась...
                              Собственно так и происходит: ГЦЦ стал жестче оптимизировать, мелкософтовский компилятор стал жестче оптимизировать, шланг оригинально жестче оптимизировал...

                              А если какой компилятор забьёт, его зачморят другие: "На этом тесте мы на 30% быстрее другого компилятора, потому что мы не считаем что данные указатели могут указывать на ту же память (что случается в 99.95% случаев)"
                              Ответить
                              • > Всегда можно написать код, который никакие эвристики не возьмут

                                ты пытаешься мне какие-то высокие материи объяснять, в то время как у меня в голове сидит тривиальный код (одна функция в ручную заинлайнена), к которому сводились мои прошлые проблемы:

                                int a;
                                *((float *)(&a)) = 1.0f;
                                write_int(a); // пишет мусор


                                когда компилер не способен даже такое скомпилировать, то извините меня...
                                Ответить
                                • Дай человеку хлеба, завтра он спросит, почему без масла.

                                  Раньше этот код работал как ожидалось. Но потом решили что лучше бить сразу линейкой по рукам, чем воспитывать говнокодеров и городить костыли каждый раз, когда им кажется что код не работает так как им хочется.
                                  Ответить
                                  • ты зациклился на какой-то высокоуровневой идеалистичной херне. "если весь мир не прав - давайте лучше исправим мир, а не наши недалёкие иделы."
                                    Ответить
                                    • В последнее время, компиляторы всё агрессивнее используют UB, тут ещё вопрос, кто не прав.

                                      Учитывая что никто не предлагал ослабить требования к алиасингу, добавив в стандарт случаи, когда компилятору нужно учитывать, что указатели указывают в одно и тоже место, это никому не нужно.
                                      Потому что, если что-то кому-то реально нужно, он берёт и пишет предложение. Может мир, в котором компиляторы начинают следовать стандарту, а не своим несовместимым расширениям, и неправ, но почему-то на это почти никто не жалуется.
                                      Ответить
                                    • показать все, что скрытоКакой багор )))
                                      Ответить
                                  • > бить сразу линейкой по рукам
                                    Но ведь гцц не бьёт линейкой, а вместо этого подкладывает грабли, пока ты отвернулся...
                                    Ответить
                              • "... жесткий как стейк за два бакса" (с)
                                Ответить
                              • показать все, что скрытоКакой багор )))
                                Ответить
                    • старый пост, но лучше этого объяснения тематики я еще не видел:

                      http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
                      Ответить
                • > > либо вычищай все говнокасты из кода

                  > курить gcc?

                  union это официальный способ обхода этой проблемы.

                  на тему было уже много написано. типа http://stackoverflow.com/questions/2906365/gcc-strict-aliasing-and-casting-through-a-union
                  Ответить
        • показать все, что скрытоКакой багор )))
          Ответить
      • показать все, что скрытоКакой багор )))
        Ответить
    • Кстати, а где ты нашёл файл с float'ом вверх-ногами?
      Ответить
      • По сети пришёл
        Ответить
      • чутка не понял.. какой файл?
        Ответить
        • Ну не от хорошей жизни же тебе пришлось переворачивать float? Значит он в каком-то файле или сетевом протоколе кверху-жопой пришёл?
          Ответить
          • esp8266 кастомная прошивка wifi-uart мост. Вот и пришлось изголяться
            Ответить
    • а можно же еще сунуть в отдельный translation unit, чтобы оптимизнуть не могло? Типа функция принимает uint32_t, возвращает float
      Ответить
      • LTO.

        Только динамическая линковка, только хардкор!
        Ответить
        • я думал lto только unused функции выкидывает...
          Ответить
      • Так оно его в том юните и оптимизнёт к хуям :)

        Ну или потом кто-нибудь включит глобальную оптимизацию...
        Ответить
        • так просто return reinterpret_cast, не?
          Ответить
          • float в int не реинтерпреткастятся же.
            Ответить
            • https://ideone.com/7dBWhK
              Ответить
              • И где тут реинтерпреткаст флоата в инт? Я тут только UB с прокастом указателей вижу.
                Ответить
                • просто, ну очень хотелось))
                  А должно быть типа что-то типа :
                  return *(int)(float*)&a; - само собой это хуита, просто реинтерпреткаст подобного вида, не?
                  Ответить
                  • > return *(int)(float*)&a
                    Конпелятор выкинет эту херню, да и всё... Ведь то место, куда указывает указатель на int не может иметь ничего общего с флоатом...
                    Ответить
              • Кстати, в 17 крестах сработает и будет легальна вот такая операция: return *std::launder((int *)&a);
                Ответить
              • показать все, что скрыто> return (int)*(float *)&a;
                чем это отличается от return (int)a; ?

                &a - берем указатель на a (указатель на float)
                (float *) - зачем-то приводим float* к float*
                * - обратно разименовываем
                (int) - приводим float к инту
                Ответить

    Добавить комментарий