Подтвердить что ты не робот

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

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

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

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

4b9b3361

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