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

этот код от восторга хакера. Он говорит, что это самая короткая такая программа В C и имеет длину 64 символа, но я этого не понимаю:

    main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);}

Я попытался скомпилировать его. Он компилируется с 3 предупреждениями и без ошибок.

4 ответов


эта программа опирается на предположения о том, что

  • тип возвращаемого main is 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);}" 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() вывод компилятора при отсутствии прототипа неверен. Отнюдь не гарантируется, что компилятор будет генерировать последовательность вызовов, которая работает для него.