fk0, легенда (08.11.2015 00:43, просмотров: 1119) ответил Alex B. на Не-не, разница между нами в том, что когда у тебя кончается компетенция и аргументы — ты переходишь на личности.
Комментирую часть MISRA rules, сами они лежат на сахаре совершенно бесплатно... Rule 6.3 (advisory): typedefs that indicate size and signedness should be used in place of the basic numerical types.
Использование самодельных типов данных чревато багами, варнингами на пустом месте (в scanf и printf, и с ними трудно бороться в проекте с которым работает толпа народу), неэффективным кодом, трудностями в собственно программировании (какой аналог INT_MAX для самодельного типа?), и большими проблемами при переезде с 32 на 64 бита, например (когда указатели кладут в LONG тип который 32-битный внутри, например). Современный C даёт (в inttypes.h) все необходимые типы, которые и нужно использовать когда нужно. И если нет особой нужды, то нужно использовать знаковые типы (потому, что можно нарваться на алгебраические фокусы с беззнаковыми, исключение для сдвига вправо который лучше беззнаковый) и по возможности обычный int для всего (вместо uint32_t и т.п.), long если нет уверенности, что влезет в 16-битный int.
Rule 7.1 (required): Octal constants (other than zero) and octal escape sequences shall not be used.
open("filename", O_RDWR, 0644) поймёт любой дурак. А записанное в десятичном виде оно превращается в фанатастический глюкодром. Есть вещи которые исторически принято использовать в восьмеричном виде.
Rule 9.1 (required): All automatic variables shall have been assigned a value before being used.
Современные компиляторы дают предупреждение в таком случае (чтение неинициализированного значения). Отладчики вроде valgrind и drmemory тоже дают предупреждение. А тупое следование этому правилу (насильное зануление переменных сразу в декларации, например) лишает всех предупреждений и оставляет баги не выявленными! Правило вредоносное!
Rule 10.1 -- 10.5 -- по меньшей мере делает код не читаемым, и насильное приведение типов (что они заставляют делать) ведёт к тому, что компилятор опять же отключает предупреждения. Которые зачастую покрывают большинство опасностей, против которые данные правила придуманы.
Rule 10.6 (required): A "U" suffix shall be applied to all constants of unsigned type.
Просто маразм. Нормальный компилятор всегда сообщает (для этого для проекта настраиваются адекватные Warning опции, разумеется) о несоответствии размеров (если 32768, например, присваивать unsigned short).
Rule 11.1 (required): Conversions shall not be performed between a pointer to a function and any type other than an integral type.
Здорово, но есть интерфейсы где принципиально передаётся только void* или только intptr_t. Типичный пример в libpthreads. И что теперь, не использовать указатели на функции? Но можно создать объект и положить в него указатель на функцию. А за сроком жизни объекта тоже следить нужно и это только ведёт к новым проблемам.
Rule 11.2 (required): Conversions shall not be performed between a pointer to object and any type other than an integral type, another pointer to object type or a pointer to void.
Конверсия типов указателей массово и широко используется. Разумеется нужно головой думать, что делаешь. Но без этого C превращается в бейсик. И вообще следуя этому правилу что угодно можно привести к void*, а потом к чему-то ещё, чему угодно другому. Т.е. правило бессмысленно.
Rule 11.3 (advisory): A cast should not be performed between a pointer type and an integral type.
Не пришлось бы выдумывать это правило, если бы пользовались стандартными типами, а не самодельными (ибо есть intptr_t для того, который 64-битный на 64-битной платформе).
Rule 11.5 (required): A cast shall not be performed that removes any const or volatile qualification from the type addressed by a pointer.
А const_cast в C++ дураки изобрели, можно подумать... Есть ситуации когда нужно убрать const, сознательно. Не копировать же целиком большой объект из-за того, когда известно, что он на самом деле в RAM лежит. Кстати это правило бессмысленно, ибо компилятор даст варнинг или ошибку, потому и const_cast существует (DECONST macro для plain C).
Rule 12.1 (advisory): Limited dependence should be placed on C's operator
precedence rules in expressions.
Куча глупых скобок делает код не читаемым. Код должен быть не только соответствующим стандартом, но важно, чтоб легко читался глазами. Скобки нужно расставлять когда компилятор даёт варнинги или когда это принципиально необходимо. А не в каждом операторе.
Rule 12.7 (required): Bitwise operators shall not be applied to operands whose underlying type is signed.
Проблема возможна только со сдвигами. Глупо из-за этого половину переменных делать беззнаковыми (что ведёт к другим ошибкам).
Rule 12.10 (required): The comma operator shall not be used.
Особенно доставляет. Стандарт языка разрабатывали полные идиоты, которые четвёртый десяток лет тащат comma operator во все стандарты, а в C++ позволяют даже перегружать. Так что ли? Comma operator как минимум полезен в операторах цикла. Да и нет ничего зазорного, чтоб через запятую присвоить несколько переменных, например. Вообще смысл оператора "запятая" -- дать возможность выполнить _последовательность_ вычислений в месте, где нужно одно выражение. Иногда это нужно. Например, в макросах-фунциях. Которые в свою очередь могут быть использованы в другом выражении.
Rule 12.13 (advisory): The increment (++) and decrement (--) operators should not be mixed with other operators in an expression.
В этом их основной смысл! Чтоб писать штуки вроде *ptr++. Можно конечно писать по оператору в строчке как на ассемблере, но почему бы сразу не взять ассемблер... Повторюсь, важно, чтоб код был читаемый.
Rule 13.1 (required): Assignment operators shall not be used in expressions that yield a Boolean value.
Если бы разрешили оператор "запятая", то конечно, не пришлось бы писать говнокод с присваиваниями и варнингами на них: if (x=y) --> if (x=y, x!=0).
Rule 13.4 (required): The controlling expression of a for statement shall not contain any objects of floating type.
"Не использовать числа с плавающей точкой для циклов". Бред? Бред. У меня может алгоритм принципиально предполагает именно такое использование. Например, вычисление квадратного корня методом Ньютона.
Rule 14.4 (required): The goto statement shall not be used.
Чушь из методички для студентов. Иногда goto необходим и именно поэтому он есть в языке. Типичное применение -- выход из вложенных циклов, обработка ошибок.
Rule 14.5 (required): The continue statement shall not be used.
continue statement нужен для good structured programming. И он есть в 90% языков программирования.
Rule 14.6 (required): For any iteration statement there shall be at most one break statement used for loop termination.
Аналогично. Вместо break, goto и continue иметь говнокод с 20ю разными переменными-флагами -- это лучше что ли? В методичке для студентов, конечно, goto лучше не упоминать... Складывается впечатление, что правила MISRA писались для индусов или студентов.
Rule 14.7 (required): A function shall have a single point of exit at the end of the function.
Плохое правило, из той же области, что goto. Иногда удобней иметь несколько return в начале, а не выписывать 10 фигурных скобочек с if (потому, что текст программы должен быть читаемым и умещаться в 80, ну пусть хотя бы в 120 колонках). Иначе получается индусский нечитаемый код.
Rule 14.8 (required): The statement forming the body of a switch, while, do ⦠while or for statement shall be a compound statement.
Rule 14.9 (required): An if (expression) construct shall be followed by a compound statement. The else keyword shall be followed by either a compound statement, or another if statement.
У меня нет сил это комментировать уже. Всё это ведёт к нечитаемому индусскому стилю кодирования. Лично я бы рекомендовал ознакомиться с google style guide.
Rule 15.0 (required): The MISRA C switch syntax shall be used.
The syntax for the switch statement in C is weak, allowing complex, unstructured behaviour.
switch() в языке C это никакой не switch (как он есть в Javascript или Tcl), а самый что ни на есть goto. Разумеется с ним возможны разные трюки, например protothreads. MISRA-C на этом ставит крест. И иногда они нужны (Скрипачу на заметку). И кроме того, опять же индусский нечитаемый код. Иногда нужен простой switch на пару операторов, без фигурных скобочек на 2 страницы.
Rule 15.2 (required): An unconditional break statement shall terminate every
non empty switch clause.
fall through в switch-операторе -- типичный приём программирования. Конечно, это может быть ошибкой, но это удобный приём. Вменяемые программисты могут писать /* no break here */ вместо break.
Rule 16.1 (required): Functions shall not be defined with a variable number of arguments.
Ага, а как сделать printf() или аналог вроде паскалевского Write() ? Видимо никак. Дальше говорить не о чём, ибо printf() -- это фундамент C-программирования и используется в любой сколько-нибудь серьёзной программе. А в современном C++ есть ещё темплейты с переменным числом аргументов (ну и макросы в C99), и к тому же рекусивные... Наверное это не просто так? Наверное это кому-то нужно?
Rule 16.2 (required): Functions shall not call themselves, either directly or indirectly.
Т.е. для реализации рекурсивного алгоритма нужно организовать стек вручную... И отлаживать этот говнокод годами. Когда современные компиляторы очень даже оптимизируют многие рекурсии (tail recursion всегда). Классика жанра qsort() -- на самого Кнута замахнулись.
Rule 17.1 (required): Pointer arithmetic shall only be applied to pointers that address an array or array element.
Очевидно, что иногда нужно просто работать с адресами как есть. Иначе C превращается в бейсик. Это правило не даёт это делать. 99% C-программ (не студенческих) нарушают это правило.
Rule 18.3 (required) An area of memory shall not be reused for unrelated purposes.
Это правило ввели потому, что вначале запретили malloc(), и тогда программисты стали хранить в массиве что-то одно в один момент времени, и что-то другое в другой момент. Потому, что всю память через static не распределить, памяти не напасёшься...
Rule 18.4 (required): Unions shall not be used.
А другого способа решить проблемы невыравненного обращения в C нет. Ну только если руками побайтно считать. Но если это native endian, то его нужно наперёд знать ещё. Кроме того, union существует не просто так и иногда действительно нужен (там же variant records). Когда-то можно его заменить структурой, а когда-то нет.
Rule 19.6 (required): #undef shall not be used.
А иногда это принципиально нужно, когда нужно переопределить макрос. Как спрашивается быть? Макрос может быть определён в таком месте, где поменять его нельзя. Кроме того, может быть вариант, вначале #define default, а потом, в зависимости от прочих условий, #undef и #define что-то конретное.
Rule 19.15 (required): Precautions shall be taken in order to prevent the contents of a header file being included twice.
#define FILE_H лучше писать в конце, перед #endif -- это обнаруживает случаи рекурсивного включения (а если вначале -- не обнаруживает), обычно не желательные (хотя иногда так и нужно).
Rule 20.4 (required): Dynamic heap memory allocation shall not be used.
Ну тут и сказать нечего. В любой современной программе используется dynamic memory allocation. У dynamic memory allocation есть ряд преимуществ, по сравнению с static -- нарушения границ обнаруживаются хотя бы. Если программа (функция) времякритичная, то для работы память, разумеется, должна быть выделена заранее. Разумеется возможны утечки и фрагментация. Про последнюю можно сказать, что она обычно замечается в низкокачественных C-библиотеках коммерческих компиляторов в области embedded. Типовая libc в linux, windows, профессионального уровня отдельные аллокаторы фрагментации мало подвержены (ибо там не используется first fit вот так в лоб). Но зачастую, в больших проектах, без динамической аллокации вовсе не обойтись. Просто памяти не хватит (т.к. одновременно она вся не нужна, но если просуммировать все возможные аллокации, то получится сильно больше, чем есть физического ОЗУ). Кроме того, непонятно где ещё память выделять -- принципиально невозможно всю память распределить заранее (кроме простых маленьких проектов). Самодельный аллокатор зачастую же будет во всём хуже, чем библиотечный malloc. Кроме того, для malloc есть развитые средства отладки (valgrind, drmemory, duma, dmalloc, ASAN в последних gcc). А для статической памяти или стека с отладкой не густо и ошибки в стеке или статической памяти обычно очень сложно выявить.
Rule 20.5 (required): The error indicator errno shall not be used.
Гениально. Игнорировать все ошибки? Хорошенькая рекомендация.
В ряде случаев ошибку можно понять только через errno (GetLastError() в windows). Эти функции существуют не просто так (да, errno на самом деле в "большой" ОС это макрос и функция, из-за TLS).
Rule 20.6 (required): The macro offsetof, in library <stddef.h>, shall not be used.
Посчитать руками смещение? Говнокод как он есть, вроде ж очевидно. Завтра компилятор сменится и всё с начала.
Rule 20.7 (required): The setjmp macro and the longjmp function shall not be used.
Эти функции для чего-то существуют. Вот например для реализации кооперативной ОС, недавно только писали на сахаре. Рекомендация из области "не использовать goto", против студентов.
Rule 20.8 (required): The signal handling facilities of <signal.h> shall not be used.
Может ещё сигналы не использовать? Но они тоже зачем-то есть в unix уже 4-й десяток лет и никто их не собирается отменять. Рекомендация в стиле "не использовать goto". Но некоторые вещи без сигналов не сделать. Как прервать select(), например, если я хочу сокет закрыть? Или как прервать accept() ?
Rule 20.9 (required): The input/output library <stdio.h> shall not be used in
production code.
Гениально. А что использовать вместо? Вместо отлаженной и надёжной C-библитеки должен написать свой говнокод? Приличные C-библиотеки пишутся десятками людей и за десятилетия. Я смогу написать свою C-библитеку за время разработки проекта? Или я должен заниматься разработкой C-библитеки вместо решения непосредственной задачи? Или я должен напрямую использовать функции ОС? Но если я использую функции libc у меня код более менее переносится между всеми ОС, а если я испольузую функции windows, то я не запущу на linux, и наоборот.
Всегда когда можно использовать библиотечную функцию (надёжную и отлаженную) вместо собственного говнокода -- нужно её использовать.
Rule 20.10 (required): The library functions atof, atoi and atol from library <stdlib.h> shall not be used.
Но они же запретили использовать errno. А без этого strtol(), например, тоже бесполезен (тоже ошибка не видна). Хотя вообще можно вначале проверить, что введены цифры и их не больше чем (переполнение), тогда эти функции вполне безопасны.
Rule 20.11 (required): The library functions abort, exit, getenv and system from library <stdlib.h> shall not be used.
Бред полнейший. А как мне получить переменные окружения? Тоже не использовать?
А завершить аварийно программу как? И наконец, почему assert() вызывающий abort() можно, а сразу abort() нельзя? Ясно, писали менагеры, которые ничего не понимают сами о чём пишут.
Rule 20.12 (required): The time handling functions of library <time.h> shall not be used.
Ага. И я должен потратить пару месяцев на разработку аналогов этих функций. И ещё пару месяцев на отладку. Если говорить о полноценной libc, то все ньюансы связанные с таймзоной ещё и не пару месяцев могут занять. И потом я где-нибудь налажаю с проблемой 2000-го года ещё. Да, embedded библиотека может не знать последних указов Путина и Медведева, но переменную TZ понимать вполне может (man tzset). Повторюсь: всегда когда можно использовать библиотечную функцию (надёжную и отлаженную) вместо собственного говнокода -- нужно её использовать
[ZX]