Комментарии 27
НЛО прилетело и опубликовало эту надпись здесь
Например, во многих проектах есть правило использовать safe_free (это может быть макрос или inline-функция) для проверки указателя на NULL перед освобождением с помощью free (опционально — занулить указатель). Т. е. даже если программисту очевидно, что указатель ненулевой, он всё-равно будет использовать safe_free, потому что так закреплено в правилах этого проекта. Остаётся надеяться, что компилятор выкинет эти проверки. Один из старейших способов сообщить GCC о ненулевом указателе — использовать
__attribute__((nonnull (...))
. Насколько я понимаю, теперь этот атрибут действует не только вглубь функций, но и наружу.+1
Именно для free() эта проверка не нужна, так как он по стандарту должен игнорировать нулевое значение. Если падает — то это уже проблема реализации, которая положила на стандарты, так что приходится так костылить.
А safe_free скорее всего определяют так:
А safe_free скорее всего определяют так:
#define safe_free(ptr) do { free(ptr); (ptr) = NULL; } while (0)
что не лишено смысла.+6
Совершенно верно.
Я раз работал в проекте, где на уровне собственного препроцессора (на тикле) определялись многие макросы, в том числе и safe_free. Принцип такой: make собирает сначала исполняемый бинарник, который определяет поведение компилятора/платформы, а затем генерит header для тиклевого препроцессора. Затем прогон на тикле, который делает из headers.tcl.h -> headers.h, и дальше компилит уже проект (использующий headers.h)…
Например (на память):
Вплоть до определения нужен ли такой «цикл»
Работать было удобно, хоть и компиляторов и платформ был целый зоопарк.
Ну а мы его пользовали не всегда по прямому назначению (например, заместо некоторых макросов или тех же сишных templates, было гораздо удобнее). До сих пор иногда беру для удобства, если пользую c/c++, т.к. много чего делать можно, например пути (сегменты их) — под виндой поворачивает слэш:
Если вдруг кому надо — препро-парсер выложу на гитхаб.
Я раз работал в проекте, где на уровне собственного препроцессора (на тикле) определялись многие макросы, в том числе и safe_free. Принцип такой: make собирает сначала исполняемый бинарник, который определяет поведение компилятора/платформы, а затем генерит header для тиклевого препроцессора. Затем прогон на тикле, который делает из headers.tcl.h -> headers.h, и дальше компилит уже проект (использующий headers.h)…
Например (на память):
<% if $std(free_checks_null) { %>
#define safe_free(ptr) { free(ptr); (ptr) = NULL; }
<% } else { %>
#define safe_free(ptr) if ( !(ptr) ) { free(ptr); (ptr) = NULL; } else {}
<% } %>
Вплоть до определения нужен ли такой «цикл»
do ... while (0)
, т.е. ваш пример может выглядеть:#define safe_free(ptr) <%= [ $need_dummy_do { free(ptr); (ptr) = NULL; } ] %>
Работать было удобно, хоть и компиляторов и платформ был целый зоопарк.
Ну а мы его пользовали не всегда по прямому назначению (например, заместо некоторых макросов или тех же сишных templates, было гораздо удобнее). До сих пор иногда беру для удобства, если пользую c/c++, т.к. много чего делать можно, например пути (сегменты их) — под виндой поворачивает слэш:
char * pdd_path = "<%= [file nativename {conf/data/idx/pdd}] %>";
Если вдруг кому надо — препро-парсер выложу на гитхаб.
+1
Так можно сказать практически про любую оптимизацию.
Как я уже писал ранее, по отдельности они могут быть незначительными, но тем не менее, любая оптимизация может открыть дорогу другим, более серьезным трансформациям кода, которые уже повлияют.
Особенно заметно это при каскадном инлайнинге кода через несколько функций.
Как я уже писал ранее, по отдельности они могут быть незначительными, но тем не менее, любая оптимизация может открыть дорогу другим, более серьезным трансформациям кода, которые уже повлияют.
Особенно заметно это при каскадном инлайнинге кода через несколько функций.
+7
И вы так реально пишете программный код, как привели в примерах? А потом видимо еще не понимаете, почему же всё падает. Очень всё же интересует, почему сначала делается memove, а только потом проверяется указатель. Надеяться на какое-то определенное поведение сторонней функции изначально глупая затея.
int wtf( int* to, int* from, size_t count ) {
if (from == 0) return 0;
memmove( to, from, count );
return *from;
}
Чем такой код не устраивает?
int wtf( int* to, int* from, size_t count ) {
if (from == 0) return 0;
memmove( to, from, count );
return *from;
}
Чем такой код не устраивает?
-8
И вы так реально пишете программный код, как привели в примерах? А потом видимо еще не понимаете, почему же всё падает.Вы это серьезно? Новая оптимизация может доломать кучу «успешно работавшего» десятилетиями кода в проектах размером в сотни тысяч и миллионы строк.
+6
Собственно, уже доломало.
www.opennet.ru/opennews/art.shtml?num=39992
(обсуждение на RSDN: www.rsdn.ru/forum/cpp/5644904 )
И ещё один весёлый примерчик — www.rsdn.ru/forum/cpp/5653418
www.opennet.ru/opennews/art.shtml?num=39992
(обсуждение на RSDN: www.rsdn.ru/forum/cpp/5644904 )
И ещё один весёлый примерчик — www.rsdn.ru/forum/cpp/5653418
0
Описанная выше ситуация может произойти вследствие трансформаций. Все то, что компилятор имеет право делать, он так или иначе будет делать.
В какой-то конкретной ситуации, компилятор может посчитать, что для снижения register pressure имеет смысл поменять порядок блоков кода с A, B, C на A, C, B, если это не повлияет на результат. А влияние на результат он рассчитает исходя из всех доступных соображений, включая и описанное в статье.
В итоге — различное поведение.
В какой-то конкретной ситуации, компилятор может посчитать, что для снижения register pressure имеет смысл поменять порядок блоков кода с A, B, C на A, C, B, если это не повлияет на результат. А влияние на результат он рассчитает исходя из всех доступных соображений, включая и описанное в статье.
В итоге — различное поведение.
+3
Плохо конечно, что легаси ломается, но пользователи undefined behaviour сами себе злобные буратины. В языке явно сказано, что так делать нельзя.
+2
PVS-Studio говорит: V595 The 'from' pointer was utilized before it was verified against nullptr. Check lines: 16, 17. test.cpp 16.
Я просто оставлю это здесь: Примеры V595. Здравствуй новый глючный мир. :)
Я просто оставлю это здесь: Примеры V595. Здравствуй новый глючный мир. :)
+25
Выдается для всех примеров в публикации или только для первого?
0
Для всех (wtf, magic1, magic3).
Анализатор правда молчит про подозрительный magic2. Впрочем, этот код может ведь быть и правильным :). Вдруг printf( «null\n» ); кидает исключение и до memmove() мы не доберёмся.
Анализатор правда молчит про подозрительный magic2. Впрочем, этот код может ведь быть и правильным :). Вдруг printf( «null\n» ); кидает исключение и до memmove() мы не доберёмся.
+2
Выдается на magic3() с «велосипедом» или с memcpy()?
0
По стандарту extern «C» функции не могут кидать исключения (чем кстати некоторые компиляторы на некоторых платформах пользуются, отключая генерацию unwind info, после чего отваливается пробрасывание исключений через С-шный код из С++-ных коллбеков, увы)
0
Пробрасывание исключений через код на C — изначально плохая идея. Даже если предположить, что исключение «просто пролетит», очевидно, что код на C никак не сможет на него отреагировать — в результате код на C, который собирался изменить состояние программы после возврата управления из callback-функции, сделать это не сможет, данные программы могут оказаться в рассогласованном состоянии.
0
Непонятно, что я сказал такого неугодного, что лепят минусы. Видимо кто-то подумал, что я не уважаю его любимый компилятор. Отнюдь. Виноват кривой код. И его, как я показал, весьма много.
+6
А с -Wall -Wextra никаких варнингов не выдаёт? (мне самому сейчас лень gcc-4.9.0 ставить)
-1
Наслышан о чрезмерно агрессивной оптимизации GCC 4.9. Но в этом конкретном примере, если memmove(0, 0, 0) это неопределённое поведение, то почему вы решили, что программа после такого вызова функции вообще продолжит нормально выполняться и даст корректный результат?
Понятно, что в каких-то конкретных реализациях компилятора это работает (или работало до этого дня), и такими реализациями могут оказаться даже все существующие компиляторы, но по стандарту это стрельба в ногу, хоть и холостыми патронами. GCC 4.9 решил помочь и дал вам боевые.
Понятно, что в каких-то конкретных реализациях компилятора это работает (или работало до этого дня), и такими реализациями могут оказаться даже все существующие компиляторы, но по стандарту это стрельба в ногу, хоть и холостыми патронами. GCC 4.9 решил помочь и дал вам боевые.
0
Проверка указателя на ноль вообще неясно, зачем нужна. Если, конечно, нулевое значение не является функциональным и что-то значит само по себе. Ничто не мешает размепить нулевую страницу и ловить такие пойнтеры на page fault.
-1
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Новые оптимизации с использованием неопределенного поведения в gcc 4.9.0