Как эта программа дублирует себя?
Этот код от Hacker Delight. Он говорит, что это самая короткая такая программа в C и имеет 64 символа в длину, но я ее не понимаю:
main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}
Я попытался скомпилировать его. Он компилируется с тремя предупреждениями и ошибкой.
Ответы
Ответ 1
Эта программа основана на предположениях, что
- Возвращает тип
main
int
- Функциональный параметр
int
по умолчанию и
- сначала будет оцениваться аргумент
a="main(a){printf(a,34,a=%c%s%c,34);}"
.
Он будет вызывать поведение undefined. Порядок оценки аргументов функции не гарантируется в 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.
Ответ 2
Он опирается на несколько причуд языка C и (что я думаю) undefined.
Сначала он определяет функцию main
. Законодательно объявлять функцию без типа возврата или типов параметров, и они будут считаться int
. Вот почему часть main(a){
работает.
Затем он вызывает printf
с 4 параметрами. Поскольку у него нет прототипа, предполагается, что он возвращает int
и принимает параметры int
(если ваш компилятор неявно объявляет об этом иначе, как это делает Clang).
Первый параметр считается int
и argc
в начале программы. Второй параметр - 34 (это ASCII для символа двойной кавычки). Третий параметр - это выражение присваивания, которое присваивает строку формата a
и возвращает ее. Он полагается на преобразование указателя на int, которое является законным в C. Последний параметр - это другой символ кавычки в числовой форме.
Во время выполнения спецификаторы формата %c
заменяются кавычками, %s
заменяется на строку формата, и вы снова получаете исходный источник.
Насколько мне известно, порядок оценки аргументов undefined. Эта процедура работает, потому что присваивание a="main(a){printf(a,34,a=%c%s%c,34);}"
оценивается до того, как a
передается как первый параметр в printf
, но, насколько я знаю, нет правила для его принудительного применения. Кроме того, это не может работать на 64-битных платформах, потому что преобразование "указатель-к-int" усекает указатель на 32-битное значение. На самом деле, хотя я вижу, как он работает на некоторых платформах, он не работает на моем компьютере с моим компилятором.
Ответ 3
Это работает на основе множества причуд, которые C позволяет вам делать, и некоторого поведения undefined, которое срабатывает в вашу пользу. В порядке:
main(a) { ...
Типы считаются int
, если они не указаны, поэтому это эквивалентно:
int main(int a) { ...
Даже если main
должен принимать либо 0, либо 2 аргумента, и это поведение undefined, это можно разрешить, просто игнорируя отсутствующий второй аргумент.
Далее, тело, которое я буду выделять. Обратите внимание, что a
является int
согласно main
:
printf(a,
34,
a = "main(a){printf(a,34,a=%c%s%c,34);}",
34);
Порядок оценки аргументов undefined, но мы полагаемся на третий аргумент - присваивание - сначала оцениваем. Мы также полагаемся на поведение undefined возможности присвоить 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);}
которая была оригинальной программой. Тада!
Ответ 4
Программа должна печатать собственный код. Обратите внимание на сходство строкового литерала с общим программным кодом. Идея состоит в том, что литерал будет использоваться как строка формата printf()
, потому что его значение присваивается переменной a
(хотя и в списке аргументов) и что она также будет передана как строка для печати (поскольку выражение-присваивание оценивает значение, которое было присвоено). 34
- это код ASCII для символа двойной кавычки ("
); используя его, избегает строки формата, содержащей экранированные символы кавычки букв.
Код основывается на неуказанном поведении в виде порядка оценки аргументов функции. Если они оцениваются в порядке списка аргументов, то программа, скорее всего, терпит неудачу, потому что значение a
будет затем использоваться как указатель на строку формата до того, как правильное значение было фактически присвоено ему.
Кроме того, тип a
по умолчанию равен int
, и нет гарантии, что int
достаточно широка, чтобы удерживать указатель объекта без его усечения.
Кроме того, стандарт C указывает только две допустимые подписи для main()
, а используемая подпись не входит в их число.
Более того, тип printf()
, который был выведен компилятором в отсутствие прототипа, неверен. Отнюдь не гарантировано, что компилятор будет генерировать вызывающую последовательность, которая работает для него.