Как стать автором
Обновить

Комментарии 98

Как мило. «Предвижу. Заранее выкладываю». Экий вы предвидец. Особенно после ЛОР-эффекта и просьбы выложить в пдф.
«Добавляя в enum новую константу, не забываем поправить операторы switch» — просто не надо добавлять default в switch для enum (ваши же советы о том, что не надо выполнять работу за компилятор и экономить на строках.
Пункт 13, самый конец.
, «KW_IF»
, «KW_IGNOREPAT»
, «KW_INCLUDES»
, «KW_JUMP»
, «KW_MACRO»
, «KW_PATTERN»
Очевидно, в первой строке запятая не нужна. На этом фоне довольно иронично смотрится «Теперь очень легко заметить недостающую запятую»
Обратите в статье внимание на многоточие. Это середина списка.
Запятая в первой строке не очень страшна, т.к. вызовет ошибку компиляции. Да, неприятно, да немного не чисто, но компилятор все равно найдет эту ошибку и подскажет, что нужно подправить.
Добавляя в enum новую константу, не забываем поправить операторы switch

Если enum представляет собой последовательные номера (или не сильно разрежённые) и начинается с 0, а количество элементов достаточно большое, то проще сделать таблицу функций. Ошибка будет легко находиться на этапе компиляции, если последним элементом enum-а сделать MYENUM_MAX и сделать эту константу размером таблицы функций.
И выглядит такой код зачастую привлекательнее, чем гигантский switch.
Но если есть гигантский switch и хочется иметь compile-time проверку, то можно также добавить static_assert на равенство MYENUM_MAX == x, где x — то количество элементов в перечислении, которое было в момент написание switch-а.
Если нет default, хороший компилятор и так ругнётся на недостающие значения.

Какая ошибка компиляции?


typedef void (*MyEnumHandler)(void);
MyEnumHandler example_handler;
const MyEnumHandler table[10] = {
    &example_handler,
};

является вполне правильным кодом. «Лишние» элементы будут просто нулями. Вот switch даст ошибку компиляции (при -Werror). А таблица нет, она даст ошибку только если из enum удалили значение и MYENUM_MAX стало меньше длины таблицы.


Плюс нужно либо синхронизировать порядок между таблицей и enum, что неудобно и не даёт группировать обработчики в таблице, что можно делать со switch. Либо нужно использовать [kMyEnumFoo] = &example_handler. Такой синтаксис также нужен, чтобы получить ошибку, если элемент перечисления был удалён, но зато вставили новый (switch тоже даёт так сделать). Но это C99. А в C89 и ниже switch имеет ещё больше преимуществ перед таблицей, а не одну только ошибку компиляции при добавлении нового элемента в перечисление.

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

Со switch-ем есть ещё две проблемы: забытый break и зачастую расползающиеся простыни кода, который явно просится выделиться в функции, но его никто не рефакторит.
Можете объяснить п.29?
Попробую. Что именно следует пояснить?
Не поверите, но п.29
А чего тут пояснять? "++i" просто проще…

"++i" обозначает, что переменную можно увеличить — и сразу использовать. «i++» обозначает, что нужно где-то «прикопать» предыдущее значение. Что может быть дорого. В случае если компилятор «видит» все определения он, конечно, справится с оптимизацией и лишнюю работу изведёт, но в любом случае — зачем его заставлять делать это, если можно этого и не делать? Ещё и человек может запутаться во всём этом…
Стандартным объяснением для такого совета является примерно следующий:
++i меняет итератор, после чего возвращает ссылку на обновленный итератор.
i++ сохраняет копию итератора, меняет итератор, возвращает сохраненный. Итого имеем на один конструктор копирования больше.
Впрочем, мне встречалось и противоположное утверждение о том, что постинкремент предпочтительнее, правда относилось оно к интегральным типам. Обоснования, увы, не помню, но выглядело оно не лишенным логики.
потому что в цикле в случае с i++ сперва идет чтение из регистра, а затем его увеличение, и процессор умеет такое делать параллельно

в случае с ++i сперва нужно увеличить, а затем считать, что распараллелить невозможно

это касается тех типов, которые влезают в регистр
Это называется «слышал звон, да не знаю — где он».

Чтение/запись и вычисления на большинстве процессоров делаются совершенно отдельными блоками, которые, уж конечно, могут всё распараллелить. Или не могут — если процессор совсем старый.

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

Разница была в PDP-11. Вот там — да, «i++» и «--i» выгоднее, чем «++i» и «i--». Но это ни с каким «параллельным исполнением» не связано — просто так система команд устроена, что в ней есть постинкремент и предекремент, а постдекремента и преинкремента нету.

Такая особенность, в свою очередь, тоже имеет объяснение: так как на PDP-11 все регистры — регистры общего назначения (влючая PC и SP) и доступ к ним всем осуществляется по одному шаблону, то нужен «перекос», чтобы нормально мог работать стек, а так как immediate'ов тоже нет, то нужно, чтобы у команды читающей по адресу PC происходил именно постинкремент, а не преинкремент. Так и получилось что в наборе способов обращения к памяти получился вот такой странный «перекос»: один бит экономии на опкоде, однако.

Оттуда всё и пошло. Но, чёрт побери, PDP-11 перестала выпускаться ещё до того, как большинство читателей Хабра на свет появились! Уже ARM в 1987м имел как LDMIB/STMIB (load/store memory increment before), так и LDMIA/STMIA (load/store memory increment after) — и в обоих случаях регистр увеличивается в том же такте, когда и используется!

Может уже стоит забыть про «дела давно минувших дней»?
Вот нашел видео, откуда я это взял, и там речь явно не про pdp-11:

https://youtu.be/vrfYLlR8X8k

примерно 48:10
Вот что бывает, когда водители такси начинают смотреть передачи, где профессиональные гонщики обсуждают как лучше проходить поворот в заносе на льду :-)

Там речь шла об очень специфическом случае: 64-битный процессор, 32-битный индекс (который может получить экономию, если у вас их много), вы хотите их смешать (однократно) и обратиться по этому индексу в память.

Вот в этом, очень специфическом, случае действительно иногда, очень редко, вы можете получить выигрыш! Из-за использования 32-битного индекса у вас появляется дополнительная операция между увеличением i и его использованием: обрезание (если процессор не имеет 32-битных операций) или «расширение» (если имеет).

И вот тут действительно может получиться проблемка. А может и не получиться. Причём в реальной жизни — обычно не получается. Дело в том, что если индекс у вас «просто int», то он не может переполниться (неопределённое поведение) и, в большинстве случаев, за счёт этого компилятор сможет вернуть «утерянный параллелизм».

Вот uinsigned int — тут да, тут у компилятора определённо проблемы возникнут.

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

По идее, еще быстрее должно работать что-то типа *a++ =…

Но в реальной жизни, думаю, компилятор из всех вариантов сделает одно и то же.
В видео ситуация немножко другая, там идет обращение к элементу массива по инкрементируюмуся указателю, и если постинкремент позволяет сначала использовать смещение для доступа к памяти, а потом увеличивать указатель(пока dma работает), то сначала инкремент заставляет сначала ждать инкремент, а потом запускать фетч из памяти.
Какой ещё DMA? Процессоры DMA не используют. Зато они активно используют prefetch, переименование регистров и спекулятивное исполнение…

Но в реальной жизни, думаю, компилятор из всех вариантов сделает одно и то же.
Если сможет. Как я уже написал выше — у него может не быть такой возможности. Я пробовал сделать так, чтобы «не получилось» — но примеры получаются весьма хитро закрученными, а выигрыш — мизерным. То есть легко сделать так, чтобы компилятору пришлось породить лишний «mov», но это мало: в современных процессорах mov «ничего не стоит», то есть нужно ещё сделать так, чтобы эта лишняя команда «напрягла декодер» и так далее. Это очень сложно сделать. То есть если вы хотите выиграть «самый последний такт», то… может тогда уже стоит на ассемблер посмотреть и отойти от C/C++?

Я думаю та строка в презентации ради «вау-фактора» появилась. Типа: «вы все знаете, что нужно делать A, а не B — а догадываетесь ли вы, что иногда B — лучше?»

Догадываемся. Если бы всё программирование сводилось к набору простых правил, то на рынке не было бы такой острой нехватки программистов и они получали бы гораздо меньше. Но это не значит, что если вы придумали как сделать так, чтобы правило которое правильно срабатывает 1000 случаях из 1000, в одном случае из миллиона всё-таки «сломалось», то об этом нужно сразу на лекции рассказывать. Может «плохо подействовать на неокрепшие умы». Вероятность столкнуться с тем, что для итератора it++ вызовет замедление на порядок (а то и на два) превышает вероятность того, что код с a[i++] окажется хоть где-то быстрее, чем код с a[++i] — и это ещё если забыть что во многих случаях переписав этот код с использованием SSE/AVX/etc вы часто сможете получить экономию не в 0.05%, а в 50% ;-)
Да, про DMA я сморозил, конечно.

Про итераторы полностью согласен.

В целом, эта магия постфикса идет с этих типовых функций типа *strdest++ = *strsrc++, которые транслировались в movs, думаю так.
За меня уже всё ответили. :)
Лев Николаевич, а все-таки что вы хотели сказать в «Войне и мире»?
Если использовать i++ то сначала будет создана копия i, сдвинута на 1 позицию и присвоена обратно i. Если использовано ++i, то копии не создается.
только применяйте это только к сложным объектам, с обычными указателями это будет пессимизацией
ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобное
только применяйте это только к сложным объектам, с обычными указателями это будет пессимизацией.
Не будет это пессимизацией, не надо вводить людей в заблуждение.

ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобное
Вы либо штаны наденьте, либо крестик снимите… Если код доступен, то любой компилятор вам всё, что угодно отлично прооптимизирует и разницы между i++ и ++i не будет вообще. А если нет — то нет… собственно достаточно взглянуть на прототипы: преикремент может вернуть ссылку на итератор, а вот постинкремент — вынужден оный итератор копировать.

Так что правило простое — и именно такое, как написано: используйте для итераторов префиксный оператор инкремента (++i) вместо постфиксного (i++) причём даже тогда, когда эти итераторы — простые указатели или числа. Точка. Конец. Без исключений.

С процессорами и компиляторами где бы это простое правило давало сбой вам, скорее всего, встретится не удастся, а более сложное правило (используйте постинкремент, но предекремент!), которое когда-то, много лет назад имело смысл — настолько криво и неестественно, что, честное слово… оно того не стоит.
В случае постфиксного инкремента создается временный объект, используется, и только после увеличивается значение самой переменной, при работе с префиксным инкрементом временный объект не создается и переменная увеличивается сразу, что позволяет сэкономить немного ресурсов в случае комплексных переменных (например, итераторов).
Но при работе с переменными простых типов (например, обычных целочисленных) следует пользоваться все равно постфиксным инкрементом (т.к. отсутствует зависимость данных, подробнее habrahabr.ru/company/mailru/blog/279449/#comment_8809081 )
Экономия ресурсов на операторах инкремента это конечно лол.
Надо полагать авторы имели ввиду что теоретически могут быть косяки из за незнания приоритета операторов если инкремент внутри сложного выражения, это вроде нередкая ошибка. Но имхо инкремент/декремент гораздо нагляднее всегда отдельным выражением делать а не запихивать в какие то сложные выражения. Это во-первых куда читаемее, а во-вторых постфиксный оператор хотя бы не так мерзко выглядит как префиксный.
По вашим же ссылкам в Release разницы как раз никакой. А в дебаге… ну что то мне подсказывает что это мало где будет критичным боттлнеком.
Скорость работы Debug версии не менее важна, чем Release. Да, конечному пользователю всё равно. А вот программисту нет.
хочу поспорить: измерять бенчмарк дебажной версии не имеет никакого практического смысла
Бенчмарк — действительно смысла не имеет. Незачем сравнивать скорость работы отладочных версий.

Но для программистов скорость работы отладочной версии имеет большой смысл. И ускорить её хотя-бы на 10% бывает очень полезно и приятно. Я думаю, Вы просто не сталкивались с ресурсоёмкими приложениями и долгими тестами.
Что-то мне подсказывает что везде где не надо писать i++ это просто неосознанная привычка оставшаяся с первых проб в программировании. Да в релизе это не скажется, но в коде это показывает неосознанность автора и действительно замедляет циклы в и без того медленном дебаге, в котором все разработчики проводят под 95% времени.
ваши дебажные шаги все равно медленнее любого цикла
Что такое «дебажные шаги»?
(ну и хотите я вам напишу код, который в 100 раз ускоряется что в дебаге что в релизе при замене постинкремента преинкрементом?)
из скорости дебажной версии вообще не следует ничего о скорости релизной

более того, нет никакого практического смысла бенчмаркить и оптимизировать скорость работы дебажной версии

шаги в смысле шаги пошаговой отладки
>из скорости дебажной версии вообще не следует ничего о скорости релизной
правда
>более того, нет никакого практического смысла бенчмаркить и оптимизировать скорость работы дебажной версии
неправда
>шаги в смысле шаги пошаговой отладки
>ваши дебажные шаги все равно медленнее любого цикла
Сформулируйте точнее: что именно медленнее чего? (какие отрезки времени больше каких?)
Воообще и в релизе может быть разница. Редко, но бывают ситуации, когда итератор тяжелый и копировать его дорого.
современные компиляторы умеют оптимизировать и не такое
А могут и не оптимизировать. Зачем раскладывать лишние грабли там, где их дешево избежать?
На эту тему высказались многие из великих: Мейерс (6. «Различайте префиксную форму операторов инкремента и декремента»), Саттер(6. «Временные объекты» )+Александреску(9. «Не пессимизируйте преждевременно», 28. «Предпочитайте канонический вид ++ и --, и вызов префиксных операторов»), Страуструп+Cline( isocpp.org/wiki/faq/operator-overloading#increment-pre-post-speed ).
Без бенчмарков релизной версии всё это лишь слова, основанные на интуиции. Об этом, кстати, и говорит тот же Александреску.
Давайте сведём всё, что вы тут понаписали вместе, идёт?

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

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

Передача параметров по ссылке (рекомендация 25), использование префиксной формы операторов ++ и -- (рекомендация 28) или подобных идиом, которые при работе должны естественным образом "стекать с кончиков ваших пальцев", преждевременной оптимизацией не являются. Это всего лишь устранение преждевременной пессимизации (рекомендация 9)."
+ Дьюхэрст «Скользкие места C++» (87. «Проблемы инкремента и декремента»)
По первому пункту так и просится цикл, но как автор кода, так и ваш анализатор это не видят.
А причём здесь анализатор? Анализатор ищет ошибки. И кое-что на тему неэффективного кода. Но здесь код как раз руками оптимизированный (развернутый), хоть и неправильный.
Ошибку анализатор нашел. Больше ему здесь сказать нечего.
Т.к. Read-and-Comment, то могу только тут: Ссылка на англ. версию PDF на Я.Диске содержит русскую версию
Уже исправил.
А что мешало разместить книгу на Хабре, даже в виде набора статей, как основном источнике и тут же править по замечаниям в каментах? Как мне кажется, разбиение текста на малые блоки с обсуждением каждого по отдельности более эффективно, нежели сборка всего в одну кучу и отделение обрабатываемого ресурса от источника основного фидбэка.
Когда материал побит на части, их сложно продвигать (рекламировать). У меня на этот материал большие планы в плане рекламы PVS-Studio. И мне хочется, чтобы всё было в одном месте.

Считайте это моими причудами. Я вот так захотел сделать и сделал. :)
Думал про это как причину, но не думал что она основная. Не знаю как в правилах с рекламой на Хабре, но куча статей с ссылками на PVS-Studio, да ещё с большим банером в начале и конце, меня бы более заинтересовало со стороны маркентинга. А так на сайте после второй части уже ничего не маячит, аж до самого подвала, что могло бы о ней напоминать. Да и ссылка на Яндекс.Диск тоже в этом не способствует. Хотя если Вам нужен трафик на ресурс и счётчики… тогда другой вопрос))
Мне нужны продажи. Способ достижения цели — интересный материал, в котором присутствует информация о PVS-Studio. Продвигать такой материал, знаю по опыту проще, когда он не побит на части. Иначе все равно приходится делать страницу «агригатор». Только хуже получается. Вот как-то так.
На самом деле большая часть книги уже есть на Хабре в виде статей
> 11. Не жадничайте на строчках кода

В рамочку и на стену, вне зависимости от языка программирования. Код некоторых товарищей выглядит так, как будто их штрафуют за каждый лишний пробел или перенос строки.
Только не говорите этим IOCCC (http://www.ioccc.org/) ребятам.
В принципе, пока они не коммитят код в мой проект, я ничего не имею против :)
Интересная подборка получилась, как и почти всегда Ваши статьи, спасибо.

Не знаю, где ещё спросить, но ответа не нашёл, а может просто плохо искал… Умеет ли ваш анализатор проверять и файлы с «почти си» кодом, в частности *.cu файлы в проектах, использующих CUDA? И если нет, то планируется ли это добавить в дальнейших релизах?
Быть может что-то и проверится, но никакой гарантии дать не могу. Поддержка CUDA не планируется. Однако, вдруг Вы сможете сделать предложение, от которого мы не сможем отказаться… Но это уже стоит обсуждать в почте. :)
PDF-версии очень недостает оглавления.
Поддерживаю по поводу активного оглавления в PDF.
Да, именно так.
Спасибо!
Спасибо, узнал много интересного.
На 23 странице функция IsInterestinError вызывается без аргумента. Это не опечатка?
Спс. Поправлю. Но лучше на почту.
C 4 пунктом не согласен. При грамотном использовании очень удобный оператор. Например для быстрой вставки по условию.
С чем именно в 4 пункте вы не согласны?

Тоже думал было написать Автору про тернарный оператор, но потом прочёл сам текст, и там всё грамотно указано на приоритеты и на рекомендацию обрамления скобками, даже если выражение тривиально. Лично я не вижу смысла экономить на скобках в логических операциях.
Спасибо за труд, узнал много нового.

Но вот это место, похоже, само требует статического анализатора:
«if (headerM != 0) Адрес массива проверяется на равенство 0. Сравнение не имеет смысла, так как результат всегда true.»

Конечно же имелось в виду «проверяется на НЕравенство 0»
Насчёт DllMain. У меня на определённой машине LoO наглухо зависал при загрузке. Если выбить и запустить по новой — порядок. Не по этой ли причине?
[blockquote]Бойтесь printf[/blockqoute]
А чего в ней страшного?
Если короткое — то строка формата. Очень много подводных камней, в том числе потенциальных дыр в безопасности.

К сожалению у C++ stream'ов есть другая проблема — отсутствие строки формата. В результате — куча проблем с локализацией (в действительности программы, использующие потоки почти невозможно локализовывать).

У boost::format — всё вроде хорошо, обе проблемы более-менее решены, но… это нестандарт.

В результате на практике, если нет возможности использовать boost, то приходится использовать printf и потоки: printf — для надписей, которые должен видеть пользователь (и которые кто-то когда-то может решить локализовать), а потоки — для всего остальное (в частности для логов).

Так и живём…
Для форматной строки лучше всегда использовать константную строку.
Тогда компилятор выдаст предупреждение, если указаны неверные параметры. По крайней мере gcc такое умеет.
Если у вас можно использовать константную строку, то и iostream отлично справится.

Я говорю о чём-нибудь типа
printf(_("There are %d file(s) in directory \"%s\"!"), num, dir)
понятно, что в идеале вам нужно грамотно обработать множественные числа и прочее, но даже с таким «расхлябанным» кодом переводчик может выкрутится и написать что-нибудь типа
"В каталоге \"%2$s\" файлов: %1$d!"
А в случае с iostream'ом форматной строки нет и отдельные «ошмётки», которые достанутся переводчику почти невозможно перевести так, чтобы результат не выглядет ужасно.

Но если в примере выше переводчик просто переставит местами %s и %d — то программа и упасть может и даже уязвимость получить!

От всего этого спасает boost::format, но… нестандарт, увы.
Для корректных переводов надо использовать сервисы, которые не дают такое делать.
Или какую-ли утилиту. Не уверен что gettext может такое проверить, но там не случайно есть атрибуты c-format / no-c-format
НЛО прилетело и опубликовало эту надпись здесь
Основные продажи PVS-Studio — США и Европа.
Так а на ломанном то зачем? Отдайте нативному спикеру на вычитку, это копейки стоит.
Теоретик.
От «нативного спикера» без IT-образования толку будет мало, скорее вред. А вычитывать всё, как с книгами делается, через несколько кругов редактирования прогонять — так статья через два года выйдет.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
В C# префиксный и постфиксный операторы одинаковы по времени и количеству операций.
«Есть масса упрямых программистов, которая не хочет видеть ничего опасного в сдвигах отрицательных чисел, переполнении знаковых чисел, сравнивании this c нулём и так далее.» — на дух не переношу сдвиги, всегда думал, что это со мной «что-то не так», а оказывается Вы их тоже не рекомендуете.
Вопрос не в сдвигах. Грамотно применённые сдвиги — очень даже полезны. И опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.

Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.
Если вы досконально не понимаете с чем столкнулись, то о чём спорить-то? С вероятностью 99% в вашей программе до сих пор сидит ошибка, просто «ворошение кода» вокруг неё скрыло её из глаз.

Я сталкиваюсь с багами разных компиляторов и стандартных библиотек регулярно — то есть примерно обнаруживаю один-два-три бага в год. Сколько за это время я сажаю (и исправляю) багов у себя — сказать сложно, но речь явно идёт о сотнях, если не о тысячах.

Так что совет такой: если уж совсем всё работает «странно», то можно и баг компилятора заподозрить, они реально существуют, они не миф — но перед этим всё-таки лучше поискать ошибку у себя потщательнее ибо ошибка с вероятностью 99% — всё-таки где-то у вас в коде…

Поправка.
Опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.

Нельзя осуществлять сдвиг отрицательных чисел. Это неопределённо поведение. http://www.viva64.com/ru/b/0142/
Прочитал стандарт. Слава богу то, что требуется (ASR) они оставили в покое. И то хлеб. А сдвиг влево… тут не очень даже и понятно, что, собственно, должно получиться, так что да, тут вы правы.
«И упаси вас боже, обвинять компилятор, что он неправильно собирает вашу программу. Такое конечно бывает, но крайне редко.» — а с вот этим не соглашусь. Писал в VS2012 код, который должен был обрабатывать CSV-файлы немалых объемов (сейчас этот объем составляет около 200Гб в сутки), использовать рефлексию(которую заменил на LINQ): запустил в Debug-режиме, подсунул файл «1.csv» — все отработало ровно так, как я и ожидал; откомпилировал приложение в Release, разместил на сервере, оно утром скачало тот же самый «1.csv», начало разбирать, произошло исключение, которое я залогировал; собрал приложение снова в Debug, подсунул скаченный файл, на котором произошла ошибка — все успешно разобралось; плюнул, случайно собрал приложение в Release, запустил на своей машине и оно упало ровно на том месте, на котором упало на сервере; запустил в Debug — снова все хорошо; понял, что проблема именно в разных версиях сборки; потом с другими целями поменял версию Framework'а, отвлекли, случайно поставил версию на Release, подсунул тот же самый файл и все отработало успешно. Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.

Не понял в чём дело == виноват компилятор.

Кхм… Что-то не очень доказательство. :)

Рекомендация 31:
Программисты иногда забывают, что в C/C++ нельзя передать в функцию массив по значению. На самом деле, в качестве аргумента передается указатель на массив. Числа в квадратных скобках ничего не значат, они всего лишь служат, своего рода подсказкой программисту, массив какого размера предполагается передать.

Для многомерных массивов это не так.
В С/С++ многомерные массивы хранятся последовательно, для вычисления адреса элемента компилятору нужно знать все размерности, кроме первой. Ее-то и можно проигнорировать.

void foo(int x[][]) // ошибка компиляции
void foo(int x[][3]) // ok
В 17-м пункте в первом из вариантов корректного кода (
memset_s(x, sizeof(apr_uint32_t), 0, sizeof(APR_MD4_DIGESTSIZE));
) мне кажется подозрительным «sizeof».
Да, фигня написана. Поправлю. Спасибо.
38. С сегодняшнего дня используйте nullptr вместо NULL

У меня всегда возникает проблема с функциями WinApi. Уместно ли туда передавать nullptr? Я всегда считал, что нужно использоваться средства библиотеки при работе с этой библиотекой. И когда появился nullptr каждый раз возникает трудный выбор что передать. Особенно когда есть пример из MSDN с NULL вместо аргумента-указателя
Пример из MSDN с вероятностью 90% был написан до того, как nullptr вообще появился. Какой может быть «трудный выбор» — мне, если честно, неясно.

Правило, на самом деле, простое: NULL — не нужно использовать. Никогда. Вообще. Точка.

Если ОЧЕНЬ НАДО поддерживать старые компиляторы — компилируйте с опцией -Dnullptr=NULL или аналогичной.

Либо библиотека принимает nullptr — и тогда нужно туда передать nullptr, либо не принимает — тогда нужно связаться с разработчиком и выяснить, с какого перепугу у него написано в документации, что куда-то нужно передавать NULL, хотя там явно требуется int. В 100 случаев из 100 это ошибка. Причём в 90% — в программе (и её нужно исправить), в 10% — в документации (и её нужно исправить тоже).
18. Знания, полученные при работе с одним языком, не всегда применимы к другому языку

я бы предположил, что автор предложенного вами кода не бывшый паскалист, а например разработчик Java: в Java условия выхода из цикла тоже проверяется каждый раз, но сама архитектура строки другая — там это просто обращение к свойству объекта… аналогичная ситуация есть и во многих других языках. Впрочем сам-то совет полностью верный, только нужно уточнить, что внимание нужно обратщать как на синтаксические конструкции так и на реализацию данных…
Небольшая дополнительная заметка "Об оптимизациях". Проверка рекомендации «не берите на себя работу компилятора».
Зарегистрируйтесь на Хабре, чтобы оставить комментарий