Как эта программа дублирует себя?

Этот код от восторга Хакера. Там написано, что это самая короткая такая программа на 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() Вывод компилятора при отсутствии прототипа неверен. Ни в коем случае не гарантируется, что компилятор сгенерирует вызывающую последовательность, которая работает для него.

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