주 함수 대신 main이라는 전역 변수가있는 프로그램이 어떻게 작동 할 수 있습니까? 컴파일되고 실행되며 다음과 같이 인쇄됩니다.

다음 프로그램을 고려하십시오.

#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 ); 

Windows 7 OS에서 g ++ 4.8.1 (mingw64)을 사용하면 프로그램이 제대로 컴파일되고 실행되며 다음과 같이 인쇄됩니다.

C ++는 훌륭합니다!

콘솔에. main함수가 아닌 전역 변수로 보입니다. 이 프로그램이 기능없이 어떻게 실행될 수 main()있습니까? 이 코드는 C ++ 표준을 준수합니까? 프로그램의 동작이 잘 정의되어 있습니까? 나는 또한 -pedantic-errors옵션을 사용 했지만 프로그램은 여전히 ​​컴파일되고 실행됩니다.



답변

무슨 일이 일어나고 있는지에 대한 질문으로 들어가기 전에 결함 보고서 1886 : Language linkage for main ()에 따라 프로그램이 잘못 구성되었음을 지적하는 것이 중요합니다 .

[…] 전역 범위에서 변수 main을 선언하거나 모든 네임 스페이스에서 C 언어 연결로 main이라는 이름을 선언하는 프로그램은 형식이 잘못되었습니다. […]

최신 버전의 clang 및 gcc는이를 오류로 만들고 프로그램이 컴파일되지 않습니다 ( gcc 라이브 예제 참조 ).

error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 );
    ^

그렇다면 왜 이전 버전의 gcc 및 clang에는 진단이 없었습니까? 이 결함 보고서에는 2014 년 말까지 제안 된 해결 방법이 없었기 때문에이 사례는 최근에야 명시 적으로 잘못 구성되어 진단이 필요했습니다.

이전에는 [basic.start.main] 섹션의 C ++ 표준 초안 의 shall 요구 사항을 위반하고 있으므로 정의되지 않은 동작 인 것 같습니다 .3.6.1

프로그램은 프로그램의 지정된 시작 인 main이라는 전역 함수를 포함해야합니다. […]

정의되지 않은 동작은 예측할 수 없으며 진단이 필요하지 않습니다. 행동을 재현 할 때 나타나는 불일치는 일반적인 정의되지 않은 행동입니다.

그렇다면 코드는 실제로 무엇을하고 있으며 어떤 경우에는 왜 결과를 생성합니까? 우리가 가진 것을 보자 :

declarator
|        initializer----------------------------------
|        |                                           |
v        v                                           v
int main = ( std::cout << "C++ is excellent!\n", 195 );
    ^      ^                                   ^
    |      |                                   |
    |      |                                   comma operator
    |      primary expression
global variable of type int

우리는이 mainINT 전역 네임 스페이스에 선언 및 초기화되고, 변수는 정적 저장 기간을 가지고 있습니다. 호출 시도가 이루어지기 전에 초기화가 발생할지 여부는 구현에 정의 main되어 있지만 gcc가 호출하기 전에이를 수행하는 것으로 보입니다 main.

코드는 쉼표 연산자 를 사용하고 왼쪽 피연산자는 폐기 된 값 표현식이며 여기서는 호출의 부작용에만 사용됩니다 std::cout. 쉼표 연산자의 결과는이 경우 195변수에 할당 된 prvalue 인 오른쪽 피연산자입니다 main.

sergejcout 가 정적 초기화 중에 호출되는 생성 된 어셈블리 쇼를 지적하는 것을 볼 수 있습니다 . 토론에 대한 더 흥미로운 점 은 라이브 godbolt 세션을 참조하십시오 .

main:
.zero   4

그리고 후속 :

movl    $195, main(%rip)

가능한 시나리오는 프로그램이 main유효한 코드가있을 것으로 예상 하는 기호로 점프 하고 경우에 따라 seg-fault 합니다. 따라서이 경우 유효한 기계어 코드를 변수에 저장 하면 코드 실행을 허용하는 세그먼트에 있다고 가정 main하고 실행 가능한 프로그램으로 이어질 수 있습니다. 우리는 볼 수 있습니다 이 1984 IOCCC 항목이 수행 그냥 .

우리가 GCC가 (사용하여 C에서이 일을 얻을 수 있습니다 나타납니다 살고 볼 ) :

const int main = 195 ;

main실행 가능한 위치에 있지 않기 때문에 아마도 변수 가 const가 아닌 경우 seg-faults , Hat Tip은 여기 에이 아이디어를 제공했습니다.

이 질문의 C 특정 버전에 대한 FUZxxl 답변 도 참조하십시오 .


답변

3.6.1 / 1부터 :

프로그램은 프로그램의 지정된 시작 인 main이라는 전역 함수를 포함해야합니다. 독립 환경의 프로그램이 주 기능을 정의하는 데 필요한지 여부는 구현으로 정의됩니다.

이로부터 g ++는 주 함수없이 프로그램 (아마 “자립”절)을 허용하는 것처럼 보입니다.

그런 다음 3.6.1 / 3에서 :

메인 기능은 프로그램 내에서 사용되지 않습니다 (3.2). main의 연결 (3.5)은 정의 된 구현입니다. main을 인라인 또는 정적으로 선언하는 프로그램이 잘못되었습니다. 이름 main은 달리 예약되어 있지 않습니다.

그래서 여기서 우리는 정수 변수라는 이름을 갖는 것이 완벽하다는 것을 배웁니다. main .

마지막으로 출력이 인쇄되는 이유가 궁금하다면의 초기화 int main는 쉼표 연산자를 사용하여 cout정적 초기화에서 실행 한 다음 초기화를 수행하기위한 실제 정수 값을 제공합니다.


답변

gcc 4.8.1은 다음 x86 어셈블리를 생성합니다.

.LC0:
    .string "C++ is excellent!\n"
    subq    $8, %rsp    #,
    movl    std::__ioinit, %edi #,
    call    std::ios_base::Init::Init() #
    movl    $__dso_handle, %edx #,
    movl    std::__ioinit, %esi #,
    movl    std::ios_base::Init::~Init(), %edi  #,
    call    __cxa_atexit    #
    movl    $.LC0, %esi #,
    movl    std::cout, %edi #,
    call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

참고 cout하지에, 초기화시 호출 main기능!

.zero 4location main에서 시작하는 4 (0- 초기화) 바이트를 선언합니다. 여기서은 변수 [!]main 의 이름입니다 .

main기호는 프로그램의 시작으로 해석됩니다. 동작은 플랫폼에 따라 다릅니다.


답변

그것은 잘못된 프로그램입니다. 내 테스트 환경 cygwin64 / g ++ 4.9.3에서 충돌합니다.

표준에서 :

3.6.1 주요 기능 [basic.start.main]

1 프로그램은 프로그램의 지정된 시작 인 main이라는 전역 기능을 포함해야합니다.


답변

이것이 작동한다고 생각하는 이유는 컴파일러main()함수를 컴파일하고 있다는 것을 모르기 때문에 할당 부작용이있는 전역 정수를 컴파일합니다.

객체 형식 이 있는지 변환 유닛은 로 컴파일는 구별 할 수없는 기능을 상징 하고, 변수 기호 .

따라서 링커는 기꺼이 (변수) 기본 기호에 연결하고이를 함수 호출처럼 처리합니다. 그러나 런타임 시스템 이 전역 변수 초기화 코드를 실행할 때까지는 아닙니다 .

샘플을 실행했을 때 인쇄되었지만 seg-fault가 발생했습니다 . 런타임 시스템int 변수함수 인 것처럼 실행하려고 할 때라고 가정합니다 .


답변

VS2013을 사용하는 Win7 64 비트 OS에서 이것을 시도했는데 제대로 컴파일되지만 응용 프로그램을 빌드하려고하면 출력 창에서이 메시지가 나타납니다.

1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------
1>LINK : fatal error LNK1561: entry point must be defined
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

답변

여기서 까다로운 일을하고 있습니다. main (어떻게 든)은 정수로 선언 될 수 있습니다. 목록 연산자를 사용하여 메시지를 인쇄 한 다음 195를 할당했습니다. 아래의 누군가가 말했듯이 C ++로 편안하지 않다는 것은 사실입니다. 그러나 컴파일러는 사용자 정의 이름, main을 찾지 못해 불만을 제기하지 않았습니다. main은 시스템 정의 함수가 아니며 사용자 정의 함수 및 프로그램이 실행을 시작하는 것은 main ()이 아니라 Main Module입니다. 다시 main ()은 로더에 의해 의도적으로 실행되는 시작 함수에 의해 호출됩니다. 그런 다음 모든 변수가 초기화되고 초기화하는 동안 출력됩니다. 그게 다야. main ()없는 프로그램은 괜찮지 만 표준은 아닙니다.