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


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

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

void main(int j) {
  printf("%dn", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}
2 147

2 ответа:

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


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

и вызовы функций описаны в §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 было бы хорошо (но не много пользы).

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

The 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.

вся эта конструкция приводит к рекурсивному поведению: while j ниже 1000, main рекурсивно вызывает саму себя, когда j достигает 1000, он вызывает exit вместо этого, делая выход программы с кодом выхода 1001 (который является своего рода грязным, но работает).