Как эта программа дублирует себя?
Этот код от восторга Хакера. Там написано, что это самая короткая такая программа на C и длиной 64 символа, но я ее не понимаю:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
Я пытался скомпилировать это. Компилируется с 3 предупреждениями и без ошибок.
4 ответа
Эта программа опирается на предположения, что
- тип возврата
main
являетсяint
- тип параметра функции
int
по умолчанию и - Аргумент
a="main(a){printf(a,34,a=%c%s%c,34);}"
будет оцениваться первым.
Это вызовет неопределенное поведение. Порядок оценки аргументов функции не гарантируется в C.
Хотя эта программа работает следующим образом:
Выражение присваивания a="main(a){printf(a,34,a=%c%s%c,34);}"
назначит строку "main(a){printf(a,34,a=%c%s%c,34);}"
в a
и значение выражения присваивания будет "main(a){printf(a,34,a=%c%s%c,34);}"
тоже по стандарту C --C11: 6.5.16
Оператор присваивания сохраняет значение в объекте, обозначенном левым операндом. Выражение присваивания имеет значение левого операнда после присваивания [...]
Учитывая вышеизложенную семантику оператора присваивания, программа будет расширена как
main(a){
printf("main(a){printf(a,34,a=%c%s%c,34);}",34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);
}
ASCII 34
является "
, Спецификаторы и соответствующие им аргументы:
%c ---> 34
%s ---> "main(a){printf(a,34,a=%c%s%c,34);}"
%c ---> 34
Лучшая версия будет
main(a){a="main(a){a=%c%s%c;printf(a,34,a,34);}";printf(a,34,a,34);}
это 4
характер длиннее, но по крайней мере следует за K&R C.
Он основан на нескольких особенностях языка Си и (как я думаю, это) неопределенное поведение.
Во-первых, он определяет main
функция. Допустимо объявлять функцию без возвращаемого типа или типов параметров, и предполагается, что они int
, Вот почему main(a){
часть работает.
Затем он вызывает printf
с 4 параметрами. Поскольку у него нет прототипа, предполагается, что он вернет int
и принять int
параметры (если только ваш компилятор не объявляет об этом иначе, как это делает C lang).
Предполагается, что первый параметр int
и является argc
в начале программы. Вторым параметром является 34 (который является ASCII для символа двойной кавычки). Третий параметр - это выражение присваивания, которое присваивает строку формата a
и возвращает его. Он основан на преобразовании указателя в int, что допустимо в C. Последний параметр - это еще один символ кавычки в числовой форме.
Во время выполнения %c
спецификаторы формата заменяются кавычками, %s
заменяется форматной строкой, и вы снова получаете исходный источник.
Насколько я знаю, порядок оценки аргументов не определен. Этот Quine работает, потому что назначение a="main(a){printf(a,34,a=%c%s%c,34);}"
оценивается раньше a
передается в качестве первого параметра printf
, но, насколько я знаю, нет никаких правил, чтобы обеспечить его соблюдение. Кроме того, это не может работать на 64-разрядных платформах, поскольку преобразование указателя в int усекает указатель до 32-разрядного значения. На самом деле, хотя я вижу, как это работает на некоторых платформах, это не работает на моем компьютере с моим компилятором.
Это работает, основываясь на множестве ухищрений, которые позволяет вам делать C, и на некоторых неопределенных действиях, которые работают в вашу пользу. С целью:
main(a) { ...
Предполагается, что типы int
если не указан, то это эквивалентно:
int main(int a) { ...
Даже если main
должен принимать 0 или 2 аргумента, и это неопределенное поведение, это можно разрешить, просто игнорируя отсутствующий второй аргумент.
Далее тело, которое я буду разносить. Обратите внимание, что a
является int
согласно main
:
printf(a,
34,
a = "main(a){printf(a,34,a=%c%s%c,34);}",
34);
Порядок оценки аргументов не определен, но мы полагаемся на третий аргумент - присваивание, - которое оценивается первым. Мы также полагаемся на неопределенное поведение возможности назначить char *
для int
, Кроме того, обратите внимание, что 34 является значением ASCII "
, Таким образом, предполагаемое воздействие программы:
int main(int a, char** ) {
printf("main(a){printf(a,34,a=%c%s%c,34);}",
'"',
"main(a){printf(a,34,a=%c%s%c,34);}",
'"');
return 0; // also left off
}
Который при оценке дает:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
которая была оригинальной программой. Тада!
Программа должна печатать свой собственный код. Обратите внимание на сходство строкового литерала со всем кодом программы. Идея состоит в том, что литерал будет использоваться как printf()
строка формата, потому что ее значение назначено переменной a
(хотя и в списке аргументов) и что он также будет передан как строка для печати (потому что выражение присваивания оценивается как значение, которое было присвоено). 34
код ASCII для символа двойной кавычки ("
); при его использовании избегается строка формата, содержащая экранированные буквенные кавычки.
Код опирается на неопределенное поведение в форме порядка вычисления аргументов функции. Если они оцениваются в порядке списка аргументов, то программа, скорее всего, потерпит неудачу, так как значение a
будет использоваться в качестве указателя на строку формата, прежде чем ей будет назначено правильное значение.
Кроме того, тип a
по умолчанию int
и нет никакой гарантии, что int
достаточно широк, чтобы содержать указатель на объект без его усечения.
Кроме того, стандарт C определяет только две разрешенные подписи для main()
и используемой подписи нет среди них.
Кроме того, тип printf()
Вывод компилятора при отсутствии прототипа неверен. Ни в коем случае не гарантируется, что компилятор сгенерирует вызывающую последовательность, которая работает для него.