루프 나 조건문없이 1부터 1000까지 인쇄하는 C 코드는 어떻게 작동합니까?


148

루프 또는 조건부없이 1에서 1000까지 인쇄하는C 코드를 찾았습니다 .하지만 작동 방식을 이해하지 못합니다. 누구나 코드를 살펴보고 각 줄을 설명 할 수 있습니까?

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

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

1
C 또는 C ++로 컴파일하고 있습니까? 어떤 오류가 보입니까? mainC ++ 에서는 호출 할 수 없습니다 .
ninjalj

@ninjalj 저는 C ++ 프로젝트를 만들고 오류를 코드로 복사 / 붙여 넣기했습니다 : 불법, 왼쪽 피연산자에는 'void (__cdecl *) (int)'유형이 있고 expression은 완전한 객체 유형에 대한 포인터 여야합니다
ob_dev

1
@ninjalj이 코드는 ideone.org에서 작동하지만 비주얼 스튜디오에서는 작동하지 않습니다. ideone.com/MtJ1M
ob_dev

@oussama 비슷하지만 약간 이해하기 어렵습니다 : ideone.com/2ItXm 천만 에요. :)
Mark

2
이 줄에서 모든 '&'문자를 제거했습니다 (& main + (& exit-& main) * (j / 1000)) (j + 1); 이 코드는 여전히 작동합니다.
ob_dev

답변:


264

그런 코드를 작성하지 마십시오.


를 들어 j<1000, j/1000영 (정수 나눗셈)이다. 그래서:

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

다음과 같습니다.

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

어느 것이 :

(&main)(j+1);

어느 전화 mainj+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 연산자 또는 단항 & 연산자 의 피연산자 인 경우를 제외하고 , " function returning type " 유형의 함수 지정자는 " function returning type 에 대한 포인터 "유형의 표현식으로 변환됩니다 .

exit함수 지정자입니다. 단항 &주소 연산자가 없어도 함수에 대한 포인터로 취급됩니다. ( &그냥 명시 적으로 만듭니다.)

함수 호출은 §6.5.2.2 / 1 및 다음에 설명되어 있습니다.

호출 된 함수를 나타내는 표현식은 void를 반환하거나 배열 유형 이외의 객체 유형을 반환하는 함수에 대한 유형 포인터를 가져야 합니다.

따라서 exit(j+1)함수 유형을 포인터 대 함수 유형으로 자동 변환하기 때문에 작동하며 포인터 대 함수 유형 (&exit)(j+1)으로의 명시 적 변환과 함께 작동합니다.

즉, 위의 코드는 일치하지 않으며 ( main두 가지 주장을 취하거나 전혀 고려하지 않음) &exit - &main§6.5.6 / 9에 따라 정의되지 않았습니다.

두 개의 포인터를 빼면 둘 다 같은 배열 객체의 요소를 가리 키 거나 하나는 배열 객체의 마지막 요소를 지나야합니다. ...

추가는 (&main + ...)그 자체로 유효한 것, 그리고 사용할 수있는 경우 양이 영 (0)이 추가 §6.5.6 / 7 말한다 이후 :

이러한 연산자의 목적 상, 배열의 요소가 아닌 객체에 대한 포인터는 객체 유형을 요소 유형으로하여 길이가 1 인 배열의 첫 번째 요소에 대한 포인터와 동일하게 동작합니다.

따라서 0을 추가하면 &main괜찮을 것입니다 (그러나 많이 사용하지는 않음).


4
foo(arg)그리고 (&foo)(arg)그들은 인수 인수와 foo는 전화, 동일합니다. newty.de/fpt/fpt.html 은 함수 포인터에 대한 흥미로운 페이지입니다.
Mat

1
@Krishnabhadra : 첫 번째 경우 foo에는 포인터 &foo이며 해당 포인터의 주소입니다. 두 번째 경우 foo는 배열이며 &foofoo 와 같습니다.
Mat

8
적어도 C99에 대해 불필요하게 복잡한 :((void(*[])()){main, exit})[j / 1000](j + 1);
Per Johansson

1
&foofoo배열 과 관련이 없습니다 . &foo배열에 foo대한 포인터이고 첫 번째 요소에 대한 포인터입니다. 그들은 같은 가치를 가지고 있습니다. 함수의 경우, fun&fun함수에 모두 포인터이다.
Per Johansson

1
참고로 위에서 언급 한 다른 질문대한 관련 답변을 보면 실제로 컴포 턴트 C99 인 변형이 있음을 알 수 있습니다. 무섭지 만 사실입니다.
Daniel Pryden

41

재귀, 포인터 산술을 사용하고 정수 나누기의 반올림 동작을 이용합니다.

j/1000용어는 모두 0으로 내림합니다 j < 1000. 일단 j도달하면 (1000)는 1로 평가.

지금 당신이있는 경우 a + (b - a) * n, 여기서 n0 또는 1, 당신은으로 끝날 a경우 n == 0, 그리고 b경우 n == 1. 사용 &main(주소 main())와 &exit대한 ab용어 (&main + (&exit - &main) * (j/1000))수익률 1000 이하를 , 그렇지. 그런 다음 결과 함수 포인터에 인수가 제공 됩니다.&mainj&exitj+1

이 전체 구조는 재귀 동작을 초래 j합니다. 1000 미만인 동안 main재귀 적으로 호출됩니다. 때 j1000에 도달, 그것을 호출 exit종료 코드 1001로 프로그램 종료하고, 대신 (종류 더러운이지만, 작품).


1
좋은 답변이지만 의심의 여지가 있습니다. 종료 코드 1001을 사용하는 주 출구는? Main은 아무것도 반환하지 않습니다. 기본 반환 값은 무엇입니까?
Krishnabhadra

2
j가 1000에 도달하면 main은 더 이상 재귀하지 않습니다. 대신, libc 함수를 호출하여 exit종료 코드를 인수로 사용하고 현재 프로세스를 종료합니다. 이 시점에서 j는 1000이므로 j + 1은 1001과 같으며 종료 코드가됩니다.
tdammers
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.