LINUX.ORG.RU

Хрупкие программы

 


0

2

Почему так бывает, что при -O0 программа работает нормально, а например при -O3 начинает падать, но не всегда. Чей тут косяк, программиста или gcc? Может ли использование static_cast и запрет неявных преобразований типов избежать такого поведения?


Почему так бывает, что при -O0 программа работает нормально, а например при -O6 начинает падать, но не всегда. Чей тут косяк, программиста или gcc?

Программиста. Или gcc. Или и программиста, и gcc.

Может ли использование static_cast и запрет неявных преобразований типов избежать такого поведения?

Может. А может, и не может.

Какие вопросы – такие и ответы.

Siborgium ()

У gcc косяки, если и есть, то попасть на них плохо программисту практически нереально. «-O6» - читайте документацию. По поводу креша - valgrind и санитайзеры в помощь.

anonymous ()

С вероятностью 99% с копейками по моей субъективной оценке это косяк программиста.

ЗЫ

gcc это не только компилятор для C и крестов, так что со статик кастом вообще некорректный вопрос.

peregrine ★★★★★ ()
Последнее исправление: peregrine (всего исправлений: 1)

Подозреваю, что выбранные оптимизации -O3 «как-то-то где-то» в коде неправильно поняты программистом, в результате чего работают не всегда как ожидается. А вот каким конкретно программистом - «это уже следующий вопрос».

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

Вот пример косяка компилятора:

https://lwn.net/Articles/342330/

«The linux kernel had a bug where a potentially NULL pointer was being dereferenced before a test for that pointer being null. However, in some cases it was possible to map memory to address zero, thus allowing the dereferencing to succeed. The compiler, upon noticing that the pointer was dereferenced, assumed that it couldn’t be NULL, then removed the NULL test later and all the code in that branch. This introduced a security vulnerability into the code, as the function would proceed to use an invalid pointer containing attacker-supplied data.»

Взял и выкинул целую ветку кода. Нормально так.

Zpp ()

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

Поэтому тонны софта резко перестают работать или может проседать стабильность. Настала эра сжв и технофашизма, народ экстримит по полной. Поэтому чтобы не стало плохо - старый код компилируем компилятором, под который он писался. Под новый код тщательно читаем стандарт и те места, где компилятору явно не сказано что делать (UB) обходим стороной, потому что сейчас считается компилятор в этом случае имеет право генерировать неработающий код. Тогда все будет хорошо и со старым кодом и с новым. Даже код под старые стандарты (i.e C89) был переосмыслен, и это уже не спасает.

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

Пример выше, без оптимизации код проверки остался и все работало, как задумал программист.

Просто у тебя в коде был UB, который трактуется компилятором по-разному на разных уровнях оптимизации.

theNamelessOne ★★★★★ ()

Потому что undefined behavior. Код написанный не в соответствии со специализацией может работать не как ожидалось. C/C++ так устроены. Пример. Если не хочется всего этого, надо использовать Pascal, Oberon, Go, Rust.

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

Там по ссылке вообще мрак. Компилятор не генерирует даже выхода из функции.

UB UB-ом, но такие кейсы надо либо компилировать в какой-то осмысленный код, либо вообще отказываться компилировать и заканчивать работу с осмысленным сообщением об ошибке.

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

Там по ссылке вообще мрак.

Это C/C++ каким мы его знаем. Если всего этого не хочется, надо либо забить на комитет и писать другую спецификацию (что например делает Microsoft), либо использовать другие языки.

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

Это недочёт в стандарте, а не в компиляторе.

Стандарт не требует, соответственно компиляторы не реализуют.

Писать свой стандарт можно, но можно и без стандарта падать с -Werror по дефолту на таких кейсах. Так как поведение – UB, то такой вариант ничего не нарушает.

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

Где в стандарте написано, что область памяти не может начинаться с адреса 0. В какой-нибудь другой архитектуре например?

В Си константа 0, преобразованная в адрес, имеет особую семантику «никуда не указывающего» указателя. Это написано в стандарте, конкретную страницу искать лень.

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

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

Если подумать, под ядро должно быть сделана особая оговорка, и возможно, в режиме freestanding семантика должна отличаться (или должен быть введён отдельный режим), но так как такого не сделано, то формально компилятор прав. Он же не знает, что это ядро.

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

Там вроде не от оптимизации же зависит? Я просто хотел пример именно кривого кода, ломающегося от оптимизаций, а не от глупых компиляторов/стандартов, но, похоже, это одно и то же.

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

Не соглашусь.

Проблема скорее в том, что в руки инженеров попал «мирный атом», но пользоваться им в мирных целях еще не научились.

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

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

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

wandrien ()
Последнее исправление: wandrien (всего исправлений: 1)

Потому что космические лучи портят программы.

Надо покупать серверные процессоры и память с поддержкой ECC, и использовать ZFS c raidz2.

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

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

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

если password_buffer далее не используется?

Конечно может. Ты не понимаешь как работает С++.

В стандарте написано:

  1. Есть программа

  2. Она что-то делает

  3. Компилятор может заменять как угодно эту программу, вызывать любые функции, делать то о чём в коде вообще не написано, не делать то что написано в коде, переставлять инструкции и в целом всё что угодно. Лишь бы «Наблюдаемое поведение не изменилось»

https://i.imgur.com/THt3PHX.png

вот можешь послушать этого дядьку: https://youtu.be/ZAji7PkXaKY

как пример: https://gcc.godbolt.org/z/ozeEeb

внезапно, нет вызова printf, а откуда-то взялся вызов puts

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

внезапно, нет вызова printf, а откуда-то взялся вызов puts

Это же бред.

Наблюдаемое поведение изменилось. До оптимизации программа вызывала printf, а после – puts.

Весь вопрос в трактовке «наблюдаемого поведения». Компиляторы заходят слишком далеко.

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

Ты не понимаешь как работает С++

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

Zpp ()