Невозможно понять этот код из CodeGolf

Таким образом, я потерял свой уик-энд на Stackru и увидел эту проблему в Hot Network Questions.

Фон

Привет гольфисты! Я хотел бы выучить все языки программирования! Но у меня немного внимания... и копирование всех примеров Hello World становится скучным... но я люблю огонь! ^ Ш ^

Вызов

Итак, вот план! Я хочу, чтобы вы все написали наименьший код, который скомпилируется, распечатайте Goodbye Cruel World!, а затем потерпите крах. Или, в качестве бонуса, напечатайте Hello World! и потерпите крах с Прощай Жестокий Мир!

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

main(){puts(puts("Goodbye Cruel World!"));}

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

Благодаря puts() документация, которую я обнаружил, что puts() возвращает неотрицательное значение в случае успеха. Так что, если я правильно понял, это эквивалентно чему-то вроде:

puts(2); 

Как 2 такое "указатель на другую строку для печати"??

Позднее к этому же ответу было добавлено улучшение:

main(i){i=puts("Goodbye Cruel World!")/0;}

И на этот раз я полностью потерян. Так i берется в качестве аргумента из main, используется для хранения возвращаемого значения puts(), Хорошо. Но как насчет \0? Зачем использовать NUL-TERMINATOR персонаж там?

Если бы вы могли меня немного осветить, мне было бы очень интересно понять это. Кроме того, я думаю, что название вопроса может быть немного более точным, если его перефразировать, но я не смог выразить словами мое недопонимание.

3 ответа

Решение

Оба решения вызывают неопределенное поведение.

Первое решение:

main(){puts(puts("Goodbye Cruel World!"));}

оценивает puts("Goodbye Cruel World!"), который возвращает неотрицательное значение в случае успеха. Это значение передается puts(), Теперь, согласно §6.5.2.2 7:

Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы посредством присваивания, в типы соответствующих параметров, принимая тип каждого параметра за неквалифицированную версию объявленной тип.

Таким образом, код пытается преобразовать значение, возвращаемое при первом вызове puts()как бы при присваивании значению типа char *, Это тип левого операнда в присваивании, в то время как тип правого операнда int, Левый операнд является типом указателя, поэтому правый операнд должен быть указателем на квалифицированную или неквалифицированную версию совместимого типа, указатель на voidили константа нулевого указателя ( §6.5.16.1 1). Ничто из этого не является истинным, так что это нарушение ограничения, и компилятор должен выдать предупреждение.

Это неопределенное поведение, в том смысле, что не определено поведение для того, что должно произойти, если вы запустите этот код.

Второе решение - также неопределенное поведение, так как деление на ноль приводит к неопределенному поведению ( §6.5.5 5):

Результатом оператора / является частное от деления первого операнда на второй; результат оператора% - остаток. В обеих операциях, если значение второго операнда равно нулю, поведение не определено.

Неопределенное поведение может включать или не включать "сбой" вашей программы.

И чтобы ответить на ваш второй вопрос:

main(i){i=puts("Goodbye Cruel World!")/0;}

Есть разница между '\0' а также /0 Первый из них NUL характер, но второй деление на ноль. Так что этот код пытается разделить результат puts на ноль.

Код не выполняется, потому что тип аргумента puts() является const char *, что означает "указатель только для чтения" char".

Это статично, оно не меняется только потому, что вы пытаетесь передать ему что-то еще, вместо этого функция интерпретирует значение аргумента, как если бы он был указателем на символ (при условии, что компилятору даже удалось его скомпилировать, что является сложным предположением). здесь, так как int возвращаемое значение не преобразуется в const char *).

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

Другие вопросы по тегам