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

    +143

    1. 1
    sprintf(path, "/usr/local/something/something_else_%d_%d.uyvy%c", some_int, some_other_int, '\0');

    Мда-с.

    Запостил: codemonkey, 28 Декабря 2014

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

    • > sprintf
      Говно.
      Ответить
      • ?????
        Ответить
        • man snprintf
          Ответить
          • Так точно.
            Ответить
          • Компиляторы от ms умеют разворачивать sprintf в snprintf. Действительно, почему юзер должен страдать хуйней и руками подставлять лимит?
            Ответить
            • > почему юзер должен страдать хуйней и руками подставлять лимит?

              Откуда компилятору знать реальные ограничения на размер буфера? В некоторых случаях он может, конечно, определить размер (если пишем в массив фиксированного размера внутри текущего скопа), в общем же случае это сделать невозможно.
              Ответить
            • Шо, компилятор знает, какой размер буфера? Даже если он выделен в куче и размер подаётся из ввода?
              Ответить
              • free(p); // всё упадёт, никто не знает, какой размер у p

                На этапе компиляции существует алгоритм вычисления размера блока на этапе исполнения.
                Ответить
                • Погодите, если выход за границы буфера - это UB, то компилятор может запилить контроль размера буфера и sprintf заменять на snprintf вместо того, чтобы форматировать жёсткий диск. Можно ещё писать в лог или cerr (как, например, node.js пишет о deprecated-методах). Хоть какая-то польза от UB.
                  Ответить
                  • >то компилятор может запилить контроль размера буфера и sprintf
                    Я думаю это будет уже не сишка. Ну в смысле полагаться на UB умного компилятора совершенно непортабельный способ.
                    Ответить
                    • UB с выводом в лог было бы годной фичей.

                      > не сишка
                      А для debug-версии - идеал.
                      Запустил, посмотрел, исправил и скомпилировал оптимизированную версию.
                      Ответить
                      • Да дебаг версии и без этого переполнения прекрасно ловят.
                        Ответить
                      • >А для debug-версии - идеал.
                        >Запустил, посмотрел, исправил и скомпилировал оптимизированную версию.
                        Ну во-первых как правильно сказано выше дебаг это и сделает, а во-вторых запилены всякого рода санитайзеры, с кучей проверок.
                        Ответить
                • > существует алгоритм вычисления размера блока на этапе исполнения...
                  ... если известно начало блока. Но sprintf же и в таком коде юзают:
                  // spritf-builder
                  buf += sprintf(buf, "%d", 42);
                  if (x)
                      buf += sprintf(buf, "%d", x);
                  Вычисли-ка, сколько байт осталось в блоке, на середину которого ссылается buf.

                  P.S. Рантайм D это как-то делает, но там, емнип, специальный навороченный аллокатор, рассчитанный на это. Для сишки это будет оверкиллом.
                  Ответить
                  • Нехуй пользоваться арифметикой указателей.
                    Ответить
                • То есть ты предлагаешь спрашивать у аллокатора размер блока по указателю? Во-первых, это слишком неочевидное поведение для сишки, во-вторых, аллокатор послать может, если указываешь не на начало блока, что в таких случаях будешь делать?
                  Ответить
                  • В-третьих аллокатор вернет размер блока (т.е. округленный минимум на 8), а не размер, переданный malloc'у. А потом что-нибудь распидорасит дальше по коду.

                    P.S. Для любителей комфорта и безопасности есть два решения - качать/писать нормальную либу для работы со строками или уходить на другой, более высокоуровневый, язык.
                    Ответить
                    • В-четвёртых, аллокатор вернёт примерный размер всей структуры, под которую выделена память, а не массива.

                      Не, можно отдельно хранить размер и как-то его спрашивать, но это уже не сишка.
                      Ответить
                    • P.P.S. А стандартная сишколиба без расширений от gnu или m$ чуть менее чем неюзабельна.

                      К примеру, в ней есть localtime и gmtime, но в обратную сторону есть только mktime, который... работает только с локальной tz, а варианта для utc тупо нет! Каким местом надо было думать, чтобы такое сделать и за столько лет не исправить? Я уж не говорю о работе с произвольной tz.
                      Ответить
                      • Не, ну я понимаю, что я могу пожертвовать localtime(), и при старте проги задать TZ=UTC... Но как я тогда буду писать удобные логи в таймзоне сервера?
                        Ответить
                    • > или уходить на другой, более высокоуровневый, язык.
                      Rust,
                      (OCaml)
                      Ответить
                  • http://govnokod.ru/17380#comment260391
                    Ответить
              • Он вместо размера подставлял sizeof(x)
                Ответить
            • В частных случаях, при известном размере массива, раслабляя жопу программиста... Чтобы потом, когда он всё-таки поюзает динамическую память, его в нее поимели хакеры.

              Я не спорю, это полезная фича. Но проблему sprintf'а и прочего говна она совершенно не решает, а только загоняет под ковёр.

              > почему юзер должен страдать хуйней...
              ...и писать на сишке, когда вокруг есть столько высокоуровневых языков.
              Ответить
              • Дай пример, когда snprintf(str, sizeof(str), ...) может привести к выходу за границу. А вообще загуглите- найдете эту фичу.
                Ответить
                • > Дай пример, когда snprintf(str, sizeof(str), ...) может привести к выходу за границу.
                  Лол, блять. Ну держи, фома неверующий:
                  char *str = malloc(100500);
                  sprintf(str, sizeof(str), ...);
                  Ответить
                  • Я не неверующий, ты меня неправильно понял. Я просто плохо знаю си. Что этот код делает?
                    Ответить
                    • Выделяет буфер размером 100500 байт в куче и sprintf'ит в него. Но т.к. sizeof() нормально работает только на массивах с известной длиной (т.е. char buf[100500]), то оно посчитает sizeof() указателя (4 на x86, 8 на x86_64) и, само собой, обосрется обрезав буфер на 4-8 байт.

                      Т.е. вижуалка в таком коде всяко не будет делать замену sprintf на snprintf. Максимум какой-нибудь ворнинг кинет или ошибку.

                      P.S. Емнип, она вообще sprintf и прочие подобные функции обзывала deprecated и заставляла юзать свои альтернативные версии (которые как раз проверяют длину).
                      Ответить
                      • Нагуглил таки для тебя http://msdn.microsoft.com/en-us/library/ce3zzk1k%28v=vs.80%29.aspx
                        Это таки для cpp. Ну значит нехуй летать в нашем небе писать на си.
                        Ответить
                        • > Нагуглил таки для тебя
                          Спасибо, значит память меня не подвела.

                          1) sprintf они считают deprecated и показывают предупреждение (и правильно делают!)
                          2) sprintf_s для сишки всегда требует указывать размер буфера вручную, как и snprintf из C99
                          3) sprintf_s для крестов для простых случаев типа char s[10] считает этот размер сам, в остальных же - требует указывать размер вручную
                          Ответить
                          • Но и snprintf может привести к переполнению, так? Или он не может привести к выполнению кода?
                            Ответить
                            • > snprintf может привести к переполнению
                              Если ему наврать и передать неправильный размер - запросто приведёт. Если же размер передан правильно - всё будет ок, просто строка обрежется под размер буфера (об этом можно узнать по возвращаемому snprintf'ом значению).
                              Ответить
                              • Ну а это переполнение может привести к выполению кода?
                                Ответить
                                • А чем это переполнение отличается от других переполнений?
                                  Ответить
                                  • Ты понимаешь, как работает срыв стека? Для этого нужно выйти за границу локальной переменной и перезаписать адрес возврата. На куче все совсем по другому, я даже хз как.
                                    Ответить
                      • >Но т.к. sizeof() нормально работает только на массивах с известной длиной
                        Известной в статике?
                        Ответить
                        • > Известной в статике?
                          Да, известной на момент компиляции. С VLA (variable length array) тоже работает, но VLA - это гццизм, на него можешь не обращать внимания.
                          Ответить
                          • Теперь понятно. Я просто много времени провел с говнокодом одного игрового сервера времен конца 90-х, где еще статические буферы использовались.
                            Ответить
                    • sizeof даёт размер операнда. Если операнд - массив - даётся размер массива. Если указатель - размер указателя (сейчас это 4 или 8).

                      sizeof(str) - размер указателя, sizeof(*str) - размер чара - ровно 1.
                      Размер блока есть, как пишут выше, у аллокатора и программиста.

                      char *str = malloc(100500);
                      sprintf(str, sizeof(str), ...); // размер - 4 или 8
                      
                      char str[100500];
                      sprintf(str, sizeof(str), ...); // размер 100500


                      Вижу, Борманд уже написал, но не пропадать же добру. Запощу.
                      Ответить
    • '\0' доставил.
      Ответить

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