Я не знаю Си

    Цель этой статьи — заставить всех, особенно программистов на Си, сказать «я не знаю Си».
    Хочется показать, что тёмные углы в Си значительно ближе, чем кажется и даже тривиальные строки кода несут в себе undefined behavior.

    Статья организована как набор вопросов. Ответы написаны белым. Все примеры — отдельные файлы исходного кода.

    1.
    int i;
    int i = 10;
    


    Q: Это корректный код? (Не возникнет ли ошибка, связанная с тем, что переменная определяется два раза? Напоминаю, это отдельный файл исходного кода, не на уровне функции или compound statement)

    A: Да, это корректный код. Первая строка это предварительное объявление (tentative definition), которое становится объявлением после того, как компилятор обработал определение (вторую строку).

    2.
    extern void bar(void);
    
    void foo(int *x)
    {
      int y = *x;  /* (1) */
      if(!x)       /* (2) */
      {
        return;    /* (3) */
      }
    
      bar();
      return;
    }
    


    Q: Оказалось, что bar() вызывается даже тогда, когда x — нулевой указатель (и программа не завершается аварийно). Ошибка оптимизатора или всё корректно?

    A: Да, всё корректно. Если x — нулевой указатель, то в строке (1) появляется undefined behavior, и тут уже никто ничего программисту не обязан: программа не обязана ни упасть в строке (1), ни сделать return в строке (2) если вдруг удалось выполнить строку (1). Если говорить о том, какими правилами руководствовался компилятор, то всё происходило так. После анализа строки (1) компилятор считает, что x не может быть нулевым указателем и удаляет (2) и (3) как недоступный код (dead code elimination). Переменная y удаляется как неиспользуемая и так как тип *x не квалифицирован volatile, то и чтение из памяти тоже удаляется.

    Вот так неиспользуемая переменная убрала проверку на нулевой указатель.


    3.
    Была вот такая функция:
    #define ZP_COUNT 10
    
    void func_original(int *xp, int *yp, int *zp)
    {
      int i;
      for(i = 0; i < ZP_COUNT; i++)
      {
        *zp++ = *xp + *yp;
      }
    }
    


    Её захотели соптимизировать так:
    void func_optimized(int *xp, int *yp, int *zp)
    {
      int tmp = *xp + *yp;
      int i;
      for(i = 0; i < ZP_COUNT; i++)
      {
        *zp++ = tmp;
      }
    }
    


    Q: Можно ли вызвать исходную и оптимизированную функцию так, чтобы получились разные результаты в zp?

    A: Можно, пусть yp == zp.

    4.
    double f(double x)
    {
      assert(x != 0.);
      return 1. / x;
    }
    


    Q: Может ли эта функция вернуть inf (бесконечность)? Считать, что числа с плавающей запятой реализованы по IEEE 754 (подавляющее большинство машин). assert включен (NDEBUG не определён).

    A: Да. Достаточно передать денормализованный x, например, 1e-309.

    5.
    int my_strlen(const char *x)
    {
      int res = 0;
      while(*x)
      {
        res++;
        x++;
      }
      return res;
    }
    


    Q: Приведённая выше функция должна возвращать длину null-terminated строки. Найдите ошибку.

    A: Использование типа int для хранения размеров объектов является ошибочным: не гарантируется, что int сможет вместить размер любого объекта. Следует использовать size_t.

    6.
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
      const char *str = "hello";
      size_t length = strlen(str);
      size_t i;
      for(i = length - 1; i >= 0; i--)
      {
        putchar(str[i]);
      }
      putchar('\n');
      return 0;
    }
    


    Q: Цикл является вечным. Почему?

    A: size_t — беззнаковый тип. Если i беззнаковое, то i >= 0 всегда выполняется.

    7.
    #include <stdio.h>
    
    void f(int *i, long *l)
    {
      printf("1. v=%ld\n", *l); /* (1) */
      *i = 11;                  /* (2) */
      printf("2. v=%ld\n", *l); /* (3) */
    }
    
    int main()
    {
      long a = 10;
      f((int *) &a, &a);
      printf("3. v=%ld\n", a);
      return 0;
    }
    


    Данную программу скомпилировали двумя разными компиляторами и запустили на little-endian машине. Получили два разных результата:
    1. v=10    2. v=11    3. v=11
    1. v=10    2. v=10    3. v=11
    


    Q: Как объяснить второй результат?

    A: В данной программе есть undefined behavior, а именно, нарушены правила строгого алиасинга (strict aliasing). В строке (2) изменяется int, поэтому можно считать что любой long не изменился. (Нельзя разыменовывать указатель, который алиасит другой указатель несовместимого типа.) Поэтому компилятор может передать в строке (3) тот же long, который был считан в процессе выполнения строки (1).

    8.
    #include <stdio.h>
    
    int main()
    {
      int array[] = { 0, 1, 2 };
      printf("%d %d %d\n", 10, (5, array[1, 2]), 10);
    }
    


    Q: Это корректный код? Если здесь нет undefined behavior, то что он выводит?

    A: Да, тут использован оператор запятая. Сначала вычисляется левый аргумент запятой и отбрасывается, затем вычисляется правый аргумент и используется как значение всего оператора. Вывод: 10 2 10.

    Обратите внимание, что символ запятой в вызове функции (например, f(a(), b())) не является оператором запятой и поэтому не гарантирует порядок вычислений: a(), b() могут быть вызваны в любом порядке.


    9.
    unsigned int add(unsigned int a, unsigned int b)
    {
      return a + b;
    }
    


    Q: Каков результат add(UINT_MAX, 1)?

    A: Переполнение беззнаковых чисел определено, вычисляется по модулю 2^(CHAR_BIT * sizeof(unsigned int)). Результат 0.

    10.
    int add(int a, int b)
    {
      return a + b;
    }
    


    Q: Каков результат add(INT_MAX, 1)?

    A: Переполнение знаковых чисел — undefined behavior.

    11.
    int neg(int a)
    {
      return -a;
    }
    


    Q: Возможен ли тут undefined behavior? Если да, то при каких аргументах?

    A: neg(INT_MIN). Если ЭВМ представляет отрицательные числа в дополнительном коде (англ. twos complement, подавляющее большинство машин), то абслютное значение INT_MIN на единицу больше, чем абсолютное значение INT_MAX. В этом случае -INT_MIN вызывает знаковое переполнение — undefined behavior.

    12.
    int div(int a, int b)
    {
      assert(b != 0);
      return a / b;
    }
    


    Q: Возможен ли тут undefined behavior? Если да, то при каких аргументах?

    A: Если ЭВМ представляет отрицательные числа в дополнительном коде, то div(INT_MIN, -1) — см. предыдущий вопрос.

    — Dmitri Gribenko <gribozavr@gmail.com>

    Creative Commons License
    This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 309

      +8
      На iPad-e ответы не select-ятся :) может сделать еще отдельный pastebin? :)
        +8
        Оно надо выделить мышью или чем там у Вас на йападе
          +129
          айпальцем сойдет.
            –10
            Особенности webkit-а
              –7
              Ну эти тонкости знает тот кто знает как устроен Chrome, я же об этом WebKit вспоминаю когда запускаю #make install clean. Но видя что я могу увидеть, а чел нет, то стремлюсь помочь.
            0
            Отправил ЛС.
              +1
              Спасибо
                +1
                Написал комментарий.
                +2
                Копипасти в блокнот и там читай — я так на айфоне делал.
                  +1
                  Ого, прям лайф хак :) спасибо :)
                    0
                    Попробуйте в opera mini. Айпада под рукой нет, но на андроиде текст выделяется привычным образом.
                    Вообще, постоянно ей обхожу похожие проблемы с сафари, например когда выделение принудительно пытается выбрать большой кусок текста вместо строки.
                  +23
                  Вот оно, легендарное юзабилити :) Это почти как Джобс говорил — «Просто не держите его таким образом (про четвертый айфон)»
                    +18
                    Я думаю, в понятие юзабилити не входит такая операция, как чтение белого текста на белом фоне.
                      +49
                      Да ну, ты брось, зато как весело потроллить можно — айпэд не показывает белый текст на белом фоне? Фуу! Да это делает даже IE6!"
                      –24
                      Хуйню сморозил.
                        +9
                        Не переживайте, отсутствие чувства юмора еще не делает вас потерянным для общества!
                          –10
                          Хреновое чувство юмора тоже.
                      +6
                      На iOS нельзя выделить текст? Весьма удивлен.
                        +2
                        Можно, но цвет текста не инвертируется
                          +9
                          Яблочники сошли с ума. Минусуют за вопрос.
                            –7
                            По вашему как можно скопипастить текст, не выделив его?
                        +1
                        Если нажать кнопку «Reader» в строке адреса текст становится читаемым.
                        +1
                        В 5. может всё-таки return еще нужен?
                          0
                          Спасибо, исправлено.
                            0
                            И в 8.?
                              +1
                              Нет, в 8 не нужен, ибо
                              reaching the } that terminates the main function returns a value of 0.
                              5.1.2.2.3/1.
                            +91
                            Я не знаю Си
                              +50
                              Я не знаю Си, и у меня начал дергаться глаз
                                0
                                Си знает, что я его не знаю и ему по фиг.
                                  +1
                                  «Си» в вашем случает от «Советский интерпретатор»?
                                    0
                                    Юмор про Soviet Russia оказался непосильным?
                                      +2
                                      Неканоничная форма же.
                              +8
                              Хороший пост.
                              Еще бы подобный для «плюсовщиков», которые пихают эти самые плюсы даже туда, где можно средствами обычного С спокойно обойтись.
                                –2
                                >>где можно средствами обычного С спокойно обойтись
                                Спасибо, но после вышеперечисленных граблей, я буду еще больше избегать C и использовать C++.
                                0
                                2.
                                После анализа строки (1) компилятор считает, что x не может быть нулевым указателем и удаляет (2) и (3) как недоступный код (dead code elimination).

                                Какой компилятор удаляет код? Это весьма дивное поведение и относится к компиляторам, а не стандарту.
                                  +8
                                  Компилятор может делать всё, что хочет если это не меняет observable behavior. В строке (1) есть два варианта: или x не нулевой указатель и (2) не сработает, или x нулевой указатель и дальше можно хоть запускать форматирование диска.
                                    +1
                                    тогда правильный ответ будет «я не знаю всех компиляторов», а не «я не знаю Си».
                                      0
                                      Покажите в чём именно нарушение стандарта.
                                        0
                                        в том что ожидается выполнение if (!x) при x == (void*)0, что как раз таки меняет observable behavior
                                          +1
                                          if (!x) не observable. return observable. Поэтому if() не обязан вычисляться. return мы не можем увидеть, потому что для того, чтобы он выполнился, должен был произойти undefined behavior, а дальше может происходить что угодно.
                                            +2
                                            А как предполагается обращаться к нулевой ячейке (если это действительно нужно, в каком-нибудь микропроцессоре или на других компьютерах с 64К памяти)?
                                              +1
                                              Менять null pointer constant — очень и очень сложно, так как очень многое в компиляторах завязано на то, что null pointer constant (который определяется как 0 любого целого типа, приведённый к указателю) имеет битовое значение 0 как указатель. Иначе, к сожалению, средствами языка никак. Но можно определить переменную и затем linker script'ом разместить её по нужному адресу.
                                                0
                                                Верно ли, что компиляторы обязаны на строчку *(int*)0x0000=0xCD выдавать ошибку?
                                                  +1
                                                  Компиляторы — не обязаны, в рантайме падение тоже не гарантируется (undefined behavior — это всё что угодно, в том числе и игнорирование проблемы: как, например, переполнение чисел со знаком игнорируется в большинстве компиляторов Си для x86).
                                                    +3
                                                    А с чего вы взяли, что нулевой указатель это undefined behavior? В эмбеддеде где нету MPU и MMU по нулевому адресу может храниться вектор ресета.
                                                      +5
                                                      ISO/IEC 9899:1999
                                                      6.5.3.2 p4

                                                      > Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer

                                                      Я согласен что по физическому адресу, соответствующему null pointer может что-то лежать и тем, что лежит ниже реализации Си (процессор/ВМ) разрешается читать/писать эту ячейку памяти, но в стандарте это UB. Понимаете, UB может означать «аварийное завершение», а может «какое-то действие». Поэтому на практике у вас даже может получаться записывать *(int *)0 = 42; и считывать обратно, но это всё равно UB.
                                                        0
                                                        Просто я со своей колокольни смотрю, и там где я использую C (это эмбеддед) разыменование нулевого указатель вещь вполне обыденная, и предсказуемая. Хоть и не красивая. Самая часто встречающаяся ошибка это:

                                                        ((void(*)(void))0)();

                                                          +4
                                                          Это предсказуемая вещь пока компилятор не станет умнее и кто-то (из лучших убеждений, стараясь помочь программистам отлавливать UB) не будет вставлять abort() вместо разыменовывания null pointer если это видно статически. И компилятор, что, самое интересное, будет прав, и подавляющее большинство программистов будут только рады такому новому поведению.
                                                            0
                                                            HP aC++ так делает уже давно.
                                                              0
                                                              Ой, ошибся. Он abort() не делает, он просто игнорирует такое разыменование.
                                                                –1
                                                                Это всё же ошибка оптимизатора. UB должно относиться только к тому выражению, где оно используется, а не ко всему коду вообще. В данном случае ваша версия компилятора решила сначала, что *x при x == NULL вызовет pagefault (вы же транслировали для pc-linux?), поэтому можно if выкинуть, а потом она решила выкинуть y, забыв сказать самой себе, что теперь никакого pagefault не будет. То есть, когда abort для x == NULL будет гарантирован if можно и выкинуть, и всё ОК, код будет вести себя так, как ему положено. А тут оптимизатор запутался в гарантиях. Но при чём тут стандарт?
                                                                  +1
                                                                  > UB должно относиться только к тому выражению, где оно используется
                                                                  UB относится ко всему выполнению программы, где имеется UB. Не гарантируется даже что действия до UB будут выполнены корректно. Вся программа с UB не имеет смысла.
                                                                    +1
                                                                    Суровый Вы человек :) Или мы читаем разные стандартны. В моём Draft для C99 написано, что Undefined Behaviour — это ситуация, когда поведение компилятора не определено. И нигде не сказано, что вся программа с UB не имеет смысла. Вам же уже тут приводили пример с доступом по NULL — иногда это часть архитектуры системы, иногда нам необходимо туда писать. Иногда нам нужен aliasing. Просто такие операции будут специфичными для данной версии компилятора/системы и не будут переносимыми. И именно для того, чтобы специально не перечислять все частные случаи и было придумано UB. А совсем не для того, чтобы лишать программы смысла. Это просто свободный такой смысл.

                                                                    Я вот не знаю, каким Вы транслятором транслировали код с этим примером на NULL, но вот, например тот же Clang генерирует для него различный код для kernel-режима трансляции и для режима по-умолчанию.

                                                                    То есть, всё не так уж и сурово :) Скорее, свободно. Чем Си и полезен.
                                                                      +1
                                                                      > Undefined Behaviour — это ситуация, когда поведение компилятора не определено. И нигде не сказано, что вся программа с UB не имеет смысла.

                                                                      Программа не имеет смысла без всего этого контекста: определённый ЦП, конкретная версия тулчейна, конкретная версия ОС и может быть даже дата и время.

                                                                      > Я вот не знаю, каким Вы транслятором транслировали код с этим примером на NULL
                                                                      pastebin.com/MpqKKpyU Вот полный код. gcc 4.6 -O2 убирает проверку на нулевой указатель. Это широко известный (в узких кругах) баг в ядре.
                                                                –6
                                                                госпадя
                                                                  –6
                                                                  госпадя
                                                                    +1
                                                                    В MSXDOS это был выход из программы. Где тут ошибка?
                                                                      +1
                                                                      А в эмбеддеде так часто делают перезагрузку, но ведь это не перезагрузка, а вызов функции по адресу 0, с проталкиванием в стек адреса возврата. Если так часто перезагружать, долго работающую систему, то она зависнет от переполнения стека.
                                                                        +2
                                                                        Понятно. В операционке первой командой, выполняющейся после JP 0000 было LD SP,(0006) — так что там стек не переполнялся.
                                                                          +2
                                                                          Не волнуйтесь вы так, стековый указатель переинициализируется заново :)
                                                                            0
                                                                            программа на старте настраивает стек заново, то есть что там запушили неважно — стек переустановится заново
                                                +6
                                                Хорошая подача материала. Нифига не смыслю в С, но дождусь полуночи и плюсану )
                                                  +4
                                                  Еще эту статью можно использовать, как аргумент против Си в этом опросе.
                                                    +10
                                                    Она и является расширенным вариантом моего комментария habrahabr.ru/blogs/study/136272/#comment_4532332
                                                      0
                                                      Тогда всё сходится)
                                                      +5
                                                      Перешел по ссылке, проголосовал, увидел результат. У меня просто вырвалось: — ЧТО?!!!
                                                        +1
                                                        Тоже сходил по ссылке, и, вы не поверите… то же самое сказал непроизвольно)
                                                        Интересно почему народ считает что си нужно давать в школе?..
                                                        ЗЫ: не забываем, что из класса примерно 10% (2-3 человека) пойдут по ИТ дорожке…
                                                      +5
                                                      Думаю, надо бы уточнить, что в 1-м пункте i — глобальная переменная в пределах модуля. Если такое написать внутри функции, то это будет ошибочный код из-за редекларации.
                                                        0
                                                        Сделал уточнение.
                                                        +4
                                                        Славные грабельки :-)
                                                        «За свободу надо платить отвественностью» вкупе с «Незнание не освобождает от ответственности».

                                                        Собсна меня в си практически всё устраивает, кроме строк. Как говорил Спольски «история развития си++ может быть проиллюстрирована исторей борьбы с изначально идиотскими строками си».

                                                        Особенно чудесно заморочен Борман — есть TString и есть AnsiString, и для оперирования ими приходится извращаться.

                                                        Эх, если бы не строки… С остальной памятью я уж как нть разберусь.
                                                          +1
                                                          А чем не устраивают строки в Си?
                                                            +8
                                                            Они прекрасны, просты и понятны до тех пор, пока не начинаешь с ними работать.
                                                            russian.joelonsoftware.com/Articles/BacktoBasics.html
                                                              +4
                                                              Во первых — в С строк нет.
                                                              Во вторых — схема работы со строками в С оптимальна!
                                                              В третьих — язык, разработанный практиком отличается от языка, разработанного теоретиком наличием встроенного типа 'string'.

                                                              Обработка строки внутри довольно сложный процесс. Если его не сделать, хотя-бы визуально, немного более сложным, то это приведёт к огромной потери производительности в большом проекте. В статье Спольски об этом упомянуто, но не написано, что при использовании встроенных типов строк всё, что он рассказал про malloc() будет применяться в неявном виде.
                                                                +4
                                                                Вы правы: в строгом смысле строки в си отсутствуют.

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

                                                                И если замена килограммов на double имеет некоторые грабли (например в виде отрицательных значений массы), то замена строк на char * имеет массовые и массивные грабли в силу самой попытки использовать char * (или char []).

                                                                На месте авторов языков я бы ввёл два строковых типа — короткие в 255 символов без гемора с malloc и длинные с таковым. Плюс механизмы взаимного перевода между этими типами с соответствующими проверками.

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

                                                                P.S. ладно, для денег не требую вводить отдельный вещественный тип. Хотя тоже неплохо бы ;-)
                                                                  +1
                                                                  А то мы уже к коболу приближаемся
                                                                  +1
                                                                  Насколько я понимаю, речь идет о том, что не надо пытаться самому реализовывать std::string (C++) и уже тем более String (Java). Потому что се уже сделано до тебя, причем сделано прилично. Вроде.
                                                                    0
                                                                    Мы говорим только про Си
                                                                      0
                                                                      Мы говорим про С.
                                                                      Но если взять С++ и стандарт разработанный теоретиками, в виде std::string, то мы увидим, что в нём отсутствует возможность сравнения строк без учёта регистра, что критично для строк из набора ASCII (латинских). Теоретики, мать их, кампухтер сайенс, мать её.
                                                                        0
                                                                        За это мне больше Qt шные строки нравятся, их как раз практики делали :)
                                                                          0
                                                                          Я, вообще-то, очень тупой, и поэтому использую Java. Но, скаже честно — Qt — первая библиотека C++, которая мне нравится. То есть там так много общих концепций с Java, что создается обманчивое впечатление, что я смогу легко перейти с одного на другое )
                                                                          +1
                                                                          В стандарте С тоже нет сравнения строк без учета регистра. strcasecmp это BSD или POSIX, что вам больше нравится, но не С99.
                                                                        +2
                                                                        > Во вторых — схема работы со строками в С оптимальна!

                                                                        До тех пор, пока мы не вспоминаем, что есть Unicode
                                                                          0
                                                                          и что с юникодом не так?
                                                                            0
                                                                            Допустим, хранить UTF-8 можно и в Сишных char-ах. А вы попробуйте вывести на микроконтроллере на 40-сегментный индикатор. А Си всё больше используется там, ниже чем айфон и дот нет.
                                                                              0
                                                                              если он не поддерживает национальные символы то ничего не получится это к гадалке не ходи. если речь идет о английских символах то они кодируются в utf-8 точто так же как и в 7-bit ASCII, т.е. занимают 1 байт.
                                                                              просто я как раз занимался приведением одной 8-битной программы к работе с utf-8… там всё в принципе тривиально. нужно читать из массива пока не встретится начало следующего символа а это начало определено стандартом.
                                                                              0
                                                                              То, что юникод не однобайтовая кодировка. Все, приплыли со стандартным сишным представлением строки.
                                                                                +1
                                                                                Не приплыли ни разу. В char* можно отлично хранить UTF8 строки, т.к. ни один из code points в UTF8 не может содержать 0x00.
                                                                                  0
                                                                                  Угу. И как с ними потом работать?
                                                                                    0
                                                                                    Задавайте более конкретные вопросы.
                                                                                      0
                                                                                      Длина в символах, поиск подстроки, разбиение на символы — короче все, что связано с символами.
                                                                                        0
                                                                                        Забыл главное — сравнение и сортировка (которая завязана на сравнение).
                                                                                          0
                                                                                          Длина в символах — зачем она нужна? В байтах — понятно, чтобы, например, для копирования нужное количество памяти выделить, а в символах зачем?

                                                                                          Поиск подстроки будет работать, ему всё равно в каких кодировках строки.

                                                                                          Разбиение на символы — зачем? Каждый символ разным цветом печатать что-ли? Практика показывает, что прямой доступ к символам строки не нужен. А последовательный доступ в случае UTF8 пишется достаточно просто.

                                                                                          Сравнение с учетом локали — ну так эта задача решается из коробки по-моему только для Latin1, все равно используются какие-то библиотеки, которые с большой вероятностью умеют и UTF8.
                                                                                            0
                                                                                            > а в символах зачем?

                                                                                            Банально вывести количество отсавшихся символов в клиенте для твиттера :-\

                                                                                            > Поиск подстроки будет работать, ему всё равно в каких кодировках строки.

                                                                                            Не будет. Особенно case-insensitive поиск. Потому что SS и ß — это одна буква.

                                                                                            Не говоря уже о том, что ü и комбинация знаков ¨ и u — это тоже одна буква.

                                                                                            > Разбиение на символы — зачем?

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

                                                                                            > А последовательный доступ в случае UTF8 пишется достаточно просто.

                                                                                            Не надо рассказывать сказки.

                                                                                            > Сравнение с учетом локали — ну так эта задача решается из коробки по-моему только для Latin1

                                                                                            Именно.

                                                                                            Вы получили ответ на вопрос «и что с юникодом не так?»
                                                                                              0
                                                                                              Не надо рассказывать сказки.

                                                                                              Ок, вот вам и подсчет символов и итерирование по UTF8 строке, ничего сложного там нет.

                                                                                              Вы получили ответ на вопрос «и что с юникодом не так?»
                                                                                              Я получил ответ на вопрос «что не так с мультибайтовыми кодировками?» :) А однобайтные кодировки своё уже отжили. Более того, не забывайте, что всякие там китайские и японские локальные кодировки — они уже не однобайтовые, а multibyte и обладают теми же проблемами.

                                                                                              Если вы хотите, чтобы у вас символ был ровно N байт — вам нужен либо UCS2 (который не покрывает весь Unicode), либо UTF32 (который оверхед), потому что в UTF-16 символ может занимать как 2 так и 4 байта.
                                                                                                0
                                                                                                > Ок, вот вам и подсчет символов и итерирование по UTF8 строке, ничего сложного там нет.

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

                                                                                                > Я получил ответ на вопрос «что не так с мультибайтовыми кодировками?» :) А однобайтные кодировки своё уже отжили. Более того, не забывайте, что всякие там китайские и японские локальные кодировки — они уже не однобайтовые, а multibyte и обладают теми же проблемами.

                                                                                                Теперь возвращаемся к моему изначальному заявлению про юникод и ваш вопрос «что не так с юникодом»? Я ответил на этот вопрос?
                                                                                                  0
                                                                                                  Ну так если в стандартной поставке есть только работа с однобайтовыми кодировками — продолжать сидеть в 90-х из-за этого? Ну то есть проблемы, которые вы описали присущи не только UTF8 (не юникоду), а любой мультибайтной кодировке, без которых вы всё равно в 2012 году не обойдётесь. Ну то есть по-вашему получается, что юникод плох тем, что с ним нельзя работать используя старые добрые str… функции, но альтернативы то нет.

                                                                                                  Что не так с юникодом вообще некорректная постановка вопроса по-моему. Мы сейчас обсуждаем «что не так с UTF-8?». И я как раз пытаюсь сказать о том, что с точки зрения работы с сишными строками с UTF-8 работать удобнее, чем с UTF-16, например.

                                                                                                  Теперь возвращаемся к моему изначальному заявлению про юникод и ваш вопрос «что не так с юникодом»? Я ответил на этот вопрос?
                                                                                                  Вопрос был не мой, но мне кажется, я понял вашу мысль.
                                                                                                    0
                                                                                                    > Вопрос был не мой, но мне кажется, я понял вашу мысль.

                                                                                                    Претензии снимаются :)
                                                                                                    +2
                                                                                                    Отвечу более развёрнуто, в прошлый раз было мало времени.

                                                                                                    Итерирование по символам UTF8 строки — функция в 7 строк. Подсчет длины в символах реализуется через неё же в 5 строк. Что тут сложного? Алгоритмическая сложность подсчета количества символов в строке — как и у strlen — линейная, дополнительного оверхеда не вносится.

                                                                                                    По поводу поиска подстроки. Как проблема
                                                                                                    Потому что SS и ß — это одна буква. Не говоря уже о том, что ü и комбинация знаков ¨ и u — это тоже одна буква.
                                                                                                    решается в случае однобайтовых кодировок?

                                                                                                    То есть в случае, когда мы имеем дело с чем-либо сложнее Latin1, дополнительные действия совершать всё равно приходится, и эти дополнительные действия для UTF8 вовсе не такие уж сложные, а даже наоборот.

                                                                                                    Зато есть определенные задачи, где переход от однобайтовых кодировок к UTF8 практически безболезненный. Вот например файловая система. Ей не надо знать, сколько в имени файла символов. Ей не надо по ним итерироваться. Она просто работает с именами файлов как с 0-терминированными строками. Раньше эти строки были в локальной однобайтовой кодировке. Если мы захотим складывать туда UTF8 строки всё продолжит работать. Потому что сравнение строк на идентичность будет работать, копироваться строки будут так же отлично как и раньше, и так далее, но при этом мы не будем ограничены 255 символами в имени файла, а сможем использовать весь Unicode. То есть, возвращаясь к «приплыли со стандартным сишным представлением строки» — в данном примере не приплыли. И юникод поддержали и код, рассчитанный на стандартные сишные строки, продолжает работать.
                                                                                                      0
                                                                                                      > Итерирование по символам UTF8 строки — функция в 7 строк. Подсчет длины в символах реализуется через неё же в 5 строк. Что тут сложного?

                                                                                                      В том, что на каждый чих в С надо реализовывать собственные функции, поому что родные строки умеют работать только с однобайтовыми кодировками.

                                                                                                      > По поводу поиска подстроки. Как проблема SS и ß, ü и комбинации знаков ¨ и u решается в случае однобайтовых кодировок?

                                                                                                      В том-то и дело, что никак :)

                                                                                                      > и так далее, но при этом мы не будем ограничены 255 символами в имени файла, а сможем использовать весь Unicode.

                                                                                                      Будем. Причем с глюками. Если в файловой системе хоть где-то стоит if(strlen(filename) > 255) strncpy(что-то-там), при условии, что и strlen и strncpy однобайтовые, то все, приплыли. По все тем же причинам, что и выше ;)

                                                                                                      Единственное, то нас спасает в этом примере, это то, что основные ОСи давно имеют внутреннее представление в многобайтовых кодировках.

                                                                                                        0
                                                                                                        Будем. Причем с глюками. Если в файловой системе хоть где-то стоит if(strlen(filename) > 255) strncpy(что-то-там), при условии, что и strlen и strncpy однобайтовые, то все, приплыли.
                                                                                                        strlen(filename)
                                                                                                          +2
                                                                                                          Долбанный ктрл-ентер.

                                                                                                          strlen(filename) вернет длину UTF-8 строки в байтах. Потом strncpy правильно скопирует эту строку (потому что UTF-8 строка не содержит внутри '\0', а длину (в байтах) мы только что правильно измерили. Где ж мы приплыли то? Функциям strlen, strcat, strcpy, strncpy и прочим всё равно, Latin1 у нас в char* или UTF8, они будут правильно работать и с тем и с другим.
                                                                                                            0
                                                                                                            > strlen(filename) вернет длину UTF-8 строки в байтах.

                                                                                                            Правильно. А у нас какое требование? ЧТобы длина строки была не больше 255 символов. ;)
                                                                                                              0
                                                                                                              С чего это у нас такое требование?
                                                                                                                0
                                                                                                                Ни или не 25 символов. Любое другое число. Обычно имя файла в ОСи не может быть безграничным
                                                                                                                  +1
                                                                                                                  И чем по-вашему это обусловлено? Не тем ли, что в формате служебных данных под это имя отводится определённое количество байт? Какая в общем-то файловой системе разница, сколько в имени символов?
                                                                                                                    0
                                                                                                                    Эо может быть обусловлено самыми разными причинами. В том числе и указанной причиной тоже.
                                                                                                                      0
                                                                                                                      Отвечу более развернуто. Если в файловой системе есть хоть какое-то ограничение на длину имени файла, то в случае с С, strlen и длиной в байтах мы приплыли. Потому что если последние два байта — это знаки «¨» и «u», и u не влезает в длину, то при обрезании мы получим в имени файла мусор. Хорошо, если это будет ¨, а не неотображаемый первый байт из двухбайтовой буквы.
                                                                                                                        0
                                                                                                                        Обрезание строк должно быть на уровне клиента файловой системы. API ФС должно не обрезать длинные строки, а возвращать ошибку типа некорректный аргумент.
                                                                                                                          0
                                                                                                                          Это уже философский вопрос о том, где это должно быть реализовано ;)
                                                                                                                0
                                                                                                                я правильно понимаю что вы не в теории столкнулись с проблемой а на практике? что именно у вас вызывает затруднения?
                                                                                                                  0
                                                                                                                  С какой проблемой? Проблемой, что в С практически нет нормальных способов работы с многобайтными кодировками и, в частности, с Unicode?
                                                                                                                    0
                                                                                                                    у вас есть проблемы с юникодом или нет?
                                                                                                                      0
                                                                                                                      У меня были проблемы с Юникодом. Более того, в С/С++ их не может не быть по определению. Спас ICU
                                                                                                              0
                                                                                                              посмотрите как в mc реалисована работа с utf-8 там есть и поис и подсчёт и сортировка, ничего сложного там нет… утвержтаю это т.к. собственно кое чего реализовывал в этом плане…
                                                                                                                0
                                                                                                                А причем тут mc?

                                                                                                                > ничего сложного там нет… утвержтаю это т.к. собственно кое чего реализовывал в этом плане…

                                                                                                                Расскажите мне про case-insensitive сортировку для немецкого, например и про посчет символов в нем же.

                                                                                                                Напомню:
                                                                                                                SS и ß — это одна буква. Не говоря уже о том, что ü и комбинация знаков ¨ и u — это тоже одна буква.
                                                                                                                  0
                                                                                                                  я в курне про немецкий, mc при том что там это организовано и реализовано.
                                                                                                                    0
                                                                                                                    То, что организовать и реализовать можно, это и так понятно. Хотя бы тот же ICU. Вспоминаем контекст подветки.
                                                                                                            0
                                                                                                            А чего это нет в стандартной поставке? В поставке есть wchar, пожалуйста, используйте. Будет вам перекодирование на лету в соответствии с локалью.
                                                                                                              +1
                                                                                                              «The width of wchar_t is compiler-specific and can be as small as 8 bits. Consequently, programs that need to be portable across any C or C++ compiler should not use wchar_t for storing Unicode text. The wchar_t type is intended for storing compiler-defined wide characters, which may be Unicode characters in some compilers.»

                                                                                                              ;)
                                                                                        0
                                                                                        c utf-16 и 32 всё элементарно, с utf-8 есть нюансы но не более того.
                                                                                          +3
                                                                                          С utf-16 ничего не просто. Просто только с UCS2. Некоторые символы в представлении UTF-16 могут занимать больше 16 бит. Так же не следует забывать о UTF-16LE и UTF-16ВE.
                                                                                            0
                                                                                            >т.к. символы Unicode после кода 0x10000 используются крайне редко. ©

                                                                                            но в любом случае забираю слова про «элементано» назад, (вы меня уделали:) ) да я имел ввиду UCS2. однако же и сказать что уж сильно сложно тоже нельзя…
                                                                                        0
                                                                                        О да, нуль-терминированные строки это так оптимально, так оптимально — не зря их кличут «самым дорогим решением в ИТ»
                                                                                          –1
                                                                                          Нормальное решение. И это значительно, на порядки лучшее решение, чем создавать новые строки динамически выделяя память на каждый чих, что и происходит при выделенном базовом типе string или при использовании класса std::string или его самописного аналога. Здесь постоянный проход asciiz строки для измерения размера как-то теряется в безднах системных вызовов.
                                                                                            +1
                                                                                            Нормальным оно является только в контексте создания Си — Керниган и Ричи делали инструмент для себя, а не ультракроссплатформенный системный язык, который в результате получился.
                                                                                            Динамическое выделение памяти к нуль-терминированности отношения не имеет, заниматься им при наличии выделенного типа или класса не обязательно, хотя и желательно-статические буфера фиксированного размера чреваты неожиданными повреждениями памяти, а на стеке — еще и эксплойтами.
                                                                                            А вот постоянный проход по всей строке и исключение нуля остаются тяжким бременем всегда, везде и повсюду. На уровне системных вызовов в том числе.
                                                                                              –1
                                                                                              Ты не догоняешь.
                                                                                              В результате того, что сложный, я бы сказал — сложнейший тип выделен в «простые» программист не видит динамического выделения памяти. Принципиально, концептуально — не видит. А динамическое выделение памяти, между прочим, к рантайму языка и его библиотеке отношения не имеет, оно имеет отношение к операционной системе и платформе.

                                                                                              И теперь сравни это с «постоянным проходом» и исключением нуля. Да хер с ними, это непринципиально!

                                                                                              Ну, и, дальше.

                                                                                              После того, как сложнейший тип обёрнут в простой, программист лишается возможности оперировать с ним как с массивом простых типов. А это чревато. Идиотские конструкции 'left', 'right', 'mid' проблем только добавляют и вынуждают неявно использовать динамическое выделение памяти.

                                                                                              Вообще, эта надуманная проблема сишных строк решается довольно просто:

                                                                                              struct string {
                                                                                              short size;
                                                                                              char content[];
                                                                                              };

                                                                                              … и вся хрень должна иметь размер sizeof(short)+sizeof content[];

                                                                                              а нахера, собственно?
                                                                                                +1
                                                                                                >В результате того, что сложный, я бы сказал — сложнейший тип выделен в «простые» программист не видит динамического выделения памяти. Принципиально, концептуально — не видит.
                                                                                                В результате использования си вместо ассемблера программист не видит отдельных машинных команд. Принципиально, концептуально не видит. Но надо же — именно это позволило писать на си лучше, чем на ассемблере.

                                                                                                > А динамическое выделение памяти, между прочим, к рантайму языка и его библиотеке отношения не имеет, оно имеет отношение к операционной системе и платформе.
                                                                                                Ага, как же, своей кучи помимо системной не бывает :)

                                                                                                > И теперь сравни это с «постоянным проходом» и исключением нуля. Да хер с ними, это непринципиально!
                                                                                                Это принципиально. Потому что почти любая «платформа» написана таки на си. Со всеми вытекающими.

                                                                                                >После того, как сложнейший тип обёрнут в простой, программист лишается возможности оперировать с ним как с массивом простых типов.
                                                                                                Расскажите это дельфистам.

                                                                                                >Вообще, эта надуманная проблема сишных строк решается довольно просто:
                                                                                                Это «просто» означает порчу памяти и эксплойты.

                                                                                                >а нахера, собственно?
                                                                                                Потому что это дешевле, чем нуль-терминированные строки.
                                                                                                  –1
                                                                                                  >Но надо же — именно это позволило писать на си лучше, чем на ассемблере.
                                                                                                  Не лучше, а быстрее.

                                                                                                  > Ага, как же, своей кучи помимо системной не бывает :)
                                                                                                  Бывает, но своя куча стандартной библиотекой не поддерживается.

                                                                                                  >Это принципиально. Потому что почти любая «платформа» написана таки на си. Со всеми вытекающими.
                                                                                                  И что?

                                                                                                  >Расскажите это дельфистам.
                                                                                                  Кому?

                                                                                                  >Это «просто» означает порчу памяти и эксплойты.
                                                                                                  Да ну?

                                                                                                  > Потому что это дешевле, чем нуль-терминированные строки.
                                                                                                  Где ты увидел дешевизну?
                                                                                                    +1
                                                                                                    > Не лучше, а быстрее.
                                                                                                    Лучше, качественно лучше. Собственно триумфальное шествие юникса стало следствием именно замены ассемблера на си в качестве основного языка системного программирования.

                                                                                                    >Бывает, но своя куча стандартной библиотекой не поддерживается.
                                                                                                    Как раз стандартная библиотека си, наряду с модульностью на включаемых файлах — его слабое место.

                                                                                                    >И что?
                                                                                                    И то. ASCIIZ-строки достанут везде, даже если избегать их как огня: через стандартную библиотеку, через сторонние библиотеки, через системные вызовы, через требования портируемости…

                                                                                                    >Кому?
                                                                                                    Есть такие языки, для которых «сложнейший объект» является с одной стороны простым, с другой — вполне совместим с массивом символов.

                                                                                                    >Да ну?
                                                                                                    Ну да. В любой нормальной книжке оно есть.

                                                                                                    >Где ты увидел дешевизну?
                                                                                                    В совокупной стоимости использования. Получение размера как O(1) и отсутствие запрещенных элементов имеет колоссальный мультипликатор полезности за счет использования на всех уровнях, от драйверов до скриптов.
                                                                                                      –1
                                                                                                      >Лучше, качественно лучше. Собственно триумфальное шествие юникса >стало следствием именно замены ассемблера на си в качестве основного >языка системного программирования.
                                                                                                      Триумфальное шествие юникса произошло потому, что он попал в лапы студентов в 70е, и их на нём учили. А ещё в нём есть юзнет.

                                                                                                      >Как раз стандартная библиотека си, наряду с модульностью на включаемых >файлах — его слабое место.
                                                                                                      Модульность! На включаемых файлах!

                                                                                                      >Есть такие языки, для которых «сложнейший объект» является с одной стороны >простым, с другой — вполне совместим с массивом символов.
                                                                                                      Вот я и говорю — за это надо бить.

                                                                                                      >В совокупной стоимости использования. Получение размера как O(1) и >отсутствие запрещенных элементов имеет колоссальный мультипликатор >полезности за счет использования на всех уровнях, от драйверов до
                                                                                                      >скриптов.
                                                                                                      Кошмар.
                                                                                                      Единственная область применения, где имеет смысл заранее измеренная длина это приём/передача данных.
                                                                                                        +1
                                                                                                        > Триумфальное шествие юникса произошло потому, что он попал в лапы студентов в 70е, и их на нём учили. А ещё в нём есть юзнет.
                                                                                                        Очевидно, что с системой на языке ассемблера такого случиться не могло по определению. А си оказался «студентопригоден» как для учебы, так и для портирования ОС на другие машины, при этом ничего не потеряв для профи.

                                                                                                        > Кошмар. Единственная область применения, где имеет смысл заранее измеренная длина это приём/передача данных.
                                                                                                        В остальных случаях тормозная конкатенация и нулевые символы нисколько не мешают, ага, как же :)
                                                                                                          0
                                                                                                          Конкатенация будет тормозна в любом случае.
                                                                                                          А нулевые символы вообще ничему не мешают.
                                                                                                            0
                                                                                                            По обоим пунктам львиная доля разработчиков с вами не согласится.
                                                                                  +1
                                                                                  Понравилось. Напоминает о том, что поведение любимого компилятора — не стандарт. А с (6) сам в своё время столкнулся.
                                                                                    +2
                                                                                    Скорее, undefined behavior в любимом компиляторе слишком часто совпадает с желаемым.
                                                                                    +2
                                                                                    4 — скорее знание особенностей вещественных чисел, потому что на другой платформе это может быть не так.
                                                                                    3, 12 — не сообразил.
                                                                                    6 — слишком очевидно для данного списка.
                                                                                    Помнится меня в своё время поразило, что код
                                                                                    #include <stdio.h>
                                                                                    
                                                                                    int main()
                                                                                    {
                                                                                        printf( "%c\n", 4["abcdefgh"] );
                                                                                        return 0;
                                                                                    }
                                                                                    

                                                                                    корректен.
                                                                                      0
                                                                                      4. IEEE 754 настолько распространён, что сложно назвать устройство где какой-то другой floating point.

                                                                                      3. Если yp = zp, то выражение *xp + *yp меняет значение после первой итерации и поэтому его нельзя вынести за цикл.

                                                                                      12. INT_MIN / -1 должно быть равно -INT_MIN, но это больше INT_MAX, поэтому получаем переполнение числа со знаком.
                                                                                        +1
                                                                                        3, 12 — имелось в виду не сообразил до тех пор пока не прочитал белый текст :) Остальные понял сам без объяснений — когда-то копался в неопределённом поведении C/C++.
                                                                                        4 — в принципе оно конечно так, но всё же проблема не Си-специфична, а скажем так, имеет пересечение, — одновременно шире (проявляется и в других языках в т.ч. ассемблере) и уже (может не проявиться на каком-либо софтверной реализации floating point, например).
                                                                                        • UFO just landed and posted this here
                                                                                            0
                                                                                            О_О, это что за процессор?
                                                                                              +2
                                                                                              есть множество процессоров где sizeof(char)==sizeof(int)==sizeof(long)==1 и при этом char реально занимет 16 либо 32 бит. Обычно это всякие DSP.
                                                                                                0
                                                                                                Стоп стоп стоп. Вы пишете sizeof(char) == 1, потом что char занимает 16-32 бита.
                                                                                                Либо вы имели ввиду sizeof(int)==sizeof(long) ==1, а чар занимает 2-4 байта.
                                                                                                Либо вы имеете ввиду другой размер байта
                                                                                                (пухнет голова)
                                                                                                  0
                                                                                                  #define CHAR_BIT 32, всё просто.
                                                                                                    +1
                                                                                                    Ну если байтом называть не 8 бит, а минимально адресуему единицу информации (то, на что может указывать указатель), то да — байт может занимать и 16 и 32 бита.
                                                                                                      0
                                                                                                      да, хочу напомнить что стандарт C требует только одного: что бы sizeof(char)<=sizeof(int)<=sizeof(long) и т.д…
                                                                                                      +3
                                                                                                      sizeof(char) == sizeof(signed char) == sizeof(unsigned char) == 1 всегда, в не зависимости от того, сколько в нём бит.
                                                                                                        +3
                                                                                                        6.5.3.4/3 в C99 если что.
                                                                                                –1
                                                                                                INT_MIN / -1 НЕ должно быть равно -INT_MIN. Ошибочно думать, что int представляет целые числа, точно так же, как ошибочно думать, что double представляет числа действительные. Тут совсем другая математика. И когда мы пишем x/-1 компилятор должен для нас сгенерировать соответствующий код, но он совсем не должен нам гарантировать получение каких-то результатов. UB в данном случае прописано в стандартне не для компилятора, а для программистов.
                                                                                                  +1
                                                                                                  > INT_MIN / -1 НЕ должно быть равно -INT_MIN
                                                                                                  Я имел ввиду алгебру. В алгебре это действительно равно.
                                                                                                    0
                                                                                                    Алгебра — тоже штука тонкая. Не во всякой алгебре x / -1 = -x :) Просто мой вопрос-то вот в чём: зачем так педалировать эти моменты? Любой нормальный программист, который пишет на Си вполне себе в курсе, что он не работает в кольце целых чисел. Если он думает иначе, значет, это программист, ну, на Clojure, например.

                                                                                                    Какие проблемы-то? То, что div можно выполнять по-разному — так это известные истины. В чём Вы видите тут сложности и трудности связанные с Си?
                                                                                                      0
                                                                                                      В других языках (тот же clojure, python) все эти тонкости успешно скрывают от программиста. А в си повсюду разбросаны UB.
                                                                                                        +1
                                                                                                        Успешно ли? Например, в Clojure, конечно, скрыто, что у числа длина конечная за счёт длинной арифметики, но потом программеры долго удивляются, а чего это у них простой алгоритм преобразования фурье всю память в системе пожирает. Как бы, закон сохранения косяков в природе: в одном месте подпилили, так оно в другом вылезет. Невозможно скрыть то, что программа для компьютера работает на компьютере с его ограничениями, вопрос лишь в том, как эти ограничения проявяться.
                                                                                                        +1
                                                                                                        То, что код
                                                                                                        int x;

                                                                                                        if(abs(x)<10){

                                                                                                        }
                                                                                                        может работать не так, как ожидалось, для меня было большой и неприятной неожиданностью.
                                                                                                  +1
                                                                                                  Действительно, занятный пример. А как объясняется такое поведение? Ведь, по сути, мы имеем аналог
                                                                                                  char s[] = "abcdefgh";
                                                                                                  printf( "%c\n", s[4] );

                                                                                                  ?
                                                                                                    +1
                                                                                                    Да, и можно написать 4[s] — это тоже будет корректно. Объясняется очень просто: x[y] является эквивалентом *(x+y), и порядок действий не важен.
                                                                                                      +2
                                                                                                      Спасибо большое ) Ваше объяснение позволило посмотреть на код под другим углом зрения. Очень познавательно, благодарю.
                                                                                                  +7
                                                                                                  Для PHP-кодеров тоже актуальна фраза «Я не знаю PHP».
                                                                                                    +2
                                                                                                    Мне кажется, для большинства языков такое высказывание верно. С PHP точно беда. Когда совсем не знаешь его и смотришь на код быдлопрограммера, с трудом представляешь, на сколько можно так не знать :)
                                                                                                    0
                                                                                                    Так что выводит 8?
                                                                                                    1 2 10?
                                                                                                      0
                                                                                                      Да, 10 2 10. Добавил в топик.
                                                                                                      0
                                                                                                      Все таки ошибся про прохождении 7 задания.
                                                                                                        0
                                                                                                        По поводу 5: вторая ошибка в том, что функция не принимает на вход параметр длины стороки, предполагая, что входная строка будет NULL-terminated:
                                                                                                        const char str[] = {'a', 'b'};
                                                                                                        int len = my_strlen(str);
                                                                                                          0
                                                                                                          Имелась ввиду null-terminated строка. Сделал уточнение.
                                                                                                            +3
                                                                                                            Передавать длину строки в функцию вычисления длины строки?
                                                                                                              +3
                                                                                                              Хм. Интересно получилось…
                                                                                                                0
                                                                                                                В любом случае длина верно будет вычислена только для null-terminated строк.
                                                                                                                +1
                                                                                                                А, вдруг?
                                                                                                              +3
                                                                                                              Хех, я почти знаю Си :)

                                                                                                              Второй пример смутил, хотя бы потому что подобные оптимизации всегда нужно держать в голове и проверять что там генерируется. Собственно любой человек который писал защиту он нелегального копирования на языках высокого уровня с этим точно сталкивался. Я даже затрудняюсь сказать сколько раз компилятор меня удивлял в этом отношении. А так — это действительно зависит от компилятора, специально дополз до компьютера проверить (clang) — заведена локальная переменная, есть получение значения, сравнение и корректные джапмы.
                                                                                                                +11
                                                                                                                По-моему это не примеры на знание Си, а примеры «как не надо писать код». Особенно п.7 — убейся об стену тот, кто такое напишет в программе.
                                                                                                                  0
                                                                                                                  Это минимальные wtf-примеы. Все примеры взяты из реальных программ, просто уменьшены, сложные типы заменены на встроенные и так далее.
                                                                                                                    +4
                                                                                                                    Я понимаю, что это все из реальных примеров. Но тут есть примеры которые:
                                                                                                                    — я знаю;
                                                                                                                    — я знаю, но толку от знания никакого (п.2 только по невнимательности мог быть написан, т.е. не зависит знал человек о том как эта фигня будет работать или нет);
                                                                                                                    — я не знаю, но никогда так не напишу (п.7-8)

                                                                                                                    Выходит я не знаю С, но умею писать неплохие программы на С :)
                                                                                                                      0
                                                                                                                      2. Представьте себе, что это критически важная функция, например в ядре, пусть если bar() выполняется, то это уязвимость. Вы исправляете баг, который случайно нашли «глазами». Если вы не выпустите security bulletin, то вы «тихо» закрыли уязвимость и поэтому кто-то мог не пропатчить свою систему.

                                                                                                                      7. Вы никогда не приводите указатели в Си? Всегда дотошно проверяете, что не нарушаете алиасинг?

                                                                                                                      8. Запятая широко используется в макросах. Код в примере вполне может быть результатом препроцессирования нескольких макросов.
                                                                                                                        0
                                                                                                                        fix: если bar() выполняется при нулевом x.
                                                                                                                          0
                                                                                                                          Конечно же привожу указатели к другим типам. Но передавать один и тот же указатель в качестве аргументов ф-ии, которая принимает 2 не константных указателя (т.е. меняет оба?) да еще и разных типов — я бы очень задумался.
                                                                                                                            0
                                                                                                                            Это пример. Упрощённый пример. Эти указатели не обязательно передавать в функцию, их можно получать через вызов другой функции, или найти в какой-то структуре данных или ещё как-нибудь.
                                                                                                                              +1
                                                                                                                              А если я напишу
                                                                                                                              int x=300;
                                                                                                                              printf("%d ",x);
                                                                                                                              ((char*)&x)[1]=0;
                                                                                                                              printf("%d\n",x);
                                                                                                                              то тоже будет шанс увидеть «300 300»?
                                                                                                                                0
                                                                                                                                Для char* есть исключение, он может алиасить любой указатель.
                                                                                                                        +2
                                                                                                                        то что это wtf и не оспаривается.

                                                                                                                        я ответил «некорректный код» на первый вопрос.
                                                                                                                        и почему то не считаю что ответил неправильно.

                                                                                                                        тут же попробовал скомпилить это, visual studio выдал error C2086: redefinition.
                                                                                                                        можно попробовать и на других компиляторах, но даже если это где то и заработает, меня не убедит в том, что это не гогнокод и что это корректно.
                                                                                                                          +1
                                                                                                                          А на два цикла
                                                                                                                          for(int i = 0; i < 10; i++) {
                                                                                                                          //smth
                                                                                                                          }
                                                                                                                          for(int i = 0; i < 10; i++) {
                                                                                                                          //smth
                                                                                                                          }

                                                                                                                          вижуал студия уже перестала ругаться, что redefinition?
                                                                                                                            0
                                                                                                                            Последняя версия студии, на которой переменная, объявленная в заголовке цикла считалась глобальной — VC6.
                                                                                                                            А в VS2005 этой проблемы уже не было.
                                                                                                                              0
                                                                                                                              проверил. не ругается. не встречал такой буг.
                                                                                                                              +1
                                                                                                                              Вы пробовали код на Си или C++ писать? Код
                                                                                                                              #include <stdio.h>                                                                                                                                                   
                                                                                                                                                                                                                                                                                                   
                                                                                                                              int i;                                                                                                                                                               
                                                                                                                              int i = 0;                                                                                                                                                           
                                                                                                                                                                                                                                                                                                   
                                                                                                                              int main()                                                                                                                                                           
                                                                                                                              {
                                                                                                                                  return 0;
                                                                                                                              }

                                                                                                                              нормален с точки зрения Си, но не С++.
                                                                                                                                –1
                                                                                                                                если только воспринимать это как баг, который был в компиляторах си, и пофиксили по пути к внедрению с++. это не нормально.
                                                                                                                                  +1
                                                                                                                                  Это не баг. Это особенность. А то так можно и такое:
                                                                                                                                  int main(argc,argv)
                                                                                                                                  int argc;
                                                                                                                                  const char** argv;
                                                                                                                                  {
                                                                                                                                      return 0;
                                                                                                                                  }

                                                                                                                                  багом объявить ;)
                                                                                                                                    +1
                                                                                                                                    Ах, да. Хотел сказать и забыл. В Си это просто декларация, подобная «extern int i;» в C++. Проверьте, добавление extern сделает код валидным для C++.
                                                                                                                                      0
                                                                                                                                      а в стандарте то оказалось прямо написано чего это обозначает. к экстерну отношения не имеет.
                                                                                                                                      экстерн работает на уровне линкера. а неполное определение на уровне компилятора.
                                                                                                                                        0
                                                                                                                                        Подобие ≠ эквивалентность.
                                                                                                                                        Подобие в том, что если в одном модуле
                                                                                                                                        int i;
                                                                                                                                        в другом
                                                                                                                                        int i =5;
                                                                                                                                        то это одна переменная равная 5.
                                                                                                                                        –1
                                                                                                                                        баг в чистом виде.
                                                                                                                                –1
                                                                                                                                тоже возникло такое впечатление. редкостной маразматичности куски кода. индус-стайл.
                                                                                                                                «хороший программист простым кодом пишет гениальные алгоритмы. а не наоборот.»(с) не помню чьё
                                                                                                                                +2
                                                                                                                                Теперь нужна соответствующая голосовалка:
                                                                                                                                В опросе «Какой язык программирования должен быть первым при изучении в школе?» я:

                                                                                                                                1. Проголосовал за Си и, после прочтения habrahabr.ru/blogs/cpp/136283/, уверен, что знаю Си
                                                                                                                                2. Проголосовал за Си, хотя не знаю Си
                                                                                                                                3. Проголосовал за другой язык
                                                                                                                                  +2
                                                                                                                                  Вот интересный пример на С++:
                                                                                                                                  int a = 2;
                                                                                                                                  unsigned int b = 4;
                                                                                                                                  cout << a - b << endl;
                                                                                                                                  

                                                                                                                                  Однажды пришлось помучаться :(
                                                                                                                                    0
                                                                                                                                    Usual arithmetic conversions есть и в Си. Правда нет ни перегрузки, ни вывода типов и придётся результату тип вручную вписать.
                                                                                                                                      0
                                                                                                                                      Как-то неочевидно, на мой взгляд. Тем не менее, спасибо за то, что подсказали как гуглить. Уже просветился)
                                                                                                                                      0
                                                                                                                                      Да, часто спрашивают на собеседованиях. Хотя основное, что здесь надо знать — все преобразования с unsigned int ведутся к этому типу.
                                                                                                                                        +1
                                                                                                                                        Допустить такую ошибку очень легко даже зная что результат операции будет unsigned int. Так что основное что надо знать — это то, что компилятор выдаст варнинг и что варнинги надо внимательно читать.
                                                                                                                                          –1
                                                                                                                                          g++ варнинг не выдаёт. clang выдаёт. Это, конечно, ещё раз показывает преимущество clang в диагностиках, но программистам, использующим gcc от этого не легче.
                                                                                                                                            –4
                                                                                                                                            Да, никогда не любил всякие выскакивающие варнинги, потому всегда искал, как от них избавится. Но очень напрягает что, кроме как во всяких форумах и мелких статейках, ничего не расписано, а стандарт, даже на английском, вообще за деньги распространяется. =(
                                                                                                                                            Многие даже про стандарт с99 ничего до сих пор не знают. =(
                                                                                                                                              +1
                                                                                                                                              Студия сдержанно промолчала.
                                                                                                                                          +3
                                                                                                                                          интересные дачки. к сожалению, верно ответил только на 9 из 12. пример с int заместо size_t выбивается из общего ряда. ошибка очевидна, но кажется, что не эта мелочь имелась ввиду. ну у кого длина строки не влезет в int?
                                                                                                                                            0
                                                                                                                                            За подобный код разработчика надо увольнять. Можно сразу при коммите/чекине делать автоматизированый анализ и в случае обнаружения генерить письмо ПМу ))
                                                                                                                                              0
                                                                                                                                              Давайте конкретные паттерны и будут варнинги в компиляторе. Только знаете в чём проблема? Этого не будет, потому что все приведённые в статье конструкции используются во вполне корректном коде.
                                                                                                                                              +1
                                                                                                                                              1 — уже был коммент, уточните что i — глобальная.
                                                                                                                                              2, 3, 6, 9, 10, 11 — если этого не понимать, то надо писать на java
                                                                                                                                              4 — грабли будут на любом языке, хоть на javascript
                                                                                                                                              5 — задумался ))) хотя и очевидно
                                                                                                                                              7 — даже из буханки можно сделать троллейбус, но зачем? очевидно что так делать нельзя, а знать как конкретный компилятор в вакууме оптимизирует этот код — не нужно.
                                                                                                                                              8 — буханка + gcc даёт вонрингами по рукам
                                                                                                                                              12 — относится не только к C. боянчик, хотя очень интересный.

                                                                                                                                              вывод — по сравнению с C++ — C — ясный и понятный язык.
                                                                                                                                                0
                                                                                                                                                7. В любом коде на Си есть приведения типов указателей. Кто даст гарантию, что они корректны? И далеко не всем очевидно, что так делать нельзя. И не обязательно передавать указатели вот так явно в функцию, их можно доставать из структур ланных или ещё как-то. К тому же, не все компиляторы используют sctrict aliasing при оптимизации и поэтому многие прораммисты даже не догадываются об этом (пока им не приходится портировать свой код на более строгий компилятор).

                                                                                                                                                8. В макросах достаточно часто используются запятые. Замените константы на вызовы функций и варнинги исчезнут.
                                                                                                                                                  0
                                                                                                                                                  И да, не согласен с выводом. Поясню: а по сравнению с malbolge С++ вообще кристально ясен. Моё мнение: в Си слишком много UB и встроенных правил (те же usual arithmetic conversions).
                                                                                                                                                    +1
                                                                                                                                                    забыл добавить, что имхо на C как раз и пишут из-за того, что можно достичь максимальной производительности используя особенности CPU напрямую (9, 10, 11) и/или иногда перекрывающиеся области памяти (3)
                                                                                                                                                    если этого не нужно — на C писать тоже не стоит.
                                                                                                                                                      0
                                                                                                                                                      9, 10, 11. То есть, вы считаете, что если сложение чисел со знаком реализовано через add на x86, то вы можете рассчитывать на свойства переполнения чисел со знаком (конечно, расставив нужные макросы условной компиляции «только x86»)?
                                                                                                                                                        0
                                                                                                                                                        x86, PPC, M68K, ARM, можно ещё добавить Z80 — мне хватает ))
                                                                                                                                                          0
                                                                                                                                                          Тогда выражение (x+1)<x истинно тогда и только тогда, когда в x+1 происходит переполнение, так?
                                                                                                                                                            0
                                                                                                                                                            что есть x?
                                                                                                                                                              0
                                                                                                                                                              Извиняюсь. int x;
                                                                                                                                                                0
                                                                                                                                                                я честно говоря не очень понимаю что вы хотите доказать.

                                                                                                                                                                int x;
                                                                                                                                                                x = ....;
                                                                                                                                                                if ( (x + 1) < x ) { printf(«here\n»); }

                                                                                                                                                                для 32-битного компилятора будет истинно при x = 0x7FFFFFFF
                                                                                                                                                                  0
                                                                                                                                                                  т.е. хочу сказать что бесполезно рассматривать сферические примеры в вакууме (особенно в случае C).
                                                                                                                                                                    0
                                                                                                                                                                    Это вполне конкретный пример (определения того, а не произойдёт ли переполнение при инкременте), который был написан для twos complement машин и работал до тех пор, пока его не скомпилировали с оптимизатором, который пользуется правом, данным ему по стандарту.
                                                                                                                                                                      0
                                                                                                                                                                      А Вы знаете такой оптимизатор, который умеет определить ситуацию, что в данном случае будет UB, чего-то там соптимизировать, но при этом не предупреждает программиста об этом UB?

                                                                                                                                                                      Я пользуюсь Clang и GCC, и оба из них вполне себе предупреждают о подобных местах в коде.

                                                                                                                                                                      Насколько я могу судить, компилятор от Intel тоже такие предупреждения выдаёт.
                                                                                                                                                                        0
                                                                                                                                                                        Оптимизатор не обязан это делать. Хорошо что в данном случае это выражение соптимизировалось в clang, у которого есть AST с выражениями и номерами строк. Но если какое-то другое выражение сворачивается на уровне LLVM (например, посмотрите вот этот доклад www.youtube.com/watch?v=r3ic-IHJTuA&list=PL970A5BD02C11F80C&index=12&feature=plpp_video и какие выражения LLVM вскоре будет уметь сворачивать) то на уровне LLVM даже может не быть нужной информации о строках. Или хуже того, такое выражение появится в результате двух уровней инлайнинга и трёх шагов раскрутки цикла: просто нет такой одной строки, где это выражение записал программист.
                                                                                                                                                                          0
                                                                                                                                                                          Это я не то написал. Оптимизатор — это компилятор, конечно. А остальное — вопрос технологий.
                                                                                                                                                                            0
                                                                                                                                                                            Так в чём суть вашего ответа? Вы спросили существует ли компилятор, который [...]. Да, существует. И какая разница, вопрос технологий или нет. Лучшего у нас нет. А то, что теоретически можно было бы и выдавать диагностику — к вопросу не относится.

                                                                                                                                                                              0
                                                                                                                                                                              Подождите. Clang 3.0, как раз, навыдавал кучу предупреждение об UB на ваших примерах. GCC 4.6.2 тоже. То есть, я понимаю, конечно, что существуют абстрактные примеры, когда вот тут пойдёт всё через LLVM и кто-то что-то не заметит… Но я думаю, в реальности, достаточно сложно и неестественно сгенерировать такие примеры.
                                                                                                                                                                                0
                                                                                                                                                                                Ещё раз, предупреждения появились потому что тут всё сворачиваемое выражение видно сразу на этапе обработки AST. Давайте я вам приведу простой пример. LLVM умеет сворачивать x*2/2 -> x. Но не потому что программисты это пишут в коде, а потому что в результате инлайнинга или вынесения общих подвыражений такое часто получается.
                                                                                                                                                                    0
                                                                                                                                                                    А теперь я вам говорю, что компилятор имеет право (и clang, например, пользуется этим правом) сказать что (x+1)<x всегда false, потому что или это false по стандарту или тут undefined behavior и тут false потому что clang'у так хочется.
                                                                                                                                                                      0
                                                                                                                                                                      и такое бывает.
                                                                                                                                                                      печально что clang не показывает ворнинг в таком случае (gcc, к примеру, должен).

                                                                                                                                                                      собственно что я говорю — сферические примеры в вакууме ничего не дают.

                                                                                                                                                                      есть конкретные вещи в которых это удобно и оправданно (написание эмуляторов, например).
                                                                                                                                                                        0
                                                                                                                                                                        Это не сферические примеры. Это настоящий пример, в котором программист рассчитывал, что раз сложение реализуется командой add, то и переполнение в Си будет такое же. Пишете эмулятор и нужно переполнение чисел со знаком mod 2^n — напишите inline asm.
                                                                                                                                                                          0
                                                                                                                                                                          что-то мне наш спор начинает напоминать анектод про суровых сибирских мужиков и японскую бензопилу.
                                                                                                                                                                          нормальный здравомыслящий программист в нормальном не сфарическом коде так никогда не напишет. даже если и напишет — компилятор он умный, он скажет. программист возьмёт и исправит.

                                                                                                                                                                          эмулятор, кстати, давно написан, работает на x86, ARM и PPC. компилируется gcc или visual studio, естественно с оптимизацией (насколько я помню, ни одного ворнинга).

                                                                                                                                                                          inlne asm использовать вредно. во-1 кроме x86 есть и другие архитектуры, а во-2, на современном процесоре писать руками код, лучше чем тот, который сгерерирует компилятор нецелесообразно по времени.

                                                                                                                                                                          это я к чему — если не пытаться сломать компилятор, то всё будет работать именно так, как рассчитывается и никак по другому.

                                                                                                                                                                          если же заниматься не пойми чем, то конечно, ничего не будет работать правильно, пиши хоть на ассемблере, хоть на C#
                                                                                                                                                                            0
                                                                                                                                                                            > (насколько я помню, ни одного ворнинга).

                                                                                                                                                                            Скомпилируйте clang -ftrapv и запустите.

                                                                                                                                                                            > во-1 кроме x86 есть и другие архитектуры

                                                                                                                                                                            Спасибо Кеп, я этот комментарий пишу с ARM ноутбука.

                                                                                                                                                                            > во-2, на современном процесоре писать руками код, лучше чем тот, который сгерерирует компилятор нецелесообразно по времени

                                                                                                                                                                            inline функция c одной командой add в inline asm. Я вам не предлагаю переписать весь код на asm, я вам предлагаю показать в коде явно какая семантика вам нужна вместо того, чтобы надеяться на непонятно что.
                                                                                                                                                                              0
                                                                                                                                                                              Эмс… А чего вы хотите от -ftrapv? Он вам генерирует проверку на переполнения и поэтому не предупреждает, о том, что при статическом анализе предполагается, что переполнений не происходит.
                                                                                                                                                                0
                                                                                                                                                                Да, именно. А (b<0? a+b>a: a+b<a) — контроль переполнения при сложении. Как его вообще иначе можно написать (предполагая, что a и b — уже числа максимальной разрядности, например, long long)?