Как работает код C, который печатает от 1 до 1000 без циклов или условных операторов?

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

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

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

Ответ 1

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


Для 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, он рассматривается как указатель на функцию. (& просто делает его явным.)

И вызовы функций описаны в п. 6.5.2.2/1 и следующие:

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

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

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

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

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

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

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

Ответ 2

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

Термин 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 (который является грязным, но работает).