Комментарии 98
, «KW_IF»
, «KW_IGNOREPAT»
, «KW_INCLUDES»
, «KW_JUMP»
, «KW_MACRO»
, «KW_PATTERN»
Очевидно, в первой строке запятая не нужна. На этом фоне довольно иронично смотрится «Теперь очень легко заметить недостающую запятую»
Добавляя в enum новую константу, не забываем поправить операторы switch
Если enum представляет собой последовательные номера (или не сильно разрежённые) и начинается с 0, а количество элементов достаточно большое, то проще сделать таблицу функций. Ошибка будет легко находиться на этапе компиляции, если последним элементом enum-а сделать MYENUM_MAX и сделать эту константу размером таблицы функций.
И выглядит такой код зачастую привлекательнее, чем гигантский switch.
Какая ошибка компиляции?
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 и зачастую расползающиеся простыни кода, который явно просится выделиться в функции, но его никто не рефакторит.
"++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) — и в обоих случаях регистр увеличивается в том же такте, когда и используется!
Может уже стоит забыть про «дела давно минувших дней»?
https://youtu.be/vrfYLlR8X8k
примерно 48:10
Там речь шла об очень специфическом случае: 64-битный процессор, 32-битный индекс (который может получить экономию, если у вас их много), вы хотите их смешать (однократно) и обратиться по этому индексу в память.
Вот в этом, очень специфическом, случае действительно иногда, очень редко, вы можете получить выигрыш! Из-за использования 32-битного индекса у вас появляется дополнительная операция между увеличением i и его использованием: обрезание (если процессор не имеет 32-битных операций) или «расширение» (если имеет).
И вот тут действительно может получиться проблемка. А может и не получиться. Причём в реальной жизни — обычно не получается. Дело в том, что если индекс у вас «просто int», то он не может переполниться (неопределённое поведение) и, в большинстве случаев, за счёт этого компилятор сможет вернуть «утерянный параллелизм».
Вот uinsigned int — тут да, тут у компилятора определённо проблемы возникнут.
Никакого отношения к итераторам этот пример не имеет. Вообще. А про использование индексов и указателей — рекомендую послушать ровно предыдущую минуту того же выступления.
По идее, еще быстрее должно работать что-то типа *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% ;-)
ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобное
только применяйте это только к сложным объектам, с обычными указателями это будет пессимизацией.Не будет это пессимизацией, не надо вводить людей в заблуждение.
ну и насчет создания копии можно уже поспорить, вроде бы clang уже умеет распознавать и оптимизировать подобноеВы либо штаны наденьте, либо крестик снимите… Если код доступен, то любой компилятор вам всё, что угодно отлично прооптимизирует и разницы между i++ и ++i не будет вообще. А если нет — то нет… собственно достаточно взглянуть на прототипы: преикремент может вернуть ссылку на итератор, а вот постинкремент — вынужден оный итератор копировать.
Так что правило простое — и именно такое, как написано: используйте для итераторов префиксный оператор инкремента (++i) вместо постфиксного (i++) причём даже тогда, когда эти итераторы — простые указатели или числа. Точка. Конец. Без исключений.
С процессорами и компиляторами где бы это простое правило давало сбой вам, скорее всего, встретится не удастся, а более сложное правило (используйте постинкремент, но предекремент!), которое когда-то, много лет назад имело смысл — настолько криво и неестественно, что, честное слово… оно того не стоит.
Но при работе с переменными простых типов (например, обычных целочисленных) следует пользоваться все равно постфиксным инкрементом (т.к. отсутствует зависимость данных, подробнее habrahabr.ru/company/mailru/blog/279449/#comment_8809081 )
Надо полагать авторы имели ввиду что теоретически могут быть косяки из за незнания приоритета операторов если инкремент внутри сложного выражения, это вроде нередкая ошибка. Но имхо инкремент/декремент гораздо нагляднее всегда отдельным выражением делать а не запихивать в какие то сложные выражения. Это во-первых куда читаемее, а во-вторых постфиксный оператор хотя бы не так мерзко выглядит как префиксный.
Но для программистов скорость работы отладочной версии имеет большой смысл. И ускорить её хотя-бы на 10% бывает очень полезно и приятно. Я думаю, Вы просто не сталкивались с ресурсоёмкими приложениями и долгими тестами.
(ну и хотите я вам напишу код, который в 100 раз ускоряется что в дебаге что в релизе при замене постинкремента преинкрементом?)
более того, нет никакого практического смысла бенчмаркить и оптимизировать скорость работы дебажной версии
шаги в смысле шаги пошаговой отладки
правда
>более того, нет никакого практического смысла бенчмаркить и оптимизировать скорость работы дебажной версии
неправда
>шаги в смысле шаги пошаговой отладки
>ваши дебажные шаги все равно медленнее любого цикла
Сформулируйте точнее: что именно медленнее чего? (какие отрезки времени больше каких?)
только применяйте это только к сложным объектам, с обычными указателями это будет пессимизацией
современные компиляторы умеют оптимизировать и не такое
из скорости дебажной версии вообще не следует ничего о скорости релизной
без бенчмарков релизной версии всё это лишь слова, основанные на интуицииСобрав всё это воедино получаем: вы можете легко написать программу, которая использует обычные указатели и, будучи собрана современным компилятором в релизной версии, работает быстрее при использовании постинкремента.
Либо приведите пример подобной программы, либо заткнитесь и прекратите морочить людям голову.
Передача параметров по ссылке (рекомендация 25), использование префиксной формы операторов ++ и -- (рекомендация 28) или подобных идиом, которые при работе должны естественным образом "стекать с кончиков ваших пальцев", преждевременной оптимизацией не являются. Это всего лишь устранение преждевременной пессимизации (рекомендация 9)."
Считайте это моими причудами. Я вот так захотел сделать и сделал. :)
В рамочку и на стену, вне зависимости от языка программирования. Код некоторых товарищей выглядит так, как будто их штрафуют за каждый лишний пробел или перенос строки.
Не знаю, где ещё спросить, но ответа не нашёл, а может просто плохо искал… Умеет ли ваш анализатор проверять и файлы с «почти си» кодом, в частности *.cu файлы в проектах, использующих CUDA? И если нет, то планируется ли это добавить в дальнейших релизах?
Тоже думал было написать Автору про тернарный оператор, но потом прочёл сам текст, и там всё грамотно указано на приоритеты и на рекомендацию обрамления скобками, даже если выражение тривиально. Лично я не вижу смысла экономить на скобках в логических операциях.
Но вот это место, похоже, само требует статического анализатора:
«if (headerM != 0) Адрес массива проверяется на равенство 0. Сравнение не имеет смысла, так как результат всегда true.»
Конечно же имелось в виду «проверяется на НЕравенство 0»
А чего в ней страшного?
К сожалению у C++ stream'ов есть другая проблема — отсутствие строки формата. В результате — куча проблем с локализацией (в действительности программы, использующие потоки почти невозможно локализовывать).
У boost::format — всё вроде хорошо, обе проблемы более-менее решены, но… это нестандарт.
В результате на практике, если нет возможности использовать boost, то приходится использовать printf и потоки: printf — для надписей, которые должен видеть пользователь (и которые кто-то когда-то может решить локализовать), а потоки — для всего остальное (в частности для логов).
Так и живём…
Тогда компилятор выдаст предупреждение, если указаны неверные параметры. По крайней мере gcc такое умеет.
Я говорю о чём-нибудь типа
printf(_("There are %d file(s) in directory \"%s\"!"), num, dir)
понятно, что в идеале вам нужно грамотно обработать множественные числа и прочее, но даже с таким «расхлябанным» кодом переводчик может выкрутится и написать что-нибудь типа"В каталоге \"%2$s\" файлов: %1$d!"
А в случае с iostream'ом форматной строки нет и отдельные «ошмётки», которые достанутся переводчику почти невозможно перевести так, чтобы результат не выглядет ужасно.Но если в примере выше переводчик просто переставит местами %s и %d — то программа и упасть может и даже уязвимость получить!
От всего этого спасает boost::format, но… нестандарт, увы.
«Есть масса упрямых программистов, которая не хочет видеть ничего опасного в сдвигах отрицательных чисел, переполнении знаковых чисел, сравнивании this c нулём и так далее.» — на дух не переношу сдвиги, всегда думал, что это со мной «что-то не так», а оказывается Вы их тоже не рекомендуете.Вопрос не в сдвигах. Грамотно применённые сдвиги — очень даже полезны. И опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.
Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.Если вы досконально не понимаете с чем столкнулись, то о чём спорить-то? С вероятностью 99% в вашей программе до сих пор сидит ошибка, просто «ворошение кода» вокруг неё скрыло её из глаз.
Я сталкиваюсь с багами разных компиляторов и стандартных библиотек регулярно — то есть примерно обнаруживаю один-два-три бага в год. Сколько за это время я сажаю (и исправляю) багов у себя — сказать сложно, но речь явно идёт о сотнях, если не о тысячах.
Так что совет такой: если уж совсем всё работает «странно», то можно и баг компилятора заподозрить, они реально существуют, они не миф — но перед этим всё-таки лучше поискать ошибку у себя потщательнее ибо ошибка с вероятностью 99% — всё-таки где-то у вас в коде…
Опасны сдвиги не отрицательных чисел, конечно, а сдвиги на отрицательную величину.
Нельзя осуществлять сдвиг отрицательных чисел. Это неопределённо поведение. http://www.viva64.com/ru/b/0142/
«И упаси вас боже, обвинять компилятор, что он неправильно собирает вашу программу. Такое конечно бывает, но крайне редко.» — а с вот этим не соглашусь. Писал в VS2012 код, который должен был обрабатывать CSV-файлы немалых объемов (сейчас этот объем составляет около 200Гб в сутки), использовать рефлексию(которую заменил на LINQ): запустил в Debug-режиме, подсунул файл «1.csv» — все отработало ровно так, как я и ожидал; откомпилировал приложение в Release, разместил на сервере, оно утром скачало тот же самый «1.csv», начало разбирать, произошло исключение, которое я залогировал; собрал приложение снова в Debug, подсунул скаченный файл, на котором произошла ошибка — все успешно разобралось; плюнул, случайно собрал приложение в Release, запустил на своей машине и оно упало ровно на том месте, на котором упало на сервере; запустил в Debug — снова все хорошо; понял, что проблема именно в разных версиях сборки; потом с другими целями поменял версию Framework'а, отвлекли, случайно поставил версию на Release, подсунул тот же самый файл и все отработало успешно. Так досконально и не понял, с чем был связан баг — то ли с Framework'ом, то ли еще с чем.
Не понял в чём дело == виноват компилятор.
Кхм… Что-то не очень доказательство. :)
Программисты иногда забывают, что в C/C++ нельзя передать в функцию массив по значению. На самом деле, в качестве аргумента передается указатель на массив. Числа в квадратных скобках ничего не значат, они всего лишь служат, своего рода подсказкой программисту, массив какого размера предполагается передать.
Для многомерных массивов это не так.
В С/С++ многомерные массивы хранятся последовательно, для вычисления адреса элемента компилятору нужно знать все размерности, кроме первой. Ее-то и можно проигнорировать.
void foo(int x[][]) // ошибка компиляции
void foo(int x[][3]) // ok
memset_s(x, sizeof(apr_uint32_t), 0, sizeof(APR_MD4_DIGESTSIZE));
) мне кажется подозрительным «sizeof».
38. С сегодняшнего дня используйте nullptr вместо NULL
У меня всегда возникает проблема с функциями WinApi. Уместно ли туда передавать nullptr? Я всегда считал, что нужно использоваться средства библиотеки при работе с этой библиотекой. И когда появился nullptr каждый раз возникает трудный выбор что передать. Особенно когда есть пример из MSDN с NULL вместо аргумента-указателя
Правило, на самом деле, простое: NULL — не нужно использовать. Никогда. Вообще. Точка.
Если ОЧЕНЬ НАДО поддерживать старые компиляторы — компилируйте с опцией -Dnullptr=NULL или аналогичной.
Либо библиотека принимает nullptr — и тогда нужно туда передать nullptr, либо не принимает — тогда нужно связаться с разработчиком и выяснить, с какого перепугу у него написано в документации, что куда-то нужно передавать NULL, хотя там явно требуется int. В 100 случаев из 100 это ошибка. Причём в 90% — в программе (и её нужно исправить), в 10% — в документации (и её нужно исправить тоже).
я бы предположил, что автор предложенного вами кода не бывшый паскалист, а например разработчик Java: в Java условия выхода из цикла тоже проверяется каждый раз, но сама архитектура строки другая — там это просто обращение к свойству объекта… аналогичная ситуация есть и во многих других языках. Впрочем сам-то совет полностью верный, только нужно уточнить, что внимание нужно обратщать как на синтаксические конструкции так и на реализацию данных…
Здесь были собраны полезные советы по программированию. А теперь для разнообразия коллекция вредных :). 50 вредных советов для C++ программиста.
Главный вопрос программирования, рефакторинга и всего такого