Как работает код C, который печатает от 1 до 1000 без циклов или условных операторов? - PullRequest
148 голосов
/ 29 октября 2011

Я нашел C код, который печатает от 1 до 1000 без циклов или условных выражений : Но я не понимаю, как это работает. Кто-нибудь может пройти код и объяснить каждую строку?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

Ответы [ 2 ]

264 голосов
/ 29 октября 2011

Никогда не пишите такой код.


Для j<1000, j/1000 равно нулю (целочисленное деление). Итак:

(&main + (&exit - &main)*(j/1000))(j+1);

эквивалентно:

(&main + (&exit - &main)*0)(j+1);

Что такое:

(&main)(j+1);

Который звонит main с j+1.

Если j == 1000, то появляются те же строки, что и:

(&main + (&exit - &main)*1)(j+1);

Что сводится к

(&exit)(j+1);

То есть exit(j+1) и выходит из программы.


(&exit)(j+1) и exit(j+1) по сути одно и то же - цитирование C99 §6.3.2.1 / 4:

Обозначение функции - это выражение с типом функции. За исключением случаев, когда это операнд оператора sizeof или унарный оператор & , обозначение функции с Тип " Функция возврата типа " преобразуется в выражение с типом " указатель на функция, возвращающая тип".

exit - обозначение функции. Даже без унарного оператора & address-of он обрабатывается как указатель на функцию. (& просто делает это явным.)

А вызовы функций описаны в §6.5.2.2 / 1 и далее:

Выражение, которое обозначает вызываемую функцию, должно иметь тип указатель на функцию , возвращающий void или возвращающий тип объекта, отличный от типа массива.

Так что exit(j+1) работает из-за автоматического преобразования типа функции в тип указателя на функцию, а (&exit)(j+1) работает также с явным преобразованием в тип указателя на функцию.

При этом вышеприведенный код не соответствует (main принимает либо два аргумента, либо ни одного вообще), а &exit - &main, я полагаю, не определен согласно §6.5.6 / 9:

Когда вычитаются два указателя, оба должны указывать на элементы одного и того же объекта массива , или один за последним элементом объекта массива; ...

Добавление (&main + ...) было бы действительным само по себе и могло бы использоваться, , если добавленное количество было равно нулю, поскольку §6.5.6 / 7 гласит:

Для целей этих операторов указатель на объект, который не является элементом массив ведет себя так же, как указатель на первый элемент массива длиной один с тип объекта как тип элемента.

Так что добавление нуля к &main будет в порядке (но не очень полезно).

41 голосов
/ 29 октября 2011

Он использует рекурсию, арифметику указателей и использует поведение округления целочисленного деления.

Термин j/1000 округляется до 0 для всех j < 1000;как только j достигает 1000, оно становится равным 1.

Теперь, если у вас есть a + (b - a) * n, где n равно 0 или 1, вы получите a, если n == 0, и b если n == 1.Используя &main (адрес main()) и &exit для a и b, термин (&main + (&exit - &main) * (j/1000)) возвращает &main, когда j ниже 1000, &exit в противном случае.Полученный указатель на функцию затем получает аргумент j+1.

Вся эта конструкция приводит к рекурсивному поведению: в то время как j ниже 1000, main вызывает себя рекурсивно;когда j достигает 1000, вместо этого вызывается exit, что приводит к завершению программы с кодом завершения 1001 (что-то грязное, но работает).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...