주 함수 대신 main이라는 전역 변수가있는 프로그램이 어떻게 작동 할 수 있습니까?


97

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

#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옵션을 사용 했지만 프로그램은 여전히 ​​컴파일되고 실행됩니다.


11
@ πάνταῥεῖ : 언어 변호사 태그가 필요한 이유는 무엇입니까?
소멸자

14
참고 195에 대한 오피 코드입니다 RET명령 및 C 호출 규칙에서, 호출자가 스택을 지 웁니다있다.
Brian

2
@PravasiMeet "그런 다음이 프로그램이 어떻게 실행되는지"– 변수에 대한 초기화 코드가 실행되어야한다고 생각하지 main()않습니까? (함수 없이도 실제로는 완전히 관련이 없습니다.)
The Paramagnetic Croissant

4
저는 프로그램 segfault가있는 그대로 (64 비트 리눅스, g ++ 5.1 / clang 3.6)를 발견 한 사람들 중 하나입니다. 그러나 프로그램이 법적으로 잘못된 형식으로 유지 되더라도이를로 수정 int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(및 포함 <cstdlib>) 하여 이를 수정할 수 있습니다 .
Mike Kinghan 2015 년

11
@Brian 그런 문장을 만들 때 아키텍처를 언급해야합니다. 전 세계가 VAX가 아닙니다. 또는 x86. 또는 무엇이든.
dmckee --- 전 중재자 새끼 고양이

답변:


84

무슨 일이 일어나고 있는지에 대한 질문으로 들어가기 전에 결함 보고서 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 답변 도 참조하십시오 .


구현시에도 경고가 표시되지 않는 이유. (-Wall & -Wextra를 사용하면 여전히 단일 경고가 표시되지 않습니다). 왜? 이 질문에 대한 @Mark B의 대답에 대해 어떻게 생각하십니까?
소멸자

IMHO, 컴파일러는 main예약 된 식별자 (3.6.1 / 3)가 아니기 때문에 경고를 표시 하지 않아야합니다. 이 경우 VS2013 의이 사례 처리 (Francis Cugler의 답변 참조)가 gcc 및 clang보다 처리하는 것이 더 정확하다고 생각합니다.
cdmh

@PravasiMeet 이전 버전의 gcc가 진단을 제공하지 않은 이유에 대한 답변 wrt를 업데이트했습니다.
Shafik Yaghmour 2015 년

2
... 그리고 실제로 Linux / x86-64에서 g ++ 5.2 (프로그램 허용- "최신 버전"에 대해 농담하지 않은 것 같음)를 사용하여 OP 프로그램을 테스트 할 때 예상했던 위치에서 정확히 충돌이 발생합니다. 할 것이다.
zwol 2015 년

1
@Walter 나는 이것이 전자가 훨씬 더 좁은 질문을하고있는 중복이라고 생각하지 않습니다. 대부분의 SO 질문을 이전 질문의 일부 버전으로 요약 할 수 있기 때문에 중복에 대해 더 환원주의적인 견해를 가진 SO 사용자 그룹이 분명히 있습니다. 그러면 그다지 유용하지 않을 것입니다.
Shafik Yaghmour

20

3.6.1 / 1부터 :

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

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

그런 다음 3.6.1 / 3에서 :

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

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

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


7
main다른 이름 으로 이름 을 바꾸면 연결이 실패한다는 점이 흥미 롭습니다 . (.text+0x20): undefined reference to main '`
Fred Larson

1
프로그램이 독립되어 있음을 gcc에 지정할 필요가 없습니까?
Shafik Yaghmour 2015 년

9

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기호는 프로그램의 시작으로 해석됩니다. 동작은 플랫폼에 따라 다릅니다.


1
같은 주 브라이언 지적이 195 에 대한 오피 코드입니다 ret일부 아키텍처에서. 따라서 제로 지시를 말하는 것은 정확하지 않을 수 있습니다.
Shafik Yaghmour

@ShafikYaghmour 귀하의 의견에 감사드립니다. 어셈블러 지시문이 엉망이었습니다.
sergej

8

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

표준에서 :

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

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


내가 인용 한 결함 보고서 이전에 이것은 정의되지 않은 평범한 동작이라고 생각합니다.
Shafik Yaghmour 2015 년

@ShafikYaghmour은 일반적인 원칙은 어디 표준가 사용하는 모든 장소에 적용 할 수 있음을인가 하여야한다 ?
R Sahu 2015 년

예라고 말하고 싶지만 차이점에 대한 좋은 설명을 보지 못했습니다. 이 토론 에서 알 수 있듯이 잘못된 형식의 NDR과 정의되지 않은 동작은 진단이 필요하지 않기 때문에 동의어 일 수 있습니다. 이것은 잘못된 형식을 암시하는 것처럼 보이며 UB는 구별되지만 확실하지 않습니다.
Shafik Yaghmour

3
C99 섹션 4 ( "Conformance")는이를 명확하게합니다. "제약 조건 외부에 나타나는 'shall'또는 'shall not'요구 사항을 위반하면 동작이 정의되지 않습니다." C ++ 98 또는 C ++ 11에서 동등한 문구를 찾을 수 없지만위원회가 그 자리에 있도록 의도 한 것 같습니다. (C 및 C ++위원회는 실제로 두 표준 간의 모든 용어 차이를 해결해야합니다.)
zwol

7

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

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

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

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


4

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 ==========

2
FWIW는 디버거의 메시지가 아니라 링커 오류입니다. 컴파일은 성공했지만 링커 main()는 유형의 변수이기 때문에 함수를 찾을 수 없습니다int
cdmh

답장을 보내 주셔서 감사합니다.이를 반영하기 위해 초기 답변을 다시 말하겠습니다.
Francis Cugler

-1

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.