Для тех, кто хочет поиграть в детектива: найди ошибку в функции из Midnight Commander

    Найди ошибку!


    Приглашаем попробовать найти ошибку в очень простой функции из проекта GNU Midnight Commander. Зачем? Просто так. Это забавно и интересно. Хотя нет, мы соврали. Мы в очередной раз хотим продемонстрировать ошибку, которую с трудом находит человек в процессе code review, но легко находит статический анализатор кода PVS-Studio.

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

    static int
    EatWhitespace (FILE * InFile)
      /* ----------------------------------------------------------------------- **
       * Scan past whitespace (see ctype(3C)) and return the first non-whitespace
       * character, or newline, or EOF.
       *
       *  Input:  InFile  - Input source.
       *
       *  Output: The next non-whitespace character in the input stream.
       *
       *  Notes:  Because the config files use a line-oriented grammar, we
       *          explicitly exclude the newline character from the list of
       *          whitespace characters.
       *        - Note that both EOF (-1) and the nul character ('\0') are
       *          considered end-of-file markers.
       *
       * ----------------------------------------------------------------------- **
       */
    {
        int c;
    
        for (c = getc (InFile); isspace (c) && ('\n' != c); c = getc (InFile))
            ;
        return (c);
    }                               /* EatWhitespace */

    Как видите, функция EatWhitespace совсем маленькая. Даже комментарий к функции занимает больше места, чем тело самой функции :). Теперь немного деталей.

    Описание функции getc:

    int getc ( FILE * stream ); 

    Функция возвращает символ, на который указывает внутренний индикатор позиции файла указанного потока. Затем индикатор переходит к следующему символу. Если на момент вызова потока достигнут конец файла, функция возвращает значение EOF и устанавливает индикатор конца файла для данного потока. Если возникает ошибка чтения, функция возвращает значение EOF и устанавливает индикатор ошибки для данного потока (ferror).

    Описание функции isspace:

    int isspace( int ch );

    Функция проверяет, является ли символ пробельным по классификации текущей локали. В стандартной локали следующие символы являются пробельными:

    • пробел (0x20, ' ');
    • смена страницы (0x0c, '\f');
    • перевод строки LF (0x0a, '\n');
    • возврат каретки CR (0x0d, '\r');
    • горизонтальный таб (0x09, '\t');
    • вертикальный таб (0x0b, '\v').

    Возвращаемое значение. Ненулевое значение, если символ является пробельным, ноль иначе.

    Функция EatWhitespace должна пропустить все символы, считающиеся пробельными, кроме перевода строки '\n'. Ещё одной причиной остановки чтения из файла может стать достижение конца файла (EOF).

    А теперь, зная всё это, попробуйте найти ошибку!

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

    Рисунок 1. Время поискать ошибку. Единороги пока подождут.


    Рисунок 1. Время поискать ошибку. Единороги пока подождут.

    Всё равно не видите ошибку?

    Всё дело в том, что мы обманули читателей по поводу isspace. Ха-ха! Это вовсе не стандартная функция, а самодельный макрос. Да, мы — бяки и заставили вас запутаться.

    Рисунок 2. Единорог дарит читателям ложное представление о том, что такое isspace.


    Рисунок 2. Единорог дарит читателям ложное представление о том, что такое isspace.

    На самом деле виноваты, конечно, не мы и не наш единорог. Путаницу внесли авторы проекта GNU Midnight Commander, решив создать собственную реализацию isspace в файле charset.h:

    #ifdef isspace
    #undef isspace
    #endif
    ....
    #define isspace(c) ((c)==' ' || (c) == '\t')

    Создав такой макрос, одни разработчики запутали других разработчиков. Код написан из предположения, что isspace — это стандартная функция, считающая возврат каретки (0x0d, '\r') одним из пробельных символов.

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

    for (c = getc (InFile);
         ((c)==' ' || (c) == '\t') && ('\n' != c);
         c = getc (InFile))

    Подвыражение ('\n' != c) является лишним (избыточным), так как его результатом всегда будет true. Об этом и предупреждает анализатор PVS-Studio, выдавая предупреждение:

    V560 A part of conditional expression is always true: ('\n' != c). params.c 136.

    Для полной ясности давайте разберём 3 варианта развития событий:

    • Достигнут конец файла. Конец файла (EOF) — это не пробел и не табуляция. Подвыражение ('\n' != c) из-за short-circuit evaluation не вычисляется. Цикл останавливается.
    • Прочитан любой символ, не являющийся пробелом или табуляцией. Подвыражение ('\n' != c) не вычисляется из-за short-circuit evaluation. Цикл останавливается.
    • Прочитан символ пробела или горизонтальная табуляции. Подвыражение ('\n' != c) вычисляется, но его результат всегда будет истинным.

    Другими словами, рассмотренный код эквивалентен этому:

    for (c = getc (InFile); c==' ' || c == '\t'; c = getc (InFile))

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

    Программист, написавший в теле функции EatWhitespace вызов isspace, рассчитывал, что будет вызвана стандартная функция. Именно поэтому он добавил условие, что перевод строки LF ('\n') не следует считать пробельным символом.

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

    Примечательнее, что планировалось пропускать и символ возврат каретки CR (0x0d, '\r'). Этого не происходит и цикл остановится, встретив этот символ. Это приведёт к неприятным неожиданностям, если разделителем строк в файле является последовательность CR+LF, используемая в некоторых не-UNIX системах, таких как Microsoft Windows.

    Для тех, кто хочет узнать больше об исторических причинах использования в качестве разделителей строк LF или CR+LF, предлагаем вниманию статью на Wikipedia "Перевод строки".

    Функция EatWhitespace по задумке должна одинаково обрабатывать файлы, где в качестве разделителя используется как LF, так и CR+LF. Для случая CR+LF это не так. Другими словами, если ваш файл прибыл из мира Windows, то вам не повезло :).

    Возможно, это и не является серьёзной ошибкой, тем более, что GNU Midnight Commander распространён в UNIX-подобных операционных системах, где для перевода строки используется символ LF (0x0a, '\n'). Однако из-за таких мелочей и возникают различные досадные проблемы несовместимости данных, подготавливаемых в Linux и Windows системах.

    Описанная ошибка интересна тем, что её почти невозможно обнаружить при классическом обзоре кода. Не все разработчики проекта могут знать про тонкости макроса, да и забыть их очень легко. Это хороший пример, когда статический анализ кода дополняет обзоры кода и другие методики поиска ошибок.

    Переопределение стандартных функций является плохой практикой. Кстати, недавно в статье "Любите статический анализ кода" рассматривался похожий случай с макросом #define sprintf std::printf.

    Более правильным решением было бы дать макросу уникальное имя, например, is_space_or_tab. Тогда путаница была бы невозможна.

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

    Спасибо за внимание. Приглашаем скачать и попробовать анализатор PVS-Studio для проверки своих проектов. Плюс напоминаем, что недавно в анализаторе появилась поддержка языка Java.



    Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Wanna Play a Detective? Find the Bug in a Function from Midnight Commander.
    PVS-Studio
    792.84
    Static Code Analysis for C, C++, C# and Java
    Share post

    Comments 136

      +37
      Коммент про #define true false
        +5
        #define true (rand() > (RAND_MAX >> 10))
        #define false (rand() < (RAND_MAX >> 10))
          +4
          #define private public, как Кармак завещал.
            0
            Ну такой-то макрос поведение программы не изменит хотя бы)
              0
              Да конечно — скажи это ABI MSVC.
          +54
          Простите, в этот раз кликбейт лично мне не понравился. Никакой возможности отгадать загадку не было.
          Да, мы — бяки и заставили вас запутаться.

          Ну лично у меня статья только раздражение вызвала из-за этого. Не знаю, может кому-то такая уловка и по душе.
            +22
            Очень хорошо передаёт впечатление человека во время ревью. Как художественный приём — отлично. У читающего код будет такое же впечатление. Взяли и обманули.
            +15
            Извините, но вы нашли ошибку не в коде функции, а в замене стандартной библиотеке.

            C99: 7.4.1.10 The isspace function
            The standard white-space characters are the following: space (' '), form feed ('\f'), new-line ('\n'), carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v'). In the «C» locale, isspace returns true only for the standard white-space characters.

            Ловить ошибке в коде, к которому кто-то приписал спереди #define true false — это не для белых людей, извините.

            P.S. То есть статический анализатор, конечно, тут молодец, но вообще идея заменять стандартные функции… за такое по рукам надо бить не в функции EatWhitespace, а там, где #define написан…
              +1
              В стандартной библиотеке как раз всё в порядке. Ошибка — в коде Midnight Commander, и заключается в том что используется не функция из стандартной библиотеки, а что-то ещё.
                +5
                Вы сказали ровно то же самое, что и khim, только другими словами.
                  0
                  Либо я отвечал на другую версию комментария, либо просто невнимательно прочитал первую строчку.
              +6

              Ошибки в указанной функции нет, она в другом месте. И ошибка отлично ловится на код ревью, только на ревью кода с ошибкой, а не ревью EatWhitespace. Как только разработчик видит вот такое:


              #ifdef isspace
              #undef isspace
              #endif

              Он сразу сообщает написавшему это падавану, что так делать не надо и пускай падаван изобретает для макросов имена не из стандартной библиотеки.

                +3
                И заодно правит ему руки подходящей кувалдой.
                –5

                Не ужили ещё кто то пользуется IDE в которых макросы не выделяются другим цветом?
                Upd: по традиции под статьёй единорога надо упомянуть про раст, там все макросы заканчиваются на "!".

                  +9
                  Это не поможет. В GLibC, к примеру isspace — это тоже макрос. Вот такой:
                  #define isspace(c) __isctype((c), _ISspace)
                  Но он стандартам соотвествует. Можно навести мышкой и увидить, что «тут что-то не то», конечно — но на каждую букву наводить мышкой не будешь…

                  Впрочем статическому анализатору хорошо бы ругнуться на этапе переопределения стандартного макроса.

                  То есть то, что он поймал ошибку — это неплохо, плохо что вообще кто-то переопределяет стандартные функции и ему за это ничего не делают…
                    0

                    У меня всё работает в qtcreator + clang-code-model.
                    Нету define:

                    Шланг умён и подсвечивает как функцию. Если интересно то Ctrl+Click я вижу из файла ctype.h:

                    Теперь сделаем что то плохое:

                    Ага явно видно макрос!

                      0

                      Хотя это с++ и там определено __NO_CTYPE в си действительно сплошные макросы повсюду.

                        +5
                        А авторы в КДПВ могли бы такую подсветку сделать. Была бы подсказка.
                      +1

                      Для меня любой макрос это сразу знак что жди сюрпризов. Стараюсь их вообще не использовать. В коде на си плюс плюс их практически не вижу. Большинство вопросов решается темплейтами. Чтобы сэкономить пару слов вводить макрос тоже смысла нету, а если и есть то они обычно пишутся капсом чтобы не спутать с названием функции, и опять же IDE все подсвечивает. Почему в glibc isspace определили как макрос хотя во всех доках это функция из int в int, и что за такты процессора они на этом экономят я искренне недоумеваю. Со спецификой си не знаком, видел один раз ООП на макросах в гномовской g-lib, сочувствую.

                        0
                        Дело не в темплейтах. Просто в C90 нет слова inline.
                          –2
                          Не в темплейтах т.к. это стандартная C а не C++ функция.

                          GCC с --std=c90 прекрасно работает с __inline__ или glibc собирается чем-то кроме gcc не поддерживающем это расширение gcc?

                          Поэтому ответ должен быть немного глубже.

                          Действительно все дело в оптимизации и довольно много кейсов когда вызов функции значительно замедляет работу. Например вам нужно удалить/заменить пробелы на что-то в большом объеме текста (простой lut даст прирост скорости в 2 раза при грамотной организации цикла) и тогда есть смысл сначала узнать доступна ли ли табличная/каноничная реализация isctype, что и делает макрос с fallback на inline либо extern int isctype(int, int) реализацию в glibc…
                            0
                            GLibC требует не просто GCC, а ещё и достаточно новых версий GCC, но вот заголовочные файлы сделаны так, чтобы годился любой ANSI C компилятор.

                            Отсюда макросы. А по скорости в современных компиляторах разницы нету.
                              0
                              Разницы для производительности нет для static inline, а для extern inline есть. Как минимум поскольку в общем случае неизвестно, как будет собираться модуль где эта функция объявлена уже inline, ну и естественно велика вероятность, что при вызове extern inline будет сбрасываться конвейер.
                                0
                                Следуя вашей логике в заголовочных файлах glibc было бы гораздо больше макросов чем мы имеем реально.
                                  +1
                                  Вы туда вообще смотрели когда-нибудь? Там макрос на макросе и макросом погоняет.
                              +1

                              т.е. разрабы glibc, жертвуют нормально подсветкой синтакиса, оборачивают все аргументы в запасные скобочки, расставляют переносы строк через "\" потому что 30 лет назад не было inline?

                                –1
                                Его и сейчас на некоторых компиляторах нет.
                                  0

                                  Но как уже писали выше, GNU libc компилируется GCC.

                                    0
                                    Не всегда
                                      –1
                                      Читаем:
                                      On most architectures, GCC 5 or later is required to build the GNU C
                                      Library. (On powerpc64le, GCC 6.2 or later is still required, as
                                      before.)

                                      Older GCC versions and non-GNU compilers are still supported when
                                      compiling programs that use the GNU C Library.
                                      Вы говорите про первый абзац, но в случае с .h-файлами важен второй.
                              0
                              Кажется, что ревью чаще делается где-нибудь в интерфейсе github, нежели IDE
                              –10
                              Даже искать не понадобилось, очевидная ошибка. Выдержка из man getc:

                              RETURN VALUE
                              fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF on end of file or error.


                              Edit:
                              P.S.: Пробежался взглядом по статье. Всётаки, со времён появления man, его отсутствие в системе, — настоящее увечье.
                              +2
                              Переопределение стандартных функций является плохой практикой.

                              До сих пор с содроганием вспоминаю как в одном проекте на JS, кто то взял и в конец одной либы дописал пару специфических вызовов. Пол дня пришлось убить чтобы понять где зарыта магия и почему если поменять версию либы на более новую, ошибок нет но и работать ничего не работает.
                                +3
                                У нас интереснее было: пара строчек была дописана только в полную версию файла, а минифицированная осталась без изменений. Оптимизация при этом в проекте была строго выключена: при попытке её включить проект «почему-то» переставал работать…
                                +1
                                Ещё неплохо бы проверить inFile на NULL. Так, на всякий случай.
                                  +5
                                  А выругался ли анализатор на #define isspace? Имхо, это куда как важнее. И ругаться тут допустимо с использованием обсценной лексики.
                                    0
                                    Нет. На практике, это будет скорее всего неудачная диагностика. Мы подумаем.
                                      +7

                                      Вам надо выдавать предупреждения на попытку переопределить в #include "" что-либо из #include <>.

                                      0
                                      там нет
                                      #define isspace

                                      там есть
                                      #undef isspace
                                      +13

                                      Поведение макроса было бы куда веселее в таком виде:


                                      for (; isspace (c = getc (InFile)) && ('\n' != c););
                                        +4
                                        Код ужасен, не пройдет pre-review даже с юнит-тестами, которые даже не пройдут если так не было задумано.
                                          –11
                                          Аррррр, пробелы перед скобками, аж душа радуется. Кстати давно меня интересует, да все как-то речь не заходила, почему в 95% случаев перед скобками никто не ставит пробелов? Ведь ни у кого даже и мысли не возникает не ставить их на письме, ведь что это такое?

                                          copyStream(readStream(inputStream));

                                          Лень пробел нажать, или на перфокарты запись идет и места нет? Давайте тогда и между словами пробелы не ставить, это же пустая трата времени) И что самое смешное — ладно бы это всего касалось, но после операторов (неожиданно), пробелы ставятся в большинстве случаев. Есть ли какое-либо объяснение этому кроме как что это бездумное употребление каких-то пережитков прошлого?
                                            +9
                                            Кажется потому что это не соответствует синтаксису письма (где в скобках пояснение), но зато соответствует синтаксису математики, где пробела нет: f(x) = «ф от икс», f (x) = «ф (а может и икс)».
                                              –15
                                              О как, пошли минусы, как-будто бы об этом постоянно говорится, а я этого не знаю. Хорошо, это многое объясняет на самом деле, только математика тут каким боком? Я хорошо в школе учился, лет с 12 мечтал стать программером, пишу сейчас на Java, PHP и С в плане расширений для него, ни разу знания из математики мне не понадобились, дак какова причина, по которой ее сюда приплетают? Массивы туда же, понятно что это обозначает отрезок, поэтому они начинаются с 0, а не с 1, но ведь логично же что первый элемент должен иметь индекс 1, а нулевого элемента не должно существовать вовсе, так как он нулевой, а первый элемент — это первый элемент, и как и в любой комбинации если что-то состоит, например, из трех элементов, то логично предположить что и общее количество должно равняться трем. Ан нет, двум (3 — 1), и все потому, что на заре все хотели считать себя математиками. А может и сейчас считают, кто их знает…
                                                +5
                                                Если бы вы писали на С, то у вас бы не возникало глупых вопросов по поводу индексации массивов с нуля.
                                                  +4
                                                  Считайте, что в квадратных скобках не индекс массива, а смещение от его начального элемента, и тогда всё встанет на свои места.
                                                    +2
                                                    Изначально имя массива представляло собой ссылку на заранее выделенную область памяти. При обращении к конкретному элементу массива указывалось смещение этого элемента от начала области. Т.е. обращение array[6] по факту находит начало зарезервированной для array области, отступает от её начало 6*[размер элемента] бит и возвращает следующие [размер элемента] бит. Логично, что к первому элементу нужно обращаться как array[0]. В современных языках массивы стали значительно сложнее и такая система не обязательна, но традиция осталась.

                                                    UPD: Я ОЧЕНЬ медленно пишу комментарии.
                                                      –3
                                                      О, ну так это другое дело, во всяком случае именно объясняет суть, а-то мне ниже на пальцах доказывают что номер и индекс совпадать не должны и все такое. Может быть это не избавит от необходимости постоянно помнить что первый элемент начинается не с 1 но хотя-бы ясность внесло)

                                                      UPD: Я ОЧЕНЬ медленно пишу комментарии.
                                                      Ахах, в смысле отвечаете не сразу, или печатаете долго?) Ничего, у нас тут не чат, я и сам отвечаю по мере возможности)
                                                      +1
                                                      первый элемент должен иметь индекс 1 ...

                                                      «первый элемент» — порядковый номер;
                                                      «индекс 1» — индекс;

                                                      Порядковый номер и индекс не обязаны совпадать.

                                                      … например, из трех элементов, то логично предположить что и общее количество должно равняться трем. Ан нет, двум (3 — 1) ...

                                                      опять путаница…
                                                      Количество элементов — 3
                                                      Индекс последнего элемента — 2
                                                        –2
                                                        Да, но ведь это подгонка под условия. В любой БД индекс начинается с 1, с какой стати в массиве он начинается с нуля? Извиняюсь за пример, ведь логично предположить, что если мешков у нас три, то и номер последнего мешка будет три, с какой стати первый мешок будет иметь индекс 0? Считаем с единицы, значит первый мешок первый. Программирую лет 10 уже, просто как-то все не было возможности это обсудить, ибо реально надоедает только из-за этого постоянно помнить что последний элемента массива — это count (array) — 1, а не count (array), и начинать цикл с 0, а не с единицы.
                                                          0
                                                          А почему мы считаем с единицы, собставенно! Почему не с нуля? Дейкстра про это писал больше 30 лет назад, не вижу смысла повторятся.

                                                          SQL был разработан сильно раньше, так что неудивительно, что он использует менее удобную, но более исторически правильную нумерацию. Когда-то компьютеры заставляли вообще работать в десятичной системе счисления (знаменитый ЭНИАК никакой другой не знал, и даже 8086, при всей своей примитивности, имел специальные команды для этого) — но мы всё-таки перешли в программировании к двоичной, как более логичной… то же самое и с индексами.

                                                          P.S. В этом смысле американцам проще: картошку они вешают в футах, а когда что-то рассчитать нужно — переходят в СИ, так что идея, что индексы в массиве обязательно должны нумероваться так же, как мешки картошки им даже в голову не приходит.
                                                            0
                                                            О, спасибо, но ведь он и объясняет почему мы считаем с единицы, а с нуля-то по какой логике? SQL был разработан сильно раньше, но со временем-то почему вдруг с какой-то стати решили, что с нуля удобнее? Есть же какое-то объяснение этому? «Удобнее» — это следствие, но должна иметься причина. Это как я на Гитхабе в issues спрашивал почему спецификация JSON подразумевает, что ключи объекта идут не по порядку добавления, а рандомом, на что получил ответ, что если они будут идти в порядке добавления, то это уже будет не JSON. Z так и говорю, что это следствие, а какова причина этого? На что даже и ответа не получил. Люди явно не видят разницы между причиной и следствием( А может просто кто-то пытается постоянно докопаться до смысла бытия и почему мы здесь, а большинство такими вопросами даже не заморачивается…
                                                              +1
                                                              О, спасибо, но ведь он и объясняет почему мы считаем с единицы, а с нуля-то по какой логике?
                                                              Вообще-то он объясняет почему так. Достаточно подробно. Вначале объясняет почему лучше использовать полуинтервалы как в C++ (включающие левый конец, но не включающие правый), потом замечает что при таком подходе у нас будет либо полуинтервал от 0 до N, либо от 1 до N+1. Последнее выглядит, прямо скажем, странно.

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

                                                              «Удобнее» — это следствие, но должна иметься причина.
                                                              Причина простая: ноль — это ничего, а единица — уже что-то. Для перехода от последовательности чисел к первому элементу вроде как мы ничего не должы бы делать — но при записи a[1] какжется что мы что-то делаем.

                                                              Дейкстра приводит пример языка Mesa, где были предусмотрены сразу все возможные варианты… вариант с нуля (включая нуль) до N (не включая N) работает лучше — в большинстве случаев (хотя и не всегда).

                                                              Это как я на Гитхабе в issues спрашивал почему спецификация JSON подразумевает, что ключи объекта идут не по порядку добавления, а рандомом, на что получил ответ, что если они будут идти в порядке добавления, то это уже будет не JSON.
                                                              Ну это же, блин, логично! JSON — это JavaScript Object Notation, а то, что вы предлагаете уже никак не будет JavaScript объектом! А поскольку у JSON одно из важнейших свойств — это неизменность спецификации (не бывает JSON 1.0, JSON 2.0, JSON X или JSON Advanced), то понятно, что предложенного вами варианта не будет. Уж не говоря о том, что этот подход нелогичен (в том же смысле в каком нелогична нумерация с единицы): для того, чтобы ключи объекта можно было возвращать в порядке добавления вам их нужно будет хранить в двух структурах в памяти и работы с ними станет медленее — а нафига это нужно?
                                                                0
                                                                Вот только как раз у Javascript-объектов ключи идут в порядке добавления (за исключением ключей, являющихся натуральными числами). Тут скорее позаботились о других языках, где может просто не оказаться аналогичной удобной структуры данных.
                                                                  0
                                                                  По спецификации это не гарантируется. Хотя если непосредственно развернуть объект из JSON, проблем быть не должно, конечно…
                                                                    0

                                                                    Спецификация Javascript лежит на сайте tc39.github.io, а не developer.mozilla.org :-)

                                                                      0
                                                                      Хм, спасибо, прошу прощения за неточность, был уверен, что описание соответствует оригиналу (а оно, судя по всему, просто учитывает наличие бага в IE).
                                                                    +1
                                                                    ECMA-262, 4.3.3:
                                                                    An object is a member of the type Object. It is an unordered collection of properties each of which
                                                                    contains a primitive value, object, or function.
                                                                      0
                                                                      Это старый es3, на дворе давно уже более новая версия. Впрочем, соглашусь что JSON тоже старый.
                                                                    0
                                                                    > Вообще-то он объясняет почему так. Достаточно подробно. Вначале объясняет почему лучше использовать полуинтервалы как в C++ (включающие левый конец, но не включающие правый),

                                                                    Такие полуинтервалы хорошо работают при интерпретации только по возрастанию. В обратном направлении — начинается издевательство над здравым смыслом. Вот пример из питоновской range(), в которой как раз их и делали:

                                                                    >>> range(0,5)
                                                                    [0, 1, 2, 3, 4]
                                                                    


                                                                    ok, а если наоборот? «Дэти, эта невазможна понят, эта нада запомнит»:

                                                                    >>> range(4,-1,-1)
                                                                    [4, 3, 2, 1, 0]
                                                                    


                                                                    А если бы рядом с подобным полуоткрытым была функция для простого закрытого — таких издевательств не было бы. Или универсальная с флагами для открытости/закрытости обеих сторон.

                                                                    А ещё у нас язык к этому не приспособлен, и это хорошо видно на том же C++: везде, где в STL написано end, должно было быть другое слово — например, limit, потому что end всеми не-программистами будет понято как последний элемент.

                                                                    То же и с нумерацией: мы не можем переломать естественный язык и потребовать называть первый нулевым. Как минимум нужны дополнительные уточнения о режиме счёта.

                                                                    > Когда накопился опыт и стало понятнее, что с нуля действительно удобнее. Формулы проще получаются.

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

                                                                    > для того, чтобы ключи объекта можно было возвращать в порядке добавления вам их нужно будет хранить в двух структурах в памяти и работы с ними станет медленее — а нафига это нужно?

                                                                    Нужно. Именно для человека: хотя бы чтобы можно было легко сравнивать два объекта в типовых условиях.
                                                                    Да, иногда для этого требуется лексикографическое упорядочение ключей. Но практика показала, что insertion order достаточен для подавляющего большинства случаев, и его формализация стала весьма распространённым явлением. Оно есть в JS, оно есть в Python начиная с 3.6 (и закреплено официально с 3.7), и наверняка будет продолжаться и дальше.
                                                                    И насчёт «станет медленнее» — опыт реализации в Python показал, что если замедление и есть, то оно ничтожно.
                                                                      0
                                                                      И насчёт «станет медленнее» — опыт реализации в Python показал, что если замедление и есть, то оно ничтожно.
                                                                      Вы бы ещё реализацию на brainfuck'е сюда приплели. Python — это такой язык, где скорость не важна. Вообще. Если вы хотя бы задумываетесь над тем, что что-то медленно работает, то это что-то нужно переписать на другом языке. Потому неудивительно, что там разработчики могут позволить себе разные странности.

                                                                      Нужно. Именно для человека: хотя бы чтобы можно было легко сравнивать два объекта в типовых условиях.
                                                                      Вот там где вы хотите сравнивать — там и отсортируйте. JSON — оказался фактически идеалом во многих ситуациях потому, что он прост и эффективен. Если вы что-то делаете «для человека» и на эффективность вам наплевать — есть много более удобных альтернатив.

                                                                      Может, в 1970-м это имело значение. Сейчас трансляторы достаточно мощные, чтобы это скомпенсировать, а соответствие ожиданиям человека важнее для получения безошибочного кода.
                                                                      «Большой переход» произошёл в 1990е, а не в 1970е. И сейчас уже, если говорить об ожиданиях, то скорее стоит ожидать, что последовательность будет x₀, x₁, x₂, ... — а не что массивы с нуля будут нумероваться.

                                                                      А если бы рядом с подобным полуоткрытым была функция для простого закрытого — таких издевательств не было бы. Или универсальная с флагами для открытости/закрытости обеих сторон.
                                                                      Почитайте Дейкстру ещё раз, чёрт побери. Пробовали — не работает. Потому что C++-стайл интервалы реально удобны, если интервалы возрастают, а поскольку этот вариант встречается намного чаще других — то люди, встречая интервалы другого типа, их путают.

                                                                      А ещё у нас язык к этому не приспособлен, и это хорошо видно на том же C++: везде, где в STL написано end, должно было быть другое слово — например, limit, потому что end всеми не-программистами будет понято как последний элемент.
                                                                      Это нормально. У любой профессии есть сленг — и часто простые, обыденные, слова в этом сленге значат не совсем то, что в быту. Программирование — не исключение.
                                                                        0
                                                                        > Вы бы ещё реализацию на brainfuck'е сюда приплели. Python — это такой язык, где скорость не важна.

                                                                        1. Важна, и реально проверяется и оптимизируется.
                                                                        2. JavaScript вы поставили в ту же категорию? Если бы было так, для него не делали бы движков типа V8. И в результате в JS делают insertion order, даже зная, что это даст потерю производительности.

                                                                        > JSON — оказался фактически идеалом во многих ситуациях потому, что он прост и эффективен. Если вы что-то делаете «для человека» и на эффективность вам наплевать — есть много более удобных альтернатив.

                                                                        Для «прост и эффективен» для машины JSON как раз не годится по определению — он таков для человека. Для машины лучше бы подошло что-то вроде ASN.1 BER или структур линуксового netlink.
                                                                        А раз сделали человекочитаемость — то оказалось, что и порядок ключей важен настолько, что попытки его убрать дали откат обратно под давлением.

                                                                        > Почитайте Дейкстру ещё раз, чёрт побери. Пробовали — не работает. Потому что C++-стайл интервалы реально удобны, если интервалы возрастают, а поскольку этот вариант встречается намного чаще других — то люди, встречая интервалы другого типа, их путают.

                                                                        Читал. Это у вас профессиональное «когда молоток в руках, всё вокруг — гвозди»: если писать под C++ STL или аналог с его подходами, то вы и не будете ничего кроме [begin;end) видеть вокруг.

                                                                        Реально же оказалось, в терминах того же Питона, что range(a,b+1) приходится писать чаще, чем range(a,b). А если бы были rangecc(a,b) и rangeco(a,b) — не пришлось бы костылировать на ровном месте.

                                                                        > Это нормально. У любой профессии есть сленг — и часто простые, обыденные, слова в этом сленге значат не совсем то, что в быту. Программирование — не исключение.

                                                                        Этот сленг в таком виде только у C++ и ещё, возможно, пары инвалидов. Остальные даже в программировании его очень неохотно принимают.
                                                                          0
                                                                          А раз сделали человекочитаемость — то оказалось, что и порядок ключей важен настолько, что попытки его убрать дали откат обратно под давлением.

                                                                          Вот только текущий порядок ключей в JS — 1, 2, 3, foo :-)

                                                                            0
                                                                            Я в курсе про исключение для чисел. Но для нечисловых ключей insertion order сейчас таки везде.
                                                                              0
                                                                              Это скорее вопрос совместимости, чем удобства, всё-таки.
                                                                            0
                                                                            2. JavaScript вы поставили в ту же категорию? Если бы было так, для него не делали бы движков типа V8. И в результате в JS делают insertion order, даже зная, что это даст потерю производительности.
                                                                            С ним очень неприятная история произошла: он также был разработан, как язык, для которого производительность не важна (а «тяжёлые» вычисления должны были выполняться в плагинах), а потом плагины «отсекли» и в результате «зерно стали возить по воздуху».

                                                                            Для «прост и эффективен» для машины JSON как раз не годится по определению — он таков для человека. Для машины лучше бы подошло что-то вроде ASN.1 BER или структур линуксового netlink.
                                                                            Если говорить об эффективности, но нужно на какие-нибудь Cap-n-Proto смотреть. Но паркинг всего этого гораздо меньше отличается по сложности от парсинга JSONа, чем парсинг JSONа от какого-нибудь XML. Зато JSON — один и его парсер для любого языка пишется за день. Ну два — если язык сильно крив.
                                                                        0
                                                                        То есть в конечном итоге оказалось, что в оригинальной спецификации ключи идут в порядке добавления?) Мне однажды понадобилось реализовать листалку влево/вправо на Андройде по месяцам, где данные хранились в JSON, и единственный возможный способ осуществить это — вычитать из количества месяцев в году единицу, когда свайпали назад, и прибавлять — когда вперед соответственно, но для этого нужно было формировать JSON в порядке добавления месяцев в него, я списался с автором дефолтной библиотеки для Java (ибо для самого Андройда она от Гугла), в итоге пришлось создать свой форк, который уже сортирует в порядке добавления. В общем, сизифов труд, да и думать о производительности в этом случае — это экономия на спичках реально.
                                                                          +1
                                                                          То есть в конечном итоге оказалось, что в оригинальной спецификации ключи идут в порядке добавления?)
                                                                          Как тут уже заметили: оригинальная спецификация — это-таки ECMA-262, 1999й год, никакого «порядка добавления» там нет.

                                                                          Из вашего описания что-либо понять сложно, но если кто-то полагается на то, что объекты возвращаются из JSON в определённом порядке — то нужно соответствующий компонент исправить/передалать, а не делать форки библиотек.
                                                                            –1
                                                                            Чтобы такого не было, можно использовать БД, например, однако это имеет смысл только когда проект пишется с нуля, ибо смена хранилища данных уж очень нетривиальна, ибо переписывать нужно все обращения к БД во всем проекте, а их могут быть тысячи, и ладно если используется какая-нибудь библиотека (причем в Андройде, например, возможная БД только одна — LiteSQL, не нравится — не пользуй, или пиши свою обвертку), но если что-то хранилось ранее в текстовых файлах, и разраб захотел переехать на БД только потому, что она более предназначена для чего-либо, этот проект легче похоронить и писать новый, что как-бы немного бессмысленно, разве что если по каким-то причинам разрабу заняться больше нечем, кроме как заново переписывать свои же проекты, типа чо такого, бог дал — бог взял, ога.

                                                                            FBReader, кстати, хороший пример, читалка такая, поддерживается всеми платформами, которыми только может поддерживаться, причем полноценно, начиная с Symbian и Blackberry, и заканчивая чуть ли не Бадой. Хочется познакомиться с разрабом и узнать у него чем он занимается по жизни, что имеет столько свободного времени, ибо лично я сам разраб и знаю сколько времени занимает программирование.
                                                                +2
                                                                Подскажите, пожалуйста, какой номер должен быть у 1-го дня в году, у первого года нашей эры и у первого часа в сутках?
                                                                  0
                                                                  Нулевой разумеется, но так как этот интерфейс «шибко давно делали», то нулевым часом начинаются только сутки, а год и день — с единицы нумеруются. При этом в каждом году, хотя бы, есть непрерывная нумерация дней, но с годами — всё совсем плохо: нулевого года нет вообще, что сильно мешает делать правильные рассчёты.

                                                                  P.S. Хороший пример, надо будет запомнить.
                                                                    –1
                                                                    Основная фишка здесь в переходе от дискретных элементов к составным или нецелочисленным. Предположим, имеем массив элементов, каждый из которых занимает s байтов. Я имею ссылку на n-й байт в этом массиве. Мне нужно узнать:

                                                                    • номер элемента массива K, в который входит этот байт
                                                                    • номер байта k внутри этого элемента


                                                                    Если считать от 1, то формулы будут такими:
                                                                    K = (n - 1) / s + 1
                                                                    k = (n - 1) % s + 1
                                                                    


                                                                    Если считать от 0, то формулы будут такие:
                                                                    K = n / s
                                                                    k = n % s
                                                                    


                                                                    С того момента, как сталкиваешься с этой арифметикой, все становится очевидно. Операции с временем — действительно хороший пример, потому что всем понятен.
                                                                      0
                                                                      Операции с временем — действительно хороший пример, потому что всем понятен.
                                                                      Как видим не всем. Людям, которые думать не хотят ничего не поможет.
                                                                        0
                                                                        Людям, которые думать не хотят ничего не поможет

                                                                        Если речь обо мне, то я попросил рассказать о причинах, а не о следствии (сутки начинаются с 00:00 — следствие, почему они начинаются с 00:00 — причина). До сих пор ответа не получил, без пищи для размышления думать просто не о чем, как бы ни хотелось.
                                                                          0
                                                                          Время типа 00:00 — это количество уже пройденного (полных минут), но все времена от 00:00 до 01:00 называются «первый час», от 01:00 до 02:00 — «второй час», и так далее до двенадцатого (в этом счёте не принято крутить по 24). И это не только со временем: «ребёнку пошёл второй годик» это возраст от года до двух, «едем третий километр» это от двух до трёх километров, и так далее.

                                                                          Но это по отношению к протяжённым сущностям всех видов, и то есть отклонения («1960-е годы» это не до 01.01.1960, а начиная от него). Перенос же этих правил на точечные объекты — а элементы массива/списка/etc. следует рассматривать именно так — однозначно не производится, и оттого, что «третий километр» можно рассматривать как цельный не пройденный ещё километр по счёту 3, начиная с 1, не мешает его точно так же рассматривать как по счёту 2, начиная с 0, потому что мы полных 3 километра ещё не намотали.

                                                                          Собственно все аргументы подходов типа «0-е число» основаны именно на единообразности представления _частичной_ реализации протяжённой сущности при любой точности, с округлением через усечение, и от того, что усечение просто удобно — отбросил цифры и получил нужный результат. Формально математически это действительно так. Осталось понять, насколько эта формальность удобна в остальных вопросах.
                                                                            0
                                                                            О, спасибо, про рождение ребенка очень хороший пример, действительно, отсчет времени его жизни начинается с нулевого года.

                                                                            Формально математически это действительно так. Осталось понять, насколько эта формальность удобна в остальных вопросах.

                                                                            Вот я о чем и говорю, по мне дак массив — это не отрезок, а хранилище, поэтому я не с проста про мешки пример привел, ибо первый мешок — это первый мешок, а не нулевой. Но за трактовку спасибо, уж что есть — то есть, что уж теперь…
                                                                        0
                                                                        > С того момента, как сталкиваешься с этой арифметикой, все становится очевидно.

                                                                        Вот пусть компьютеры и занимаются этой арифметикой, они хорошо это делают. Если компилятор соптимизирует при этом for (i=1;i<=N;++i) в for(i=0;i<N;++i) и внутри себя перестанет вычитать ту единицу — его дело и его право!

                                                                        Людям же надо делать так, как удобно для людей. Вы не переделаете естественные языки и мышление так, чтобы счёт везде (не в компьютере) начинался с 0.

                                                                        Ну а для тех случаев, когда действительно удобнее считать с 0, можно ввести и синтаксис в духе var t: array[0..999] of integer. Нужно такое системщикам — пусть рисуют;))

                                                                        (Да, я сам системщик, и я тут жуткий предатель своего клана.)
                                                                          0
                                                                          Сейчас уже не актуально протягивать индекс в цикле. Уже можно спокойно обойтись конструкциями типа array.foreach() или for (v: array). Вам не нужно беспокоиться о том, какой там индекс. Индекс нужен не для работы со всеми элементами массива, а для работы с отдельными элементами.
                                                                            0
                                                                            > Сейчас уже не актуально протягивать индекс в цикле.

                                                                            Где как. Я регулярно встречаюсь с необходимостью именно такого.

                                                                            > Уже можно спокойно обойтись конструкциями типа array.foreach() или for (v: array).

                                                                            Ага, только потом начинается for v: slice(array, sub_begin, sub_length) или ещё что посложнее. И вот тут все эти индексы всплывают снова.
                                                                            0
                                                                            Вы не переделаете естественные языки и мышление так, чтобы счёт везде (не в компьютере) начинался с 0.
                                                                            А зачем переделывать естественные языки? Достаточно переделать профессиональный. Де-факто это уже произошло.

                                                                            Нужно такое системщикам — пусть рисуют;))
                                                                            Извините, но «системщики» сделали C — для себя. Как им было удобнее. А если кто-то решил его использовать для чего-то ещё — ну так это его выбор, никто ж не заставляет.
                                                                              0
                                                                              > Достаточно переделать профессиональный. Де-факто это уже произошло.

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

                                                                              > Извините, но «системщики» сделали C — для себя. Как им было удобнее.

                                                                              Ну вот и создают альтернативы с другими подходами.
                                                                          0
                                                                          нулевого года нет вообще, что сильно мешает делать правильные рассчёты

                                                                          Я не знаю, что вам мешает делать правильные расчёты, но нулевой год есть (спросите любого астронома), его ещё называют "первый год до нашей эры". (А ещё есть нулевой год до нашей эры — он же первый год нашей эры.)

                                                                            +1
                                                                            В том-то и проблема, что первый год до нашей эры != минус первый год нашей эры.
                                                                          –2
                                                                          Приятно что мои комменты все же читаются, однако жалко что не внимательно, поэтому давайте-ка еще раз. Я не имею претензий к тому что и как нумеруется, я спрашиваю о том, каковы причины этому. Причины. Я понимаю что на этот вопрос ответить сложнее чем просто язвить, однако прошу конкретных ответов, а не просто лишь бы что-то написать.
                                                                        0
                                                                        > f (x) = «ф (а может и икс)».

                                                                        Ни разу не видел такого понимания такой записи в математике. Можете привести какой-то авторитетный источник?

                                                                        Зато пробел тут является как раз историческим явлением и остался в таких записях, как «sin x» вместо «sin(x)» — раньше всё так писали. А сейчас пробел в этой роли (а не скобки) — принципиальный синтаксический элемент математизированных языков вроде Haskell.
                                                                        +8

                                                                        Скобки скобкам рознь. В обычной математческой нотации пробелы вокруг скобок не ставятся. То же самое и в программировании:


                                                                        (a + b) * sin(c) — правильно
                                                                        (a + b)*sin(c) — неправильно
                                                                        (a + b) * sin (c) — неправильно

                                                                          0
                                                                          Однако, мой компилятор говорит, что все 3 строчки правильные. И это потому, что он следует не правилам математической нотации, а стандарту языка программирования. И отличий у этого языка от нотации огромное количество. Например то, что на нем записывается алгоритм. Но с вашей точки зрения скобочка обязательно должна быть приклеена к функции (процедуре?). А то математики не поймут.
                                                                          Количество пробелов это вопрос только привычки, удобства, скорости распознования.
                                                                          Сам использую нотацию ( a + b ) * sin ( c ) (да пробелов не жалею)
                                                                            +1

                                                                            Это не две разных сущности sin и (c), это одна функция sin(c). Скобки являются ее неотъемлемой частью, потому и пишется слитно.
                                                                            Вы же не пишите во - первых с пробелами — со скобками у функции та же история.

                                                                            –2
                                                                            Ясно, спасибо, дак вот я действительно не понимаю почему к программированию приплетается математика, сколько не спрашивал, еще не получил ни единого кейса где она нужна, даже от тех же переменных одно название осталось по сути. Да, осталась только запись математическая, и зачем? Дань традиции просто?
                                                                              –2
                                                                              Программирование — это способ заставить математику помогать людям. Потому программирование без знания математики — всё равно что вождение автомобиля без знания того, как он устроен.

                                                                              В обоих случаях это возможно, в обоих случаях результат — так себе.

                                                                              Если вы хотите просто доехать из точки A в точку B (чёрт, опять математика, да?), то без знания того, как работает двигатель вам можно и обойтись, а если хотите принять участие в ралли Даккар — то нужно будет не просто понять это «в общих чертах», но и научиться его регулировать «в поле».

                                                                              Если вы хотите написать программу, которая вам три числа сложит (хотя зачем вам числа складывать, раз математику вы не знаете?) — то без знания математики вам можно и обойтись, а если программа что-то делает с нетривиальными объёмами данных — то уже нет.
                                                                                –1
                                                                                Хрена Вас заминусовали! Видать народ уже сам запутался… То-то и расстраивает что одного понимания среди аудитории нет, и главное объясни что не так, но нет, минус влепить намного легче чем напрягаться и что-то пояснять…

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

                                                                                А примеры можно привести? Одно дело спортивное программирование for fun, а другое дело повседневная разработка.
                                                                                  +1
                                                                                  А примеры можно привести? Одно дело спортивное программирование for fun, а другое дело повседневная разработка.
                                                                                  Пресловутого Шлемиэля можно вкрутить куда угодно, если не думать. Я как-то работал с программой создания DVD — и видя, что у неё на создания двуслойного DVD уходит час (при том, что однослойный создаётся за 15 мин) я очень хорошо понимал, что её создал кто-то, кто разбирался в видео, но решил, что для програмирования — математика не нужна. Я даже мог бы, примерно, объяснить как написать тот цикл, который автор запрограммировал непаравильно — но, увы, исходников от программы не было…
                                                                                    0
                                                                                    Ясно, спасибо, если для этого действительно нужна математика — окей, но по мне дак возможно проблема в оптимизации кода, может какой-то цикл бесконечный крутится потому-что разраб в своем же коде запутался, и т. п.
                                                                                      0
                                                                                      Разраб просто тупо не понимает, что обращаться к элементам структуры, которые, в сущности, являются двусвязным списком, по индексу — неэфективно.

                                                                                      А как вы собрались «оптимизировать код» без математики — для меня полная загадка. Переписывать на ассемблер? Или на GPU перевести?

                                                                                      Что вы будете делать, если вы понятия не имеете — как время вашей программы будет себя вести при переходе от DVD-5 к DVD-9 и далее к разрым стогигабайтным Blu-Ray'ам? И точно ли то, что вы сделаете что-то ускорит.

                                                                                      Тот факт, что создание качественных, быстрых и надёжных программ себя не окупает — да, таки имеет место быть. Иначе бе пресловутый Slack уже давно обанкротился бы.

                                                                                      Но, тем не менее, подход «а теперь для решения задачи, которую когда-то мы решали на C64 — купите пожалуйста рабочую станцию со 128GB оперативки и RAID SSD» работает не всегда. Иногда (к сожалению достаточно редко) заказчики всё-таки задают вопросы на тему «а почему ваше поделие жрёт столько ресурсов и нельзя ли с этим что-нибудь сделать?»…
                                                                                        –1
                                                                                        Я почему и попросил реальных примеров применения математики в программировании, потому-что я веб-разраб, там я ни разу не сталкивался с тем, что мне могла понадобиться математика, на досуге еще пишу для Java, там картина аналогичная. Но я и сам против раздувания проектов, и предпочитаю даже свою библиотеку написать если вижу, что проект разросся раз десять после того, как я добавил дефолтную библиотеку для работы с гуглодиском, в итоге моя библиотека занимает пару листов A4, размер же официальной в несколько раз превышает размер всего моего проекта, хотя выполняет те же самые функции. Просто надо знать как решать задачи оптимально, и дело тут не в математике. Нет, опять же, я сужу это по своим областям, если она нужна в какой-нибудь робототехнике — тут я без претензий.
                                                                            +3
                                                                            Аррррр, пробелы перед скобками, аж душа радуется.

                                                                            А не слишком ли вы эмоционально относитесь к оформлению кода? В разных проектах — разные требования к расстановке пробелов и прочему оформлению. Если вы так переживаете из-за пробелов, что с вами будет, когда вы прочитаете в coding style guide'е, скажем, про расстановку фигурных скобок? :)

                                                                            Кстати давно меня интересует, да все как-то речь не заходила, почему в 95% случаев перед скобками никто не ставит пробелов?

                                                                            У вас тут две закавыки:
                                                                            1. 95% вы, очевидно, взяли из собственного опыта. Потому что у меня, например, опыт обратный: 95% кода, в который я смотрю каждый день, написан именно с пробелом перед открывающей скобкой.
                                                                            2. Цель пробелов (и вообще whitespace'ов) в коде — облегчить понимание кода для человека. Пробелами визуально отделяют значимые части кода. Но делать это можно разными способами: до открывающей скобки или внутри скобок:
                                                                            — if (a) или
                                                                            — if( a ).
                                                                            Лично мне более приятен второй вариант, но пишу я все равно так, как сказано в гайдах.
                                                                            И кстати, с появлением редакторов с подсветкой синтаксиса можно вообще пробелы не ставить — редактор сам визуально выделит ключевые места. Некоторые граждане этим злоупотребляют.

                                                                            Ведь ни у кого даже и мысли не возникает не ставить их на письме, ведь что это такое?

                                                                            Пфф… Вы когда последний раз в чатиках всяких были? Там сплошь и рядом пробелы ставят от балды или вообще не ставят. И ничего, массовых волнений не наблюдается.

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

                                                                            Вызов функции неотделим от ее аргументов. Поэтому ставить пробел между именем функции и открывающей скобкой — значит, отделять ее визуально от ее аргументов, что не очень логично. Даже в тех конторах, где пробел перед открывающей скобкой прописан в гайдах, пробел между именем функции и ее аргументами обычно не ставится (субъективное утверждение, основанное на индивидуальном опыте).
                                                                            Операторы же — отдельная песня, их нужно визуально выделять. Они, как правило, коротки (if, for, do, while), и без отбивки пробелами могут потеряться в тексте. Поэтому их и выделяют.

                                                                            Есть ли какое-либо объяснение этому кроме как что это бездумное употребление каких-то пережитков прошлого?

                                                                            Оформление кода должно помогать человеку правильно воспринять текст программы. А поскольку восприятие у людей неоднозначное и субъективное, постольку и единых правил оформления кода не существует. Попытки унифицировать оформление были неоднократно (см. питон), но вывести единый и универсально-понятный стиль пока никому не удалось.
                                                                              0
                                                                              > Если вы так переживаете из-за пробелов, что с вами будет, когда вы прочитаете в coding style guide'е, скажем, про расстановку фигурных скобок? :)

                                                                              Ну вообще мне довелось столкнуться с местом, где применялся откровенно дурной стиль просто потому, что главному начальнику так хотелось. Там ещё и другие проблемы были — категорический запрет на описание происходящего в стиле doxygen, настаивание на негрепабельных именах типа a, b, e, и так далее, запрет на осмысленные комментарии в CVS…
                                                                              так что местные законы стиля до какой-то меры терпимы, но выше неё — нет, лучше не связываться с такими.
                                                                              Но порог у каждого свой.

                                                                              > Потому что у меня, например, опыт обратный: 95% кода, в который я смотрю каждый день, написан именно с пробелом перед открывающей скобкой.

                                                                              Это проприетарный код или открытый?

                                                                              > Цель пробелов (и вообще whitespace'ов) в коде — облегчить понимание кода для человека.

                                                                              Или, надо таки упомянуть, однозначность парсинга — например, в C последовательность a/*b требует пробела, несмотря на все задумки программиста. Или vector<vector<int>> до C++11. Но в последнем случае вообще диверсией было изначально использовать <> для этой цели (и нам с её последствиями жить ещё лет 50).

                                                                              > Попытки унифицировать оформление были неоднократно (см. питон), но вывести единый и универсально-понятный стиль пока никому не удалось.

                                                                              Питон не самый показательный (на самом деле обычно код на нём следует PEP8 чуть менее, чем везде). Вот `go fmt` тут ближе. Но, честно говоря, их выбранный стиль не сильно паршив :)
                                                                              0
                                                                              А что хорошего-то в таких пробелах?
                                                                              Местами они рекомендуются — например, в классическом стиле GNU будет именно так:
                                                                              copyStream (readStream (inputStream));


                                                                              ещё долго писал с пробелами внутри, когда-то больше всего нравилось:
                                                                              copyStream( readStream( inputStream ) );
                                                                              sin( x + y * ( c - 1 ) );
                                                                              

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

                                                                              А ваш сарказм таки неуместен.

                                                                              > после операторов (неожиданно), пробелы ставятся в большинстве случаев

                                                                              А это отдельная тема. Если обратите внимание на новые языки (Go, Swift...), там скобки вокруг вложенных тел обязательны (даже при одном операторе в них), а вокруг условий в if, while — нет, то есть

                                                                              if a + b > c {
                                                                                run();
                                                                              }
                                                                              


                                                                              к первому есть очень серьёзные причины (задолбались отлаживать, и грамматика проще), ну а второе само из этого получается.
                                                                                0
                                                                                просто задолбало стучать клавишей пробела
                                                                                Когда можно ожидать что Вы задолбаетесь «стучать клавишей пробела» в обычной переписке, где пробелов больше в разы?)

                                                                                А ваш сарказм таки неуместен
                                                                                Перечитал свои комменты, но так и не смог понять где там сарказм, вроде совершенно серьезно пишу. Наверное оттого и минусы — что-то домыслили, чьи-то чувства заделись, и пошло-поехало. Но минусовать уже пошли даже тех, кто доказывает что это все пошло из математики, видать народ уже сам запутался. Приехали(
                                                                                  0
                                                                                  > но так и не смог понять где там сарказм

                                                                                  Ну вот пассажи типа такого

                                                                                  >> Лень пробел нажать, или на перфокарты запись идет и места нет? Давайте тогда и между словами пробелы не ставить, это же пустая трата времени)

                                                                                  или даже последнего

                                                                                  > Когда можно ожидать что Вы задолбаетесь «стучать клавишей пробела» в обычной переписке, где пробелов больше в разы?)

                                                                                  Может, филологи это как-то иначе бы назвали, но мне что-то не приходит на ум.

                                                                                  И я не минусовал (нет прав, но даже если бы были, за это бы минус не ставил).

                                                                                  По сути:

                                                                                  > Когда можно ожидать что Вы задолбаетесь «стучать клавишей пробела» в обычной переписке, где пробелов больше в разы?)

                                                                                  Никогда, наверно — там они таки функциональны. А вот внутри скобок — нет, и перед скобкой — как-то тоже (просто эстетически сравнил подходы — не увидел преимущества у пробела между именем функции и скобкой).
                                                                                    0
                                                                                    Нет, ну а что, не ставил целью своей язвить, просто Вы сами сказали что задолбались бить по пробелу в программировании, дак этот вопрос сам собой и напрашивается, ибо почему-то в простынях на лист A4 бить по пробелу Вы (и никто другой) не задалбываетесь)
                                                                              –4

                                                                              Нормально ошибка ловится взглядом, но для этого, наверное, надо не знать стандартную библиотеку С или успеть за 20 лет её забыть.


                                                                              Взгляд споткнулся как раз о isspace(c) && ('\n' != c). Про isspace прочитать ещё не успел, исходил из названия и персонального здравого смысла. Если символ пробельный (собственно пробел или, в крайнем случае, табуляция), то он не может быть одновременно переводом строки. Соответственно, объединение по И выглядит подозрительно. Я предположил, что, возможно, имелось в виду ИЛИ; а даже если нет, то тут в любом случае какая-то фигня.

                                                                                0
                                                                                например символ 'А' одновременно является ни пробелом, ни табуляцией, ни переводом строки, ни…
                                                                                  –3

                                                                                  В условии содержится логическая ошибка. На примере "пробела" она очевидна. Код уже неправильный, и рассматривать его поведение дальше на других наборах данных просто нет смысла. Сначала надо исправить.

                                                                                    +2
                                                                                    Да прочитайте уже статью!
                                                                                    Код был бы вполне корректным (очень корректным, если угодно), если бы не макрос
                                                                                    P.S.
                                                                                    кстати,
                                                                                    #ifndef isspace
                                                                                    #error кто-то украл isspace из стандартной библиотеки
                                                                                    #endif
                                                                                    сделало бы его корректным даже с макросом
                                                                                  +4
                                                                                  Вот только переводы строки всегда считаются пробельными символами…
                                                                                    +1

                                                                                    Я не заметил бы ошибку, если бы функция назвалась iswhitespace. Но она называется так, как называется. ((c)==' ' || (c) == '\t') — примерно так я и представлял себе её реализацию. Авторы MC тоже.

                                                                                      +2
                                                                                      Как видим не все. Некоторые знают стандарт…
                                                                                        0

                                                                                        Об этом и говорю. Чтобы видеть ошибку (не важно, кто в ней виноват), надо забыть или не знать стандартную библиотеку С, в которой сэкономили на слове white внутри isspace.

                                                                                          0
                                                                                          А ещё нужно думалку отключить, да. В 70-е/80-е типичное ограничение на длину идентификатора была 8, а иногда, вообще 6. Соотвественно iswhitespace эта функция называться никак не могла. Предположить же, что пропуск пробельных символов — это что-то новое, оригинальное, порождение 90х — это нужно иметь очень больное воображение.
                                                                                            0

                                                                                            Думалку отключать вредно. Функция легко могла называться iswspace. Там есть намного более страшные имена и ничего.

                                                                                              0
                                                                                              Это whitespace или, например, writablespace?
                                                                                                +2
                                                                                                iswspace — это как бы версия isspace для работы с wchar_t :-)
                                                                                            +2
                                                                                            Так оно наоборот получается — это доработку mcʼшников надо было назвать iswhitespace() или как-то похоже (а, на самом деле, лучше вообще именем, которое даже под ночной усталостью не будет похоже на имя из libc).
                                                                                    +1
                                                                                    А разве оно не зациклится на пустом файле или в конце файла?
                                                                                      +3
                                                                                      isspace вернёт false и выведет из цикла, в этом смысле всё нормально. Да, я тоже сначала именно это заподозрил.
                                                                                      +1
                                                                                      Если символ пробельный (собственно пробел или, в крайнем случае, табуляция), то он не может быть одновременно переводом строки
                                                                                      Перевод строки считается пробельным символом. Именно поэтому добавили исключение.
                                                                                        –1
                                                                                        А вот нечего было функцию в define закидывать:
                                                                                        // C99
                                                                                        extern inline int my_isspace(int ch) {
                                                                                          return (ch == ' ') || (ch == '\t');
                                                                                        }
                                                                                        

                                                                                        Так бы и компилятор пожаловался, если бы встретил название из стандартной библиотеки.
                                                                                          +6
                                                                                          Он и так жаловался. :)
                                                                                          #ifdef isspace
                                                                                          #undef isspace
                                                                                          #endif
                                                                                          +2
                                                                                          А ведь на самом деле не допустить подобную ошибку (или легко найти ее во время анализа) было бы очень просто, если бы проект придерживался строгого стандарта кодирования, в котором было бы явно сказано, что идентификаторы всех кастомных макросов во всех случаях пишутся по определенному правилу, например, апперкейсом и с прочерками (типа IS_SPACE(x)). И даже возжелавший проверить наличие стандартной функции в реализации библиотеки или переопределить ее поведение, вынужден был вынести это в макрос, именованный по правилу, и это сразу бросилось бы в глаза на ревью.
                                                                                            +2
                                                                                            Ответ нет. Ничего не мешает переопределить функцию, в glibc большая часть все равно weak алиасы. Как и во многих других реализациях libc.
                                                                                              0
                                                                                              От переопределения weak-символа не спасет (кстати интересный вопрос, а отловит ли подобное PVS-Strudio? вполне вероятно что нет),
                                                                                              а вот от макросов, маскирующихся под функции со всеми вытекающими последствиями — вполне.
                                                                                            0
                                                                                            Я не программист, только учусь. Поясните, а где тут проверка на EOF?
                                                                                              +3
                                                                                              isspace (как настоящий, так и поддельный) не посчитает EOF как пробельный символ и цикл остановится.
                                                                                              0

                                                                                              Не скажу конкретно про этот случай, но tidy казалось бы тоже умеет в такой ворнинг. Я не к тому, что вы не молодцы. А к тому, что показывать такие вещи нужно всегда в разрезе конкурентов, т.к. статические анализаторы для C/C++ в текущем временном отрезке доступны в ассортименте, и имеются бесплатные аналоги.


                                                                                              Гораздо интереснее было бы сравнивать diff на не false possitive с основными соревнующимися товарищами. Как минимум с clang-tidy.

                                                                                                +2
                                                                                                Спасибо за комментарий. Всё правильно написали. Но.

                                                                                                Я зарёкся делать сравнения :). Чем тщательнее мы их делали, тем больше нас обвиняли в… как бы это помягче сказать то… В предвзятости и неадекватности сравнений! Можно конечно кому-то заплатить, чтобы он написал независимую статью. Но только тогда скажут, что она проплаченная :).

                                                                                                Предлагаю написать такую заметку. Вам или любому желающему. Я серьезно буду рад и благодарен. Только просьба не повторять подобные недоработки при сравнении. Или такие :).

                                                                                                Про Clang могу только сказать, что каждый раз проверяя код компилятора, мы находим там ошибки (1, 2, 3).

                                                                                                Что ещё… Cppcheck? Он вообще не работает :). Недавно хотели добавить его в ночные прогоны, чтобы и он помимо других инструментов, код нашего анализировал проверял. Он просто не парсит множество файлов (мы пишем на современном C++). Пришлось выбросить.
                                                                                                  –1
                                                                                                  Про Clang могу только сказать, что каждый раз проверяя код компилятора, мы находим там ошибки
                                                                                                  ну теперь вы просто обязаны написать статью об ошибках в своём коде
                                                                                                    +1
                                                                                                    Q: Проверяете ли вы код PVS-Studio при помощи PVS-Studio?
                                                                                                    A: Конечно! Более того, в случае обнаружения ошибок, список виновных предаётся огласке с их последующим отлучением от холодильника с мороженым.

                                                                                                    habr.com/ru/company/pvs-studio/blog/431086

                                                                                                    Всё уже отвечено до нас.
                                                                                                      0
                                                                                                      И где же статья про ошибки в коде PVS-Studio? По ссылке вижу только Вопросы-Ответы, один из которых говорит о том, что код PVS-Studio проверяется с помощью PVS-Studio

                                                                                                      Всё уже отвечено до нас
                                                                                                      но я не задавал вопросов!
                                                                                                        0
                                                                                                        Проверяется, верно. И проверяется регулярно, что подтверждается здесь: www.viva64.com/ru/a/0085/#ID0E2QAE
                                                                                                        И авторы нас активно убеждают (я склонен им здесь поверить), что регулярное использование статического анализатора не оставляет ошибкам, которые он отлавливает, возможности добраться до момента, когда они накопятся на статью.
                                                                                                          0
                                                                                                          И авторы нас активно убеждают (я склонен им здесь поверить), что регулярное использование статического анализатора не оставляет ошибкам, которые он отлавливает, возможности добраться до момента, когда они накопятся на статью.
                                                                                                          Совершенно верно. Но однажды был случай, когда по недосмотру часть кода не проверялась. Результатом стала вот эта заметка :). Проверяем исходный код плагина PVS-Studio с помощью PVS-Studio.
                                                                                                    0
                                                                                                    «Или такие». Ссылочку пропустил :). Народ против PVS-Studio: дубль первый (см. комментарий).
                                                                                                  0
                                                                                                  Я бы предложил добавить в анализатор следующие инспекции:
                                                                                                  • Имя макроса совпадает с идентификатором стандартной библиотеки
                                                                                                  • Имя макроса совпадает с любым уже имеющимся идентификатором
                                                                                                  • Вместо макроса рекомендуется использовать inlinе функции и константы (вроде, в современном C они уже появились?)


                                                                                                  P. S. Я не из мира С, так что сильно не критикуйте, если что-то не в кассу.
                                                                                                    0
                                                                                                    Написал заметку в которой развиваю мысль о вреде макросов: Вред макросов для C++ кода.

                                                                                                    Only users with full accounts can post comments. Log in, please.