Ну тут можно еще добавить такие операции, как перетасовку содержимого файлов в случайном порядке, архивирование сотен файлов и посылку их на FTP, автоматическую массовую замену идентификаторов на имена персонажей из LotR, проигрывание MP3 музыки из файлов проекта с соответствующим расширением и еще бесчисленное множество разнообразных действий, которые никому на самом деле не нужны за пределами статистической погрешности.
И это при том, что "добавление и исключение сотен .с файлов" не представляет никакой сложности и в IDE.
O_o. Все повседневная разработка прикладного ПО ведется именно в IDE, ибо именно такой мгновенной интегрированной доступ к средствам сборки и отладки абсолютно необходим для эффективной разработки. IDE и родились из требований разработчиков. Повседневная разработка без IDE сегодня - это упрямое пожирание кактуса.
Именно из-за того, что MS Visual Studio с гигантским отрывом является самой мощной, удобной и эффективной IDE на сегодняшний день, и сложилась нынешняя парадоксальная ситуация, когда разработка прикладного ПО под все целевые платформы в 99 случаях из 100 делается под Windows в Visual Studio. Как это ни удивительно, но никто за рамками статистической погрешности не разрабатывает прикладные продукты для Линукса под Линуксом. Все разрабатывается под Windows и лишь интегрируется и тестируется на целевой платформе.
В MSVC никто вам не мешает сразу инициировать процесс сборки классического MSVC-шного проекта из командной строки. Никакой мышкой водить никуда не надо.
Это даже не говоря о том, что доминирование Windows и MS Visual Studio, как инструмента повседневной разработки для всех целевых платформ привело к тому, что средства автоматической конвертации проектов Visual Studio в makefile или любой другой целевой формат доступны в огромном количестве. Стандартный flow для прикладного ПО под Linux: повседневная разработка в MSVS под Windows -> автоматическая конвертация vcxproj в makefile -> сборка и тестирование под Linux.
Программирование MK, как впрочем и любое программирование, нацеленное на конкретный экземпляр или класс хардвера, всегда и везде будет делаться с существенным привлечением нестандартных/надстандартных гарантий и возможностей, предоставляемых конкретным компилятором. Цитировать запреты стандарта при это смысла нет. Если конкретная реализация С++ позволяет вам брать адрес main и вам это действительно нужно - берите его. Это в одинаковой мере относится и к С, и к С++, и к любому другому ЯВУ.
В частности, с педантичной точки зрения фразы типа "Линукс написан С" являются бессмысленными, ибо на стандартном С невозможно реализовать ОС. Все прекрасно понимают, что в такой реализации будут использоваться нестандартные/расширенные возможности конкретного компилятора. Это же в равной мере относится и к программированию МК.
А кто гарантирует, что он будет действительно фиксом для "всего кода"?
Если выделение функций делалось механически "по повторяющемуся коду", а не путем продуманного выделения абстракций, то вполне запросто может получится, что "фикс в этой функции" окажется фиксом для одного контекста ее вызова, но багом для других контекстов ее вызова.
Когда эта ситуация обнаруживается, как правило начинается дополнительный "фиксинг" путем введения "странных параметров" для данной функции. Это параметры, которые не несут никакой осмысленной абстрактной семантики, а служат лишь для идентификации места, из которого вызвана функция. Им, как правило, дают какие-то ничего не говорящие странные имена, вроде mode, passили for_johnny.
А в реальности, на самом деле, просто не надо было выносить этот, пусть даже немного повторяющийся код в отдельную функцию.
Нет, такого простого критерия, как "код используется более одного раза", никогда не существовало, кроме как у нерадивых рукопопов.
Разбиение кода на функции по любому формально-механическому принципу - это грубый, кривой и косой антипаттерн, который только сделает код нечитаемым. К этой категории относится и "код используется более одного раза", и "код содержит слишком много строк" и прочие подобные механические критерии.
Разбиение кода на функции всегда делается исключительно по признаку выделения естественных, хорошо очерченных параметризованных абстракций. Функция должна делать что-то одно, что-то понятное, что-то легко описываемые в нескольких словах. При этом функция должна быть более самостоятельна, чем вызывающий ее код, то есть она должна обладать дополнительной ценностью - быть потенциально полезной и в других местах кода, а не только в тех в которых она вызывалась в момент своего создания. Это - важный критерий того, что функция имеет право на существование. Также хорошим признаком правильно выделенной функции является то, что для такой функции легко придумать название.
Если в вашем коде не получается выделить подобные участки, из которых можно вылепить такие аккуратные, обособленные, самостоятельные и потенциально переиспользуемые абстракции, то не нужно разбивать его на функции, даже если он представляет из себя "стену текста" на 100500 строк и даже если в нем есть повторения.
Но, ЧСХ, и MSVC до сегодняшнего для продолжает поддерживать свой "полиморфный delete []", и GCC тоже полез в это болото. В GCC delete [] с некоторых пор ведет себя по-майкрософтовски.
Формально никто им этого, разумеется, не запрещает, ибо поведение все равно не определено.
Из Большой Тройки только в clang не поддерживается "полиморфный delete [] ".
То есть неудивительно, что среди программистов "от сохи" может встречаться верование, что delete [] может использоваться полиморфно.
В ситуациях, когда счетчик цикла не участвует в теле цикла, любой уважающий себя компилятор запросто выполнит такую оптимизацию за вас.
В ситуациях, когда счетчик цикла участвует в теле цикла, такая оптимизация навскидку не применима.
Не надо пытаться в голове переводить С код в машинный код. В 9 случаях из 10 ваши мысленные переводы будут мало коррелировать с реальностью. С - не ассемблер.
Это "ерунду" написал не я, а авторы языка С++: дедушка Страуструп и WG21 ISO C++ Committee.
Еще раз повторяю "для тех кто в танке", и не для разглагольствования, а вызубривания наизусть: никакого полиморфного delete [] в С++ не существует и никогда не существовало. Попытки применения delete [] для удаления объекта (или массива), чей статический тип не совпадает с динамическим, приводит к неопределенному поведению. Ни с какими "виртуальными деструкторами", "vtbl" и т.п. delete [] никогда не работает.
delete [] не имеет ничего общего с полиморфным поведением delete.
P.S. Это "пионэрское" верование в существование некоего "полиморфного delete []" я встречаю уже не первый раз. Откуда-то это лезет... Где-то у этого "гнездо"... Я помню, что MSVC++ в старинных версиях своего особенного видения С++ пытался "подарить" пользователям "полиморфный delete []", но даже они со времен прекратили заниматься подобной чушью. Тем не менее эта чушь все живет...
В смысле? В С++ не существует "полиморфных массивов" и не существует полиморфного delete []. В delete [] не разрешается передавать указатель на базовый класс. В delete [] статический тип удаляемого объекта должен совпадать с его динамическим типом. В противном случае поведение не определено.
Старый код - это особый случай, к теме не относящийся. Да и правильнее сказать (уж не стесняясь), что не дружит не со "старым кодом", а с безграмотным кодом.
Тут стоит заметить, что во всех популярных реализациях дополнительное хранение размера массива в new[]/delete[] используется только в двух случаях:
Элемент массива является классом с нетривиальным деструктором. Размер нужен для вызова правильного количества деструкторов.
Элемент массива является классом с перегруженным operator delete [](void *ptr, std::size_t size).Размер нужен для вычисления правильного значения аргумента для параметраsize.
В остальных случаях дополнительного хранения размера массива вnew[]/delete[]не производится, т.е. эти операторы выделяют память так же, как голый malloc.
Компилятор MSVC++ до последнего времени содержал баг - игнорировал вторую причину из перечисленных выше, в результате чего в нем при вызове перегруженного operator delete [] в качестве размера передавалось "мусорное" значение. Надо проверить, возможно уже исправили...
Во-первых, да, избежание оверхеда для единичных объектов.
Во-вторых, delete обязан уметь выполнять полиморфное удаление, которое совершенно не актуально для delete []. То есть это существенно разные функциональности, калит которые в один оператор было бы неправильно.
Ну и как следствие - независимые механизмы перегрузки скрытых за ними операторов выделения сырой памяти.
Также, в третьих, начиная c С++14 реализации имеют право объединять запросы на память между соседними new-expressions, то есть заголовок сырого блока памяти уже не является тривиально доступным из каждого указателя, возвращенного new-expression.
Записанный в начале блока размер совсем не обязательно корректно отражает количество элементов в массиве. То есть деление размера блока на размер элемента в общем случае будет больше или равно количеству элементов массива. А нам нужно знать точное количество элементов.
Компиляторы уже давно "не пропускают такое", предупреждая автора предупреждением. Использование присваивания в условии обычно требует дополнительной пары скобок для подавления этого предупреждения.
Правило 1 - "короткие функции" - и правило 20 - "один return" - два антипаттерна, которые существуют лишь для поддержки друг друга.
Нет, ни в коем случае не старайтесь следовать правила "одного return". Наоборот, при написании функции старайтесь отсечь простейшие случаи сразу, обработать их в начале функции и тут же сделать явный return. Это существенно повышает удобочитаемость кода, так как явно дает читатель возможность понять, что обработка полностью закончена и больше ничего делать не нужно (т.е. ниже по коду функции больше ничего нет). Придерживайтесь этого правила и при написании циклов: при описании итерации старайтесь отсечь простейшие случаи сразу, обработать их в начале итерации и тут же сделать явный continue.
Стратегия "одного return" не дает понять, закончена обработка случая или нет. Чтобы побороть эту проблему сторонники "одного return" придумали правило "коротких функций". Это чушь. Функция может быть сколько угодно длинной, хоть на 100 экранов. Ничего плохого в этом нет. Функция должна реализовывать некую законченную хорошо очерченную абстракцию - это важно. А сколько экранов займет реализация это абстракции - не имеет никакого значения. Ни в коем случае не подразбивайте функции на под-функции только из соображений длины - введение притянутых за уши искусственных под-абстракций существенно затруднит чтение и понимание кода.
12–Если что-то можно проверить на этапе компиляции, то это надо проверить на этапе компиляции (assert(ы))
O_o? Может имелись в виду static_assert(ы)? assert(ы) - это средство проверки времени выполнения.
32–Функции CamelCase переменные snake_case
Термином camel case называют капитализацию с в стиле camelCase.CamelCase - это не camel case.
38--При сравнении переменных с константой константу ставьте слева от оператора ==.
О ужас! Yoda conditions... Катастрофическая чушь, которую уже давно отправили на свалку истории. Удивило, что кто-то еще пытается их оттуда вытянуть, да еще и в рамках публикации, содержащей довольно много разумного.
39–В каждом if всегда обрабатывать else вариант даже если else тривиальный. Это позволит предупредить многие осечки в программе.
Еще одно фейковое правило из той же оперы, что и 38. Звучит логично, но все уже давно известно в жизни подобных проблем не возникает. Они существуют только в "рыбацких рассказов" от чайников.
40–Всегда инициализировать локальные переменные в стеке. Иначе там просто будут случайные значения, которые могут что-нибудь повредить.
В таком виде - "всегда" - очень вредный антипаттерн. Занимаясь dummy-инициалиазцией локальных переменных вы лишь подавляете полезную функциональность санитайзеров кода, т.е. заметаете под ковер потенциальные ошибки.
Инициализация - полезнейшее свойство языка, которое нужно использовать всегда, когда это возможно. А именно: когда у вас есть наготове полезное инициализирующее значение. Если такого значения у вас нет на момент объявления переменной, то лучше оставить ее неинициализированной, чем инициализировать ее "чем попало".
42–В хорошем С-коде в принципе не должно быть комментариев. Лучший комментарий к коду - это имена функций и переменных.
Это - странная максима.
Во-первых, лучший комментарий к коду - это огромное количество assert, описывающих подразумеваемые автором кода инварианты.
Во-вторых, без текстовых комментариев не обойтись.
В стандартной библиотеке языка C больше нет функции gets, поэтому ваше "все компилируется" является не более чем следствием случайного стечения посторонних обстоятельств.
Ну тут можно еще добавить такие операции, как перетасовку содержимого файлов в случайном порядке, архивирование сотен файлов и посылку их на FTP, автоматическую массовую замену идентификаторов на имена персонажей из LotR, проигрывание MP3 музыки из файлов проекта с соответствующим расширением и еще бесчисленное множество разнообразных действий, которые никому на самом деле не нужны за пределами статистической погрешности.
И это при том, что "добавление и исключение сотен .с файлов" не представляет никакой сложности и в IDE.
O_o. Все повседневная разработка прикладного ПО ведется именно в IDE, ибо именно такой мгновенной интегрированной доступ к средствам сборки и отладки абсолютно необходим для эффективной разработки. IDE и родились из требований разработчиков. Повседневная разработка без IDE сегодня - это упрямое пожирание кактуса.
Именно из-за того, что MS Visual Studio с гигантским отрывом является самой мощной, удобной и эффективной IDE на сегодняшний день, и сложилась нынешняя парадоксальная ситуация, когда разработка прикладного ПО под все целевые платформы в 99 случаях из 100 делается под Windows в Visual Studio. Как это ни удивительно, но никто за рамками статистической погрешности не разрабатывает прикладные продукты для Линукса под Линуксом. Все разрабатывается под Windows и лишь интегрируется и тестируется на целевой платформе.
В MSVC никто вам не мешает сразу инициировать процесс сборки классического MSVC-шного проекта из командной строки. Никакой мышкой водить никуда не надо.
Это даже не говоря о том, что доминирование Windows и MS Visual Studio, как инструмента повседневной разработки для всех целевых платформ привело к тому, что средства автоматической конвертации проектов Visual Studio в makefile или любой другой целевой формат доступны в огромном количестве. Стандартный flow для прикладного ПО под Linux: повседневная разработка в MSVS под Windows -> автоматическая конвертация vcxproj в makefile -> сборка и тестирование под Linux.
С++ прекрасно подходит для программирования МК.
Программирование MK, как впрочем и любое программирование, нацеленное на конкретный экземпляр или класс хардвера, всегда и везде будет делаться с существенным привлечением нестандартных/надстандартных гарантий и возможностей, предоставляемых конкретным компилятором. Цитировать запреты стандарта при это смысла нет. Если конкретная реализация С++ позволяет вам брать адрес
main
и вам это действительно нужно - берите его. Это в одинаковой мере относится и к С, и к С++, и к любому другому ЯВУ.В частности, с педантичной точки зрения фразы типа "Линукс написан С" являются бессмысленными, ибо на стандартном С невозможно реализовать ОС. Все прекрасно понимают, что в такой реализации будут использоваться нестандартные/расширенные возможности конкретного компилятора. Это же в равной мере относится и к программированию МК.
А кто гарантирует, что он будет действительно фиксом для "всего кода"?
Если выделение функций делалось механически "по повторяющемуся коду", а не путем продуманного выделения абстракций, то вполне запросто может получится, что "фикс в этой функции" окажется фиксом для одного контекста ее вызова, но багом для других контекстов ее вызова.
Когда эта ситуация обнаруживается, как правило начинается дополнительный "фиксинг" путем введения "странных параметров" для данной функции. Это параметры, которые не несут никакой осмысленной абстрактной семантики, а служат лишь для идентификации места, из которого вызвана функция. Им, как правило, дают какие-то ничего не говорящие странные имена, вроде
mode
,pass
илиfor_johnny
.А в реальности, на самом деле, просто не надо было выносить этот, пусть даже немного повторяющийся код в отдельную функцию.
Нет, такого простого критерия, как "код используется более одного раза", никогда не существовало, кроме как у нерадивых рукопопов.
Разбиение кода на функции по любому формально-механическому принципу - это грубый, кривой и косой антипаттерн, который только сделает код нечитаемым. К этой категории относится и "код используется более одного раза", и "код содержит слишком много строк" и прочие подобные механические критерии.
Разбиение кода на функции всегда делается исключительно по признаку выделения естественных, хорошо очерченных параметризованных абстракций. Функция должна делать что-то одно, что-то понятное, что-то легко описываемые в нескольких словах. При этом функция должна быть более самостоятельна, чем вызывающий ее код, то есть она должна обладать дополнительной ценностью - быть потенциально полезной и в других местах кода, а не только в тех в которых она вызывалась в момент своего создания. Это - важный критерий того, что функция имеет право на существование. Также хорошим признаком правильно выделенной функции является то, что для такой функции легко придумать название.
Если в вашем коде не получается выделить подобные участки, из которых можно вылепить такие аккуратные, обособленные, самостоятельные и потенциально переиспользуемые абстракции, то не нужно разбивать его на функции, даже если он представляет из себя "стену текста" на 100500 строк и даже если в нем есть повторения.
Но, ЧСХ, и MSVC до сегодняшнего для продолжает поддерживать свой "полиморфный
delete []
", и GCC тоже полез в это болото. В GCCdelete []
с некоторых пор ведет себя по-майкрософтовски.Формально никто им этого, разумеется, не запрещает, ибо поведение все равно не определено.
Из Большой Тройки только в clang не поддерживается "полиморфный
delete []
".То есть неудивительно, что среди программистов "от сохи" может встречаться верование, что
delete []
может использоваться полиморфно.В ситуациях, когда счетчик цикла не участвует в теле цикла, любой уважающий себя компилятор запросто выполнит такую оптимизацию за вас.
В ситуациях, когда счетчик цикла участвует в теле цикла, такая оптимизация навскидку не применима.
Не надо пытаться в голове переводить С код в машинный код. В 9 случаях из 10 ваши мысленные переводы будут мало коррелировать с реальностью. С - не ассемблер.
Да, "заглядывание в eax" - удобнейшая практика в процессе отладки. Особенно если результат лежит не там...
Это "ерунду" написал не я, а авторы языка С++: дедушка Страуструп и WG21 ISO C++ Committee.
Еще раз повторяю "для тех кто в танке", и не для разглагольствования, а вызубривания наизусть: никакого полиморфного
delete []
в С++ не существует и никогда не существовало. Попытки примененияdelete []
для удаления объекта (или массива), чей статический тип не совпадает с динамическим, приводит к неопределенному поведению. Ни с какими "виртуальными деструкторами", "vtbl" и т.п.delete []
никогда не работает.delete []
не имеет ничего общего с полиморфным поведениемdelete
.P.S. Это "пионэрское" верование в существование некоего "полиморфного
delete []
" я встречаю уже не первый раз. Откуда-то это лезет... Где-то у этого "гнездо"... Я помню, что MSVC++ в старинных версиях своего особенного видения С++ пытался "подарить" пользователям "полиморфныйdelete []
", но даже они со времен прекратили заниматься подобной чушью. Тем не менее эта чушь все живет...В смысле? В С++ не существует "полиморфных массивов" и не существует полиморфного
delete []
. Вdelete []
не разрешается передавать указатель на базовый класс. Вdelete []
статический тип удаляемого объекта должен совпадать с его динамическим типом. В противном случае поведение не определено.Старый код - это особый случай, к теме не относящийся. Да и правильнее сказать (уж не стесняясь), что не дружит не со "старым кодом", а с безграмотным кодом.
Тут стоит заметить, что во всех популярных реализациях дополнительное хранение размера массива в
new[]/delete[]
используется только в двух случаях:Элемент массива является классом с нетривиальным деструктором. Размер нужен для вызова правильного количества деструкторов.
Элемент массива является классом с перегруженным
operator delete [](void *ptr, std::size_t size).
Размер нужен для вычисления правильного значения аргумента для параметраsize
.В остальных случаях дополнительного хранения размера массива в
new[]/delete[]
не производится, т.е. эти операторы выделяют память так же, как голыйmalloc
.Компилятор MSVC++ до последнего времени содержал баг - игнорировал вторую причину из перечисленных выше, в результате чего в нем при вызове перегруженного
operator delete []
в качестве размера передавалось "мусорное" значение. Надо проверить, возможно уже исправили...Во-первых, да, избежание оверхеда для единичных объектов.
Во-вторых,
delete
обязан уметь выполнять полиморфное удаление, которое совершенно не актуально дляdelete []
. То есть это существенно разные функциональности, калит которые в один оператор было бы неправильно.Ну и как следствие - независимые механизмы перегрузки скрытых за ними операторов выделения сырой памяти.
Также, в третьих, начиная c С++14 реализации имеют право объединять запросы на память между соседними new-expressions, то есть заголовок сырого блока памяти уже не является тривиально доступным из каждого указателя, возвращенного new-expression.
Записанный в начале блока размер совсем не обязательно корректно отражает количество элементов в массиве. То есть деление размера блока на размер элемента в общем случае будет больше или равно количеству элементов массива. А нам нужно знать точное количество элементов.
-pedantic обязателен, если вы пишете именно на С, а не на развеселом GCC-шном студенческом суржике.
-pedantic-errors - лучше, но уже по желанию
-Werror - это что-то непонятно зачем нужное.
Компиляторы уже давно "не пропускают такое", предупреждая автора предупреждением. Использование присваивания в условии обычно требует дополнительной пары скобок для подавления этого предупреждения.
Правило 1 - "короткие функции" - и правило 20 - "один return" - два антипаттерна, которые существуют лишь для поддержки друг друга.
Нет, ни в коем случае не старайтесь следовать правила "одного return". Наоборот, при написании функции старайтесь отсечь простейшие случаи сразу, обработать их в начале функции и тут же сделать явный
return
. Это существенно повышает удобочитаемость кода, так как явно дает читатель возможность понять, что обработка полностью закончена и больше ничего делать не нужно (т.е. ниже по коду функции больше ничего нет). Придерживайтесь этого правила и при написании циклов: при описании итерации старайтесь отсечь простейшие случаи сразу, обработать их в начале итерации и тут же сделать явныйcontinue
.Стратегия "одного return" не дает понять, закончена обработка случая или нет. Чтобы побороть эту проблему сторонники "одного return" придумали правило "коротких функций". Это чушь. Функция может быть сколько угодно длинной, хоть на 100 экранов. Ничего плохого в этом нет. Функция должна реализовывать некую законченную хорошо очерченную абстракцию - это важно. А сколько экранов займет реализация это абстракции - не имеет никакого значения. Ни в коем случае не подразбивайте функции на под-функции только из соображений длины - введение притянутых за уши искусственных под-абстракций существенно затруднит чтение и понимание кода.
O_o? Может имелись в виду static_assert(ы)? assert(ы) - это средство проверки времени выполнения.
Термином camel case называют капитализацию с в стиле
camelCase
.CamelCase
- это не camel case.О ужас! Yoda conditions... Катастрофическая чушь, которую уже давно отправили на свалку истории. Удивило, что кто-то еще пытается их оттуда вытянуть, да еще и в рамках публикации, содержащей довольно много разумного.
Еще одно фейковое правило из той же оперы, что и 38. Звучит логично, но все уже давно известно в жизни подобных проблем не возникает. Они существуют только в "рыбацких рассказов" от чайников.
В таком виде - "всегда" - очень вредный антипаттерн. Занимаясь dummy-инициалиазцией локальных переменных вы лишь подавляете полезную функциональность санитайзеров кода, т.е. заметаете под ковер потенциальные ошибки.
Инициализация - полезнейшее свойство языка, которое нужно использовать всегда, когда это возможно. А именно: когда у вас есть наготове полезное инициализирующее значение. Если такого значения у вас нет на момент объявления переменной, то лучше оставить ее неинициализированной, чем инициализировать ее "чем попало".
Это - странная максима.
Во-первых, лучший комментарий к коду - это огромное количество
assert
, описывающих подразумеваемые автором кода инварианты.Во-вторых, без текстовых комментариев не обойтись.
Ну с точки зрения направления выхода это та же "перевернутая" платформа.
В стандартной библиотеке языка C больше нет функции
gets
, поэтому ваше "все компилируется" является не более чем следствием случайного стечения посторонних обстоятельств.