> 백엔드 개발 > C++ > return과 exit()가 모두 main()에서 작동하는 이유

return과 exit()가 모두 main()에서 작동하는 이유

DDD
풀어 주다: 2024-11-08 09:37:02
원래의
1005명이 탐색했습니다.

Why Both return and exit() Work in main()

소개

C 프로그래밍에서는 메인 함수에서 프로그램을 종료하는 방법이 두 가지 있는데, return을 사용하는 것과 exit()를 사용하는 것입니다.

int main() {
    printf("Hello, World!");
    return 0;    // Method 1: Normal termination
}

int main() {
    printf("Hello, World!");
    exit(0);     // Method 2:Normal termination
}
로그인 후 복사

두 가지 방법이 완전히 다르게 보이지만 프로그램을 올바르게 종료할 수 있는 이유는 무엇입니까?
이 기사에서는 C 프로그램이 실제로 어떻게 시작하고 종료하는지 이해하여 이 미스터리를 풀어보겠습니다.
이 기사는 특히 glibc를 사용한 GNU/Linux 환경에서의 구현에 중점을 두고 있습니다.

Exit() 작동 방식

먼저 프로그램 종료 메커니즘을 이해하기 위해 종료 기능이 어떻게 작동하는지 살펴보겠습니다.
종료 함수는 프로그램을 적절하게 종료시키는 표준 라이브러리 함수입니다.
내부적으로는 종료에 의해 호출되는 _exit 함수가 glibc에서 다음과 같이 구현됩니다.

void
_exit (int status)
{
  while (1)
    {
      INLINE_SYSCALL (exit_group, 1, status);

#ifdef ABORT_INSTRUCTION
      ABORT_INSTRUCTION;
#endif
    }
}
로그인 후 복사

이 구현을 보면 _exit 함수가 종료 상태를 인수로 받고 exit_group(시스템 호출 번호 231)을 호출하는 것을 볼 수 있습니다.

이 시스템 호출은 다음 작업을 수행합니다.

  1. 커널에 프로그램 종료 알림을 보냅니다
  2. 커널은 정리 작업을 수행합니다.
    • 프로세스에 사용된 리소스를 해제합니다
    • 프로세스 테이블 업데이트
    • 추가 정리 절차 수행

이러한 작업을 통해 프로그램이 정상적으로 종료됩니다.

그러면 왜 main()에서 복귀해도 프로그램이 제대로 종료되나요?

C 프로그램의 숨겨진 진입점

이를 이해하려면 중요한 사실을 알아야 합니다. C 프로그램은 실제로 메인에서 시작되지 않습니다.

실제 진입점을 보려면 링커(ld)의 기본 설정을 확인해 보겠습니다.

$ ld --verbose | grep "ENTRY"
ENTRY(_start)
로그인 후 복사

이 출력에서 ​​볼 수 있듯이 C 프로그램의 실제 진입점은 _start 함수입니다. main은 _start 다음에 호출됩니다.
_start 함수는 표준 라이브러리에서 구현되며 glibc에서는 다음과 같습니다.

_start:
    # Initialize stack pointer
    xorl %ebp, %ebp
    popq %rsi        # Get argc
    movq %rsp, %rdx  # Get argv

    # Setup arguments for main
    pushq %rsi       # Push argc
    pushq %rdx       # Push argv

    # Call __libc_start_main
    call __libc_start_main
로그인 후 복사

_start 함수에는 두 가지 주요 역할이 있습니다.

  1. 프로그램 실행에 필요한 스택 프레임을 초기화합니다
  2. 주 함수에 대한 명령줄 인수(argc, argv) 설정

이러한 초기화가 완료되면 __libc_start_main이 호출됩니다.
이 함수는 메인 함수 호출을 담당합니다.

이제 __libc_start_main이 어떻게 작동하는지 자세히 살펴보겠습니다.

__libc_start_main이 반환 작업을 수행하는 방법

__libc_start_main에 의해 호출되는 __libc_start_call_main은 다음과 같이 구현됩니다.

_Noreturn static void
__libc_start_call_main (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
                        int argc, char **argv
#ifdef LIBC_START_MAIN_AUXVEC_ARG
                            , ElfW(auxv_t) *auxvec
#endif
                        )
{
  int result;

  /* Memory for the cancellation buffer.  */
  struct pthread_unwind_buf unwind_buf;

  int not_first_call;
  DIAG_PUSH_NEEDS_COMMENT;
#if __GNUC_PREREQ (7, 0)
  /* This call results in a -Wstringop-overflow warning because struct
     pthread_unwind_buf is smaller than jmp_buf.  setjmp and longjmp
     do not use anything beyond the common prefix (they never access
     the saved signal mask), so that is a false positive.  */
  DIAG_IGNORE_NEEDS_COMMENT (11, "-Wstringop-overflow=");
#endif
  not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
  DIAG_POP_NEEDS_COMMENT;
  if (__glibc_likely (! not_first_call))
    {
      struct pthread *self = THREAD_SELF;

      /* Store old info.  */
      unwind_buf.priv.data.prev = THREAD_GETMEM (self, cleanup_jmp_buf);
      unwind_buf.priv.data.cleanup = THREAD_GETMEM (self, cleanup);

      /* Store the new cleanup handler info.  */
      THREAD_SETMEM (self, cleanup_jmp_buf, &unwind_buf);

      /* Run the program.  */
      result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
    }
  else
    {
      /* Remove the thread-local data.  */
      __nptl_deallocate_tsd ();

      /* One less thread.  Decrement the counter.  If it is zero we
         terminate the entire process.  */
      result = 0;
      if (atomic_fetch_add_relaxed (&__nptl_nthreads, -1) != 1)
        /* Not much left to do but to exit the thread, not the process.  */
    while (1)
      INTERNAL_SYSCALL_CALL (exit, 0);
    }

  exit (result);
}
로그인 후 복사

이번 구현에서 중점적으로 살펴볼 부분은 다음과 같습니다.

result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
exit(result);
로그인 후 복사

여기서 중요한 점은 main 함수가 어떻게 실행되고 반환 값이 처리되는지입니다.

  1. 메인 함수를 실행하고 그 반환 값을 결과에 저장합니다
  2. main의 반환 값을 종료 인수로 사용합니다

이 메커니즘을 통해:

  • main에서 return을 사용하는 경우 → 반환 값은 __libc_start_main에 전달된 다음 이를 종료에 전달됩니다
  • 메인에서 직접 exit()를 호출한 경우 → 프로그램이 즉시 종료됩니다

두 경우 모두 궁극적으로 종료가 호출되어 프로그램이 올바르게 종료되도록 합니다.

결론

C 프로그램에는 다음과 같은 메커니즘이 있습니다.

  1. 프로그램은 _start부터 시작됩니다
  2. _start가 메인 실행을 준비합니다
  3. main은 __libc_start_main을 통해 실행됩니다
  4. main의 반환 값을 받아 종료 인수로 사용합니다

이 메커니즘을 통해:

  • main에서 return을 사용해도 return 값은 자동으로 exit로 전달됩니다
  • 결과적으로 return과 exit() 모두 프로그램을 올바르게 종료합니다

이 메커니즘은 GNU/Linux에만 국한되지 않습니다. 다른 운영 체제(예: Windows 및 macOS)와 다른 C 표준 라이브러리에도 유사한 구현이 존재합니다.

위 내용은 return과 exit()가 모두 main()에서 작동하는 이유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿