main () 메서드는 C에서 어떻게 작동합니까?


96

주요 방법을 작성하는 데 두 가지 다른 서명이 있음을 알고 있습니다.

int main()
{
   //Code
}

또는 명령 줄 인수를 처리하기 위해 다음과 같이 작성합니다.

int main(int argc, char * argv[])
{
   //code
}

에서 C++우리가하는 방법을 오버로드 만에 할 수 있습니다 알고 C어떻게 컴파일러는이 두 가지 서로 다른 서명을 어떻게 처리합니까 main기능을?


14
오버로딩은 동일한 프로그램에서 동일한 이름을 가진 두 개의 메소드를 갖는 것을 의미합니다 . (또는 실제로는 그러한 구조를 가진 거의 모든 언어로) main단일 프로그램에서 하나의 메소드 만 가질 수 있습니다 C.
카일 스트랜드

13
C에는 메소드가 없습니다. 그것은 기능이 있습니다. 메서드는 객체 지향 "일반"함수의 백엔드 구현입니다. 프로그램은 일부 개체 인수가있는 함수를 호출하고 개체 시스템은 해당 유형에 따라 메서드 (또는 메서드 집합)를 선택합니다. C는 여러분이 직접 시뮬레이트하지 않는 한 이런 것들을 가지고 있지 않습니다.
Kaz

4
프로그램 진입 점에 대한 심도있는 논의를 위해 mainJohn R. Levines의 고전적인 책인 "Linkers & Loaders"를 추천합니다.
Andreas Spindler 2013 년

1
C에서 첫 번째 형식은 int main(void), 아닙니다 int main()( int main()폼 을 거부하는 컴파일러를 본 적이 없지만 ).
키이스 톰슨

1
@harper :이 ()양식은 더 이상 사용 되지 않으며 심지어 허용 된 것인지도 명확하지 않습니다 main(구현에서 특별히 허용 된 양식으로 문서화하지 않는 한). C 표준 (5.1.2.2.1 프로그램 시작 참조)은 ()양식에 대해 언급하지 않으며 이는 양식과 완전히 동일 하지 않습니다 (). 이 댓글에 대한 세부 정보가 너무 깁니다.
Keith Thompson

답변:


133

C 언어의 일부 기능은 방금 작동하는 해킹으로 시작되었습니다.

기본 및 가변 길이 인수 목록에 대한 다중 서명은 이러한 기능 중 하나입니다.

프로그래머는 함수에 추가 인수를 전달할 수 있으며 주어진 컴파일러에서 나쁜 일이 발생하지 않는다는 것을 알았습니다.

호출 규칙이 다음과 같은 경우입니다.

  1. 호출 함수는 인수를 정리합니다.
  2. 가장 왼쪽의 인수는 스택의 맨 위 또는 스택 프레임의 기본에 더 가깝기 때문에 가짜 인수가 주소 지정을 무효화하지 않습니다.

이러한 규칙을 따르는 호출 규칙 중 하나는 호출자가 인수를 팝하고 오른쪽에서 왼쪽으로 푸시되는 스택 기반 매개 변수 전달입니다.

 ;; pseudo-assembly-language
 ;; main(argc, argv, envp); call

 push envp  ;; rightmost argument
 push argv  ;; 
 push argc  ;; leftmost argument ends up on top of stack

 call main

 pop        ;; caller cleans up   
 pop
 pop

이러한 유형의 호출 규칙이 적용되는 컴파일러에서는 두 가지 유형 main또는 추가 유형 을 지원하기 위해 특별히 수행 할 필요가 없습니다 . main인수가없는 함수일 수 있습니다.이 경우 스택에 푸시 된 항목을 알 수 없습니다. 두 인수의 함수 인 경우 argcargv두 개의 최상위 스택 항목을 찾습니다 . 환경 포인터 (공통 확장)가있는 플랫폼 별 3 개 인수 변형 인 경우에도 작동합니다. 스택 맨 위에서 세 번째 요소로 세 번째 인수를 찾습니다.

따라서 고정 호출은 모든 경우에 작동하므로 단일 고정 시작 모듈을 프로그램에 연결할 수 있습니다. 해당 모듈은 다음과 유사한 함수로 C로 작성 될 수 있습니다.

/* I'm adding envp to show that even a popular platform-specific variant
   can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
  /* This is the real startup function for the executable.
     It performs a bunch of library initialization. */

  /* ... */

  /* And then: */
  exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

즉,이 시작 모듈은 항상 3 인수 main을 호출합니다. main이 인수를 사용하지 않거나 만 사용 int, char **하는 경우 호출 규칙으로 인해 인수가 필요하지 않을뿐만 아니라 정상적으로 작동합니다.

프로그램에서 이런 종류의 일을한다면 이식이 불가능하고 ISO C에서 정의되지 않은 동작으로 간주됩니다. 한 가지 방식으로 함수를 선언하고 호출하고 다른 방식으로 정의하는 것입니다. 그러나 컴파일러의 시작 트릭은 이식 가능할 필요가 없습니다. 휴대용 프로그램에 대한 규칙을 따르지 않습니다.

그러나 호출 규칙이 이러한 방식으로 작동 할 수 없다고 가정합니다. 이 경우 컴파일러는 main특별히 처리해야 합니다. main함수를 컴파일하고 있음을 발견 하면 세 개의 인수 호출과 호환되는 코드를 생성 할 수 있습니다.

즉, 다음과 같이 작성합니다.

int main(void)
{
   /* ... */
}

그러나 컴파일러가 그것을 볼 때 본질적으로 코드 변환을 수행하여 컴파일하는 함수가 다음과 같이 보입니다.

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
   /* ... */
}

이름 __argc_ignore이 문자 그대로 존재하지 않는다는 점을 제외하면 . 이러한 이름은 범위에 도입되지 않으며 사용되지 않은 인수에 대한 경고도 없습니다. 코드 변환은 컴파일러가 세 개의 인수를 정리해야한다는 것을 알고있는 올바른 연결로 코드를 내보내도록합니다.

또 다른 구현 전략은 컴파일러 또는 링커가 __start함수 (또는 호출되는 모든 항목) 를 사용자 지정 생성 하거나, 미리 컴파일 된 여러 대안 중에서 하나를 선택하는 것입니다. 지원되는 형식 중 어느 main것이 사용 되는지에 대한 정보를 개체 파일에 저장할 수 있습니다 . 링커는이 정보를보고 main프로그램 정의와 호환되는 호출이 포함 된 시작 모듈의 올바른 버전을 선택할 수 있습니다 . C 구현에는 일반적으로 지원되는 형식 main이 적으므로이 접근 방식이 가능합니다.

C99 언어 용 컴파일러 main는 함수가 return명령문 없이 종료 되면 동작이 return 0실행 된 것처럼 동작 하는 해킹을 지원 하기 위해 항상 어느 정도 특별히 처리해야합니다 . 이것은 다시 코드 변환으로 처리 할 수 ​​있습니다. 컴파일러 main는 호출 된 함수 가 컴파일되고 있음을 인식합니다 . 그런 다음 신체 끝 부분에 잠재적으로 도달 할 수 있는지 여부를 확인합니다. 그렇다면return 0;


34

과부하가 없습니다 mainC ++에서도 . 주 기능은 프로그램의 진입 점이며 단일 정의 만 존재해야합니다.

표준 C 용

호스팅 된 환경 (일반적인 환경)의 경우 C99 표준은 다음과 같이 말합니다.

5.1.2.2.1 프로그램 시작

프로그램 시작시 호출되는 함수의 이름은 main. 구현은이 함수에 대한 프로토 타입을 선언하지 않습니다. 반환 유형 int과 매개 변수없이 정의되어야합니다 .

int main(void) { /* ... */ }

또는 두 개의 매개 변수를 사용합니다 (여기에서 argc및 로 지칭됩니다. argv이름이 선언 된 함수에 로컬이기 때문에 모든 이름을 사용할 수 있음).

int main(int argc, char *argv[]) { /* ... */ }

또는 동등한 것; 9) 또는 다른 구현 정의 방식으로.

9) 따라서으로 int정의 된 typedef 이름으로 대체 int되거나의 유형이 argv로 작성 될 수 있습니다 char **argv.

표준 C ++의 경우 :

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

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

2 구현시 주요 기능을 미리 정의 해서는 안됩니다 . 이 기능은 과부하되지 않아야합니다 . 반환 유형은 int 유형이어야하지만 그렇지 않으면 유형이 구현 정의됩니다. 모든 구현은 main에 대한 다음 정의를 모두 허용해야합니다.

int main() { /* ... */ }

int main(int argc, char* argv[]) { /* ... */ }

C ++ 표준은 "[주 함수]가 int 유형의 반환 유형을 가져야하지만 그렇지 않으면 해당 유형이 구현 정의 됨"이라고 명시 적으로 명시하고 C 표준과 동일한 두 개의 서명이 필요합니다.

A의 호스팅 환경 (또한 C 라이브러리를 지원하는 AC 환경) - 운영 체제를 호출합니다 main.

A의 호스트되지 않은 환경 (임베디드 애플리케이션을위한 하나) 당신은 항상 전처리 지침 등을 이용하여 프로그램의 진입 점 (또는 종료)를 변경할 수 있습니다

#pragma startup [priority]
#pragma exit [priority]

우선 순위는 선택적 정수입니다.

Pragma 시작은 기본 (우선 순위) 전에 함수를 실행하고 pragma 종료는 기본 함수 다음에 함수를 실행합니다. 하나 이상의 시작 지시문이있는 경우 우선 순위는 어느 것이 먼저 실행될 것인지 결정합니다.


4
이 대답은 실제로 컴파일러가 실제로 상황을 처리하는 방법에 대한 질문에 실제로 대답한다고 생각하지 않습니다. @Kaz가 제공하는 대답은 내 의견으로는 더 많은 통찰력을 제공합니다.
Tilman Vogel

4
나는이 대답이 @Kaz의 질문보다 더 잘 대답한다고 생각합니다. 원래 질문은 연산자 오버로딩이 발생하고 있다는 인상 아래 있으며,이 답변은 일부 오버로딩 솔루션 대신 컴파일러가 두 가지 다른 서명을 허용 함을 보여줌으로써 해결합니다. 컴파일러 세부 사항은 흥미롭지 만 질문에 답하는 데 필요하지 않습니다.
Waleed Khan

1
독립 환경 ( "호스팅되지 않음")의 경우 일부 #pragma보다 훨씬 더 많은 작업이 진행됩니다. 하드웨어에서 리셋 인터럽트가 있으며 실제로 프로그램이 시작됩니다. 거기에서 모든 기본 설정이 실행됩니다 : 설정 스택, 레지스터, MMU, 메모리 매핑 등. 그런 다음 NVM에서 정적 스토리지 변수 (.data 세그먼트) 로의 초기화 값 복사가 발생하고 모두에서 "zero-out"이 발생합니다. 0으로 설정해야하는 정적 스토리지 변수 (.bss 세그먼트). C ++에서는 정적 저장 기간이있는 개체의 생성자가 호출됩니다. 모든 작업이 완료되면 main이 호출됩니다.
Lundin

8

과부하가 필요하지 않습니다. 예, 두 가지 버전이 있지만 한 번에 하나만 사용할 수 있습니다.


5

이것은 C 및 C ++ 언어의 이상한 비대칭 및 특수 규칙 중 하나입니다.

제 생각에는 그것은 역사적인 이유만으로 존재하며 그 뒤에 진정한 심각한 논리는 없습니다. 주 main(예를 들어, 다른 이유도 특별 mainC ++로 재귀 할 수 없으며 그 주소와 마지막을 생략 할 수 있습니다 ++ C99 / C에 걸릴 수 없습니다return 문을).

또한 C ++에서도 오버로드가 아닙니다. 프로그램에 첫 번째 형식이 있거나 두 번째 형식이 있습니다. 둘 다 가질 수는 없습니다.


returnC에서도 명령문을 생략 할 수 있습니다 (C99 이후).
dreamlax

C에서는 전화를 걸어 main()주소를받을 수 있습니다 . C ++는 C가 적용하지 않는 제한을 적용합니다.
Jonathan Leffler 2013 년

@JonathanLeffler : 당신이 옳습니다. 반환 값을 생략 할 수있는 가능성 외에도 C99 사양에서 발견 한 main에 대한 유일한 재미있는 점은 표준이 IIUC라는 단어로되어 있으므로 반복 할 argc때 음수 값을 전달할 수 없다는 것입니다 (5.1.2.2.1은 제한 사항을 지정하지 않습니다). argc)에 argv대한 최초 통화에만 적용됩니다 main.
6502

4

특이한 점은 여러 가지 방법으로 정의 main할 수 있다는 것이 아니라 두 가지 다른 방법 중 하나로 정의 할 수 있다는 것 입니다.

main사용자 정의 함수입니다. 구현은 프로토 타입을 선언하지 않습니다.

foo또는에 대해서도 마찬가지 bar이지만 원하는 방식으로 해당 이름으로 함수를 정의 할 수 있습니다.

차이점은 main자체 코드가 아니라 구현 (런타임 환경)에 의해 호출 된다는 것 입니다. 구현은 일반적인 C 함수 호출 의미 체계에 국한되지 않으므로 몇 가지 변형을 처리 할 수 ​​있습니다 (그리고 반드시 처리해야합니다).하지만 무한히 많은 가능성을 처리 할 필요는 없습니다. 이 int main(int argc, char *argv[])양식은 명령 줄 인수를 허용 int main(void)하며 C 또는int main() C ++에서는 명령 줄 인수를 처리 할 필요가없는 간단한 프로그램에 편리합니다.

컴파일러가이를 처리하는 방법은 구현에 따라 다릅니다. 대부분의 시스템에는 두 형식이 효과적으로 호환되도록하는 호출 규칙이있을 수 있으며 main매개 변수없이 정의 된에 전달 된 인수 는 조용히 무시됩니다. 그렇지 않다면 컴파일러 나 링커가 main특별히 처리하는 것이 어렵지 않을 것 입니다. 시스템 에서 어떻게 작동하는지 궁금하다면 몇 가지 어셈블리 목록을 볼 수 있습니다.

그리고 C와 C ++의 많은 것들과 마찬가지로 세부 사항은 대부분 언어 디자이너와 그 전임자들이 내린 역사와 임의의 결정의 결과입니다.

C와 C ++는 모두 다른 구현 정의 정의를 허용 main하지만이를 사용하는 좋은 이유는 거의 없습니다. 그리고 독립 구현 (예 : OS가없는 임베디드 시스템)의 경우 프로그램 진입 점은 구현에 따라 정의되며 반드시 main.


3

main링커에 의해 결정 시작 주소 단지 이름 main기본 이름입니다. 프로그램의 모든 함수 이름은 함수가 시작되는 시작 주소입니다.

함수 인수는 스택에서 푸시 / 팝핑되므로 함수에 지정된 인수가없는 경우 스택에서 푸시 / 팝핑되는 인수가 없습니다. 이것이 main이 인수를 사용하거나 사용하지 않고 작동하는 방법입니다.


2

음, 동일한 함수 main ()의 두 가지 다른 서명은 원할 때만 그림으로 표시됩니다. 따라서 프로그램이 실제 코드를 처리하기 전에 데이터가 필요한 경우 다음을 사용하여 전달할 수 있습니다.

    int main(int argc, char * argv[])
    {
       //code
    }

여기서 변수 argc는 전달 된 데이터의 개수를 저장하고 argv는 콘솔에서 전달 된 값을 가리키는 char에 대한 포인터 배열입니다. 그렇지 않으면 항상 함께가는 것이 좋습니다

    int main()
    {
       //Code
    }

그러나 어떤 경우에도 프로그램에는 하나의 main () 만있을 수 있습니다. 프로그램에서 실행을 시작하는 유일한 지점이므로 둘 이상일 수 없습니다. (그 가치가 있기를 바랍니다)


2

이전에도 비슷한 질문이있었습니다. 매개 변수가없는 함수 (실제 함수 정의와 비교하여)가 컴파일되는 이유는 무엇입니까?

가장 높은 답변 중 하나는 다음과 같습니다.

C에서 func()당신이 통과 할 수 있음을 의미 있는 인수의 수입니다. 인수를 원하지 않으면 다음과 같이 선언해야합니다.func(void)

그래서 나는 그것이 main선언 된 방법이라고 생각한다 ( "declared"라는 용어를에 적용 할 수 있다면 main). 실제로 다음과 같이 작성할 수 있습니다.

int main(int only_one_argument) {
    // code
}

여전히 컴파일되고 실행됩니다.


1
훌륭한 관찰! 링커가 매우에 대한 용서 표시 main도 : 아직 언급되지 않은 문제가있는 한, 의 인수를 main! "Unix (Posix.1은 아님) 및 Microsoft Windows"추가 char **envp(DOS도 허용 했습니까?), Mac OS X 및 Darwin은 또 다른 "임의 OS 제공 정보"char * 포인터를 추가합니다. wikipedia
usr2564301 2013-10-25

0

한 번에 하나만 사용되므로이를 재정의 할 필요가 없습니다.

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