Как эта программа дублирует себя?
этот код от восторга хакера. Он говорит, что это самая короткая такая программа В C и имеет длину 64 символа, но я этого не понимаю:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
Я попытался скомпилировать его. Он компилируется с 3 предупреждениями и без ошибок.
4 ответов
эта программа опирается на предположения о том, что
- тип возвращаемого
main
isint
- тип параметра функции -
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);}"
to 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
is "
. Спецификаторы и соответствующие им аргументы:
%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.
он опирается на несколько причуд языка C и (что я думаю) неопределенное поведение.
во-первых, он определяет . Это законно объявить функцию без возвращаемого типа или типов параметров, и они будут считаться int
. Вот почему main(a){
часть работ.
затем он называет printf
С 4 параметрами. Поскольку он не имеет прототипа, предполагается, что return int
и признать int
параметры (если ваш компилятор неявно заявляет об обратном, как это делает Clang).
первый параметр предполагается int
и argc
в начале программы. Второй параметр-34 (ASCII для символа двойной кавычки). Третий параметр-это выражение присваивания, которое присваивает строке формата значение a
и возвращает его. Он основан на преобразовании указателя в int, которое является законным в C. Последний параметр-это еще один символ кавычки в числовой форме.
At время выполнения %c
спецификаторы формата заменяются кавычками,%s
заменяется строкой формата, и вы снова получаете исходный источник.
насколько мне известно, порядок оценки аргументов не определен. Этот Куайн работает, потому что назначение 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);
порядок оценки аргументов не определен, но мы полагаемся на 3 - й аргумент - назначение-сначала оценивается. Мы также полагаемся на неопределенное поведение возможности назначить 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
(хотя и в списке аргументов) и что он также будет передан как строка для печати (потому что выражение присваивания оценивает значение, которое было назначено). The 34
код ASCII для символа двойной кавычки ("
); использование его позволяет избежать строки формата, содержащей символы кавычек экранированного литерала.
код опирается на неопределенное поведение в виде порядка оценки аргументов функции. Если они оцениваются в порядке списка аргументов, то программа, скорее всего, потерпит неудачу, потому что значение a
затем будет использоваться в качестве указателя на строку формата, прежде чем ей будет присвоено правильное значение.
кроме того, тип a
по умолчанию:int
, и нет никакой гарантии, что int
достаточно широк, чтобы удерживать указатель объекта без его усечения.
кроме того, стандарт C указывает только две разрешенные подписи для main()
и подпись не среди них.
кроме того,printf()
вывод компилятора при отсутствии прототипа неверен. Отнюдь не гарантируется, что компилятор будет генерировать последовательность вызовов, которая работает для него.