Я нашел C
код, который печатает от 1 до 1000 без циклов или условных выражений : но я не понимаю, как это работает. Может кто-нибудь пройти код и объяснить каждую строку?
#include <stdio.h>
#include <stdlib.h>
void main(int j) {
printf("%d\n", j);
(&main + (&exit - &main)*(j/1000))(j+1);
}
c
function-pointers
ob_dev
источник
источник
main
в C ++.Ответы:
Никогда не пишите такой код.
For
j<1000
,j/1000
это ноль (целочисленное деление). Так:эквивалентно:
Который:
Какие звонки
main
сj+1
.Если
j == 1000
, то получаются те же строки, что и:Который сводится к
Который есть
exit(j+1)
и выходит из программы.(&exit)(j+1)
иexit(j+1)
по сути одно и то же - цитируя C99 §6.3.2.1 / 4:exit
обозначение функции Даже без унарного&
оператора адресации он рассматривается как указатель на функцию. (&
Просто делает это явным.)А вызовы функций описаны в §6.5.2.2 / 1 и следующие:
Так
exit(j+1)
работает из-за автоматического преобразования типа функции в тип указателя на функцию, а также(&exit)(j+1)
работает с явным преобразованием в тип указателя на функцию.Тем не менее, приведенный выше код не соответствует (
main
принимает либо два аргумента, либо ни одного вообще) и&exit - &main
, я полагаю, не определен в соответствии с §6.5.6 / 9:Добавление
(&main + ...)
будет действительным само по себе и может быть использовано, если добавленное количество будет равно нулю, поскольку в п. 6.5.6 / 7 говорится:Таким образом, добавление нуля к
&main
будет в порядке (но не очень полезно).источник
foo(arg)
и(&foo)(arg)
эквивалентны, они вызывают foo с аргументом arg. newty.de/fpt/fpt.html - интересная страница с указателями на функции.foo
это указатель,&foo
это адрес этого указателя. Во втором случаеfoo
является массивом и&foo
эквивалентен foo.((void(*[])()){main, exit})[j / 1000](j + 1);
&foo
не то же самое, чтоfoo
когда дело доходит до массива.&foo
указатель на массив,foo
указатель на первый элемент Они имеют одинаковую ценность, хотя. Для функций,fun
и&fun
оба указатели на функцию.Он использует рекурсию, арифметику указателей и использует поведение округления целочисленного деления.
В
j/1000
перспективе округляется до 0 для всехj < 1000
; однаждыj
достигает 1000, это оценивает к 1.Теперь, если у вас есть
a + (b - a) * n
, гдеn
0 или 1, вы в конечном итоге сa
ifn == 0
иb
ifn == 1
. Используя&main
(адресmain()
) и&exit
дляa
иb
, термин(&main + (&exit - &main) * (j/1000))
возвращается,&main
когда значениеj
меньше 1000, в&exit
противном случае. Полученный указатель на функцию затем передается аргументj+1
.Вся эта конструкция приводит к рекурсивному поведению: пока
j
она меньше 1000,main
рекурсивно вызывает себя; когдаj
достигает 1000, он вызываетexit
вместо этого, вызывая выход из программы с кодом завершения 1001 (что немного грязно, но работает).источник
exit
, которая принимает код выхода в качестве аргумента и, в общем, выходит из текущего процесса. В этот момент j равно 1000, поэтому j + 1 равно 1001, что становится кодом выхода.