LINUX.ORG.RU

Это вообще законно? Провал выполнения в нижележащую «мёртвую» функцию

 , , , ,


3

3

Случай из реального проекта, код ~10-летней выдержки. Когда-то давно забыл добавить return в функцию, где имеется хитрая условная компиляция. Долгое время всё нормально работало, а сегодня вот пересобрал проект и поимел весёлых проблем. При проигрывании звука сегфолтилось, хотя вроде как в коде всё было нормально. Старые версии (собранные старым GCC) не сегфолтились. При отключенной оптимизации сегфолта тоже не было. Что тут можно ещё сказать? Спасибо Сталлману за gdb, сильно удивился когда увидел вызов якобы вырезанной функции в нём. Ну и главное: читайте и анализируйте Warning’и, господа! Минимальный пример:

$ cat dead_code.cpp 
// dead_code.cpp

#include <cstdio>

int stub_0();
int pxt_PlayWithCallback(int chan, int slot, char loop, void (*FinishedCB)(int, int));

int pxt_Play(int chan, int slot, char loop) {
#ifdef _PLS_NO_DEAD_CODE
	if (stub_0()) {
		fprintf(stderr, "!!!!! GOOD CODE !!!!!\n");
	}
#else
    return pxt_PlayWithCallback(chan, slot, loop, NULL);
#endif
}

int pxt_PlayWithCallback(int chan, int slot, char loop, void (*FinishedCB)(int, int)) {
	fprintf(stderr, "????? DEAD CODE ?????\n");
	return stub_0();
}

int stub_0() { return 42; }

int main(int argc, char *argv[]) {
	return pxt_Play(-1, 20, 0);
}

// OK:
$ g++ dead_code.cpp
$ ./a.out 
????? DEAD CODE ?????

// OK:
$ g++ -D_PLS_NO_DEAD_CODE dead_code.cpp
$ ./a.out 
!!!!! GOOD CODE !!!!!

// WTF?:
$ g++ -O2 -D_PLS_NO_DEAD_CODE dead_code.cpp
$ ./a.out 
!!!!! GOOD CODE !!!!!
????? DEAD CODE ?????
Segmentation fault (core dumped)

// WTF???:
$ g++ -O3 -D_PLS_NO_DEAD_CODE dead_code.cpp
$ ./a.out 
!!!!! GOOD CODE !!!!!
!!!!! GOOD CODE !!!!!
...
!!!!! GOOD CODE !!!!!
!!!!! GOOD CODE !!!!!
Segmentation fault (core dumped)

А ведь довольно интересный простор за этим может скрываться. Ну право ведь, забыли return проставить, компилятор же по-дефолту return 0 впихнёт, верно? А я в этом был уверен.

P.S.

// Имеется предупреждение по-дефолту, отсутствует ret в конце функции, провал и сегфолт.
$ gcc --version
gcc (GCC) 10.2.1 20200723 (Red Hat 10.2.1-1)

// Предупреждение только с -Wall, ret в конце функции имеется, нет провала и сегфолта.
$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39)

P.P.S. поиграться с компиляторами:

C++: https://gcc.godbolt.org/z/7ne8PM
C: https://gcc.godbolt.org/z/b6vqbK

Может кто-нибудь из профи подробно объяснить механизм такого поведения? Спасибо.

См. комментарии и ссылки в теме.

★★★★★

Последнее исправление: EXL (всего исправлений: 4)
Ответ на: комментарий от no-such-file

Хм, это нужно спрашивать гениальных гениев из комитета.

rumgot ★★★★★
()
Ответ на: комментарий от byko3y

не просто ret, но еще и выход из блока обработки исключений. ret — это один байт, в вот выход из блока исключений — это уже несколько байт вдобавок к самому ret

А если возвращается void то типа всё ок, исключения не надо раскручивать что-ли? Что-то тут не сходится. Чем ситуация с void oops(int in, int *out) отличается от int* oops(int in) ?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от anonymous

потому что в C++ есть деструкторы и значение соответствующих типов всегда «используется», м?

И что? Ну и пусть ломается, когда вызывает деструктор. Это как раз логично и предсказуемо.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

До вызовов деструктора бывает нужно вызвать конструктор или ещё как проинициализировать объект. А инициализатора — нету.

anonymous
()
Ответ на: комментарий от theNamelessOne

Если бы проектировали «безопасный язык», то это было бы ошибкой компиляции.

Определение достижимости места в коде в общем случае - это проблема останова и не разрешима. Использование хитроумных алгоритмов в компиляторе только понижает надёжность, т.к. повышается вероятность багов в этих алгоритмах.

X512 ★★★★★
()
Ответ на: комментарий от anonymous

бывает нужно вызвать конструктор или ещё как проинициализировать объект. А инициализатора — нету.

Ну нету и нету - это как раз и есть UB.

no-such-file ★★★★★
()
Ответ на: комментарий от byko3y

Очевидно, параграф в стандарте писал человек, который сам на языке не пишет.

Вот откуда у тебя такие разведданные, что ты запросто выдаешь подобные суждения о людях? И gcc у тебя разрабатывают «отбитые наглухо люди». А ведь в gcc не только C и C++. Ты везде свечку держал?

seiken ★★★★★
()
Ответ на: комментарий от X512

ИМХО, то, что в какой-то ветке выполнения отсутствует return, компилятор вполне может определить без каких либо проблем. А то, что данный код недостижим, должен явно указывать сам программист.

theNamelessOne ★★★★★
()
Ответ на: комментарий от no-such-file

Сравни код с возвратом объекта:

https://gcc.godbolt.org/z/cfWhc5

с кодом без возврата:

https://gcc.godbolt.org/z/hf711d

7 лишних команд! И это даже без исключений, с которыми было бы еще больше. Да, до момента UB это только одна лишняя команда mov, но, эй, L1i кэш не резиновый.

byko3y ★★★★
()
Ответ на: комментарий от seiken

Вот откуда у тебя такие разведданные, что ты запросто выдаешь подобные суждения о людях? И gcc у тебя разрабатывают «отбитые наглухо люди». А ведь в gcc не только C и C++. Ты везде свечку держал?

Я изучал списки людей. которые писали стандарты, и в каком-то из моих крестосрачевых тредов приводил обзор: там львиная доля участников — это вахтеры, которые всю жизнь сидят в комитетах и создают сообщества, и лишь малая доля из них — это люди, которые реально пишут софт.

И gcc у тебя разрабатывают «отбитые наглухо люди»

Я сужу о них по принимаемым ими решениям и их аргументации. Не по одному решению, а по выстраиваемой линейке. Да, писать софт они умеют, но отказываются от принятия решений, вместо этого оставляя принятие решений отбитым вахтерам из комитетов. При этом, они уже давно могли бы пойти за MSVC с оправданием «ну чтобы портировалось лучше». Но нет, они идут за стандартом, который один хрен никто полностью не поддерживает.

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от byko3y

7 лишних команд!

Ты жопой читаешь чтоли? Я не об этом спрашивал. Я то как раз и говорю, что если нет return, то просто суём ret (как для void) а обращение к результату пусть будет UB. Как в Си. Почему нужно такое поведение как есть?

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Ты жопой читаешь чтоли? Я не об этом спрашивал. Я то как раз и говорю, что если нет return, то просто суём ret (как для void) а обращение к результату пусть будет UB. Как в Си. Почему нужно такое поведение как есть?

Ну вот я сейчас добавил «throw std::exception()» к коду — компилятор не стал добавлять в конец коды для «return», даже если я явно дописал их сам. То есть, положение невозврата считается нормой для языка с исключениями. Добавлять код возврата на случай, если исключение не будет сгенерировано? Это много букав процессору читать.

В Си, наоборот, принято всегда возвращаться из функции, либо же падать полностью всей программой. Как это делает Go, например.

Вообще, исключения — зло. Они неплохи в роли «return», но проблема в том, что RAII семантика приводит к прыганию по деструкторам, которое иногда может быть весьма неожиданным.

byko3y ★★★★
()

компилятор же по-дефолту return 0 впихнёт, верно?

Нет, по дефолту он не соберёт прогу, а с -fpermissive это UB, у меня например вываливал содержимое EAX

SR_team ★★★★★
()
Ответ на: комментарий от SR_team

Нет, по дефолту он не соберёт прогу

Это если у тебя cl.exe, а не GCC, тем более старый. См. всё обсуждение в этом треде.

EXL ★★★★★
() автор топика
Ответ на: комментарий от byko3y

это оптимизация ускоряет работу программ в среднем на 0.2%

так «это» и есть основание для многих современных оптимизаций в gcc. поднимать в сотый раз разговор о том, что UB - не повод для говнотворчества не вижу смысла, местная флора и фауна будет с пеной на губах отстаивать точку зрения что так и надо

salozar
()
Ответ на: комментарий от wandrien

компилятор должен был впихнуть error и послать погромиста в лес.

он не только в этом случае должен был так сделать. он например не должен проводить оптимизации сквозь операции типа И, ИЛИ но я лично видел как он выдает отрицательные величины после (x & 8191).

salozar
()
Ответ на: комментарий от EXL

Я в gcc собирал. У меня какой-то старый проект был с -fpermissive и я его оттуда убирал и фиксил траблы. Хз, мб в MXE gcc 7 (или уже 8 был) собран с какими-то флагами по умолчанию, но собрать он мне код не дал из-за return. При чем на старых версиях все работало корректно, там в функции был ряд проверок, после чего дергалось что-то из WinAPI, результат чего и нужен был. Из-за того что результат писался в EAX, все работало как надо, пока не обновился.

SR_team ★★★★★
()
Ответ на: комментарий от SR_team

но собрать он мне код не дал из-за return

Скорее всего там был -Werror, в новых GCC забытый return просто предупреждение (ну хоть дефолтным его сделали!), а не ошибка. Собственно о чём весь этот тред.

EXL ★★★★★
() автор топика
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от byko3y

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

Да и хер бы с ним, это совершенно другой вопрос.

Ещё раз (сколько можно уже блджад) - мы не это обсуждаем, а конкретно то положение, когда конец функции достигнут. Именно это в стандарте описано, а не «если рак на горе свистнет». Исключения тут абсолютно ни при чём. Мы находимся у } в конце функции. В чём смысл позволять UB с невозвратом и вываливанием за пределы функции? А для void не позволять.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

В чём смысл позволять UB с невозвратом и вываливанием за пределы функции? А для void не позволять

Для ответа на этот вопрос нужно свериться с библией:

9.6.3 The return statement [stmt.return]
2.
Flowing off the end of a constructor, a destructor, or a function with a cv void return type is equivalent to a return with no operand. Otherwise, flowing off the end of a function other than main (6.6.1) results in undefined behavior

Где здесь слово про то, что не нужно делать возврат? Его нету. Стандарт предусматривает UB, но не предписывает конкретный вариант UB. Поехавшие из GCC помолились господу, и господь им ответил, что UB здесь — это значит вываливаться за функцию. ИЧСХ, clang поведение скопировал.

Я уже начинаю думать, что все-таки неадекваты здесь скорее сидят в GCC, чем в комитете. Все-таки, если слово в слово брать стандарт, то он не предусматривает той дичи, до которой додумались в GCC.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Шутка

Предлагаю произвести доработку стандарта для уменьшения количества UB.
Например

Результат деления на ноль должен быть равен нулю.

Владимир

anonymous
()
Ответ на: комментарий от byko3y

Где здесь слово про то, что не нужно делать возврат?

Ну вот же

flowing off the end of a function other than main (6.6.1) results in undefined behavior

В отличие от void

equivalent to a return with no operand

В GCC конечно очень странные люди, но они в своём праве. Формально возврат не обязателен, если UB наступает в момент достижения }. Можно просто сразу сегфолтиться.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)

Короче, какая мораль отсюда? Прежде чем код оптимизировать, надо смотреть на ворнинги и думать. Так получается?

seiken ★★★★★
()
Ответ на: комментарий от byko3y

Нет, я намекал на #pragma warning(suppress: 1234).

Все взрослые люди просто обязаны иметь 0 ворнингов в коде, чтобы ворнинги от свежих изменений оперативно фиксить. А те, кто ворнинги оставляет без внимания, просто теряют часть функционала компилятора, т.е. эталонные ССЗБ. Вон, aвтор топика ворнинги-то прочитал.

dsxl
()
Ответ на: комментарий от dsxl

Все взрослые люди просто обязаны иметь 0 ворнингов в коде, чтобы ворнинги от свежих изменений оперативно фиксить

Блин, я не спорю — просто другое дело, что ноль предупреждений компилятора ты не получишь бесплатно, парой ключей компилятора.

byko3y ★★★★
()
Ответ на: комментарий от seiken

В моей реальной ситуации по сути оптимизации не было, а была раздельная компиляция, так как имплементировался вывод звука через другие библиотеки и технологии, но старая ветка тоже осталась и использовалась.

EXL ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.