本帖最后由 crifan 于 2011-10-13 16:34 编辑
Main函数之前
Version: 2011-10-13
Author: green-waste (at)163.com
【声明】
此文主要是摘录和整理《程序员修养》那本书中关于运行库部分相关的内容,为了简要的总结一下main函数之前都干了啥。 【正文】
多数人接触C语言的人,对main函数,应该都比较了解了。不少人,应该是,或许以为main就是程序执行的入口,或许知道main函数不是入口,但是不知道哪里是入口,又或许知道入口,但是对于main之前发生的事情,系统做了哪些事情,不是很清楚。 那么,下面这段文字,目的就是简单介绍一下,main之前到底发生了什么。 在main函数中,一般来说,你可以放心去使用很多资源了,比如:申请内存,使用系统调用,触发异常,访问I/O,使用scan/printf等输入输出等等。 之所以你可以放心使用这些资源,那么是因为有人帮你在main函数执行之前,就帮你准备好了这些东西供你使用。 到底是谁做的?答案是,运行库。不过在介绍运行库之前,先要清楚一般程序执行的过程是怎么样的。 【程序运行步骤】 一个典型的程序运行步骤大致如下: (1)操作系统在创建进程后,把控制权交到了程序的入口,这个入口往往是运行库的某个入口函数。 (2)入口函数对运行库和程序运行环境进行初始化,包括堆Heap,I/O,线程。全局变量构造等等。 (3)入口函数在完成初始化之后,调用main函数,正式执行程序主体部分。 (4)Main函数执行完毕之后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程。 【入口函数】
从上面可以看出,其实真正所谓的程序开始部分,其实可以算是入口函数。 入口函数,也叫入口点(Entry Point),是运行库的一部分,视平台不同而有不同名字。 下表显示了不同平台下,对应的入口函数的相关内容: 表格 1 不同平台下的入口函数相关信息 平台 | 运行库 | 入口函数的名称 | 说明 | Linux | Glibc.c | __libc_start_main | 详情参考glibc源码中libc/csu位置的源码 | Windows | MSVC CRT的Crt0.c | mainCRTStartup | 以Microsoft Visual Studio 2003为例,代码位于VC安装目录crt/src中 |
【__libc_start_main和mainCRTStartup】
1. __libc_start_main Glib的库源码 http://ftp.gnu.org/gnu/libc/glibc-2.14.tar.bz2 中找到的文件是:csu\Libc-start.c 对应函数代码为: - STATIC int
- LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
- int argc, char *__unbounded *__unbounded ubp_av,
- #ifdef LIBC_START_MAIN_AUXVEC_ARG
- ElfW(auxv_t) *__unbounded auxvec,
- #endif
- __typeof (main) init,
- void (*fini) (void),
- void (*rtld_fini) (void), void *__unbounded stack_end)
- {
- #if __BOUNDED_POINTERS__
- char **argv;
- #else
- # define argv ubp_av
- #endif
- /* Result of the 'main' function. */
- int result;
- __libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up;
- #ifndef SHARED
- char *__unbounded *__unbounded ubp_ev = &ubp_av[argc + 1];
- INIT_ARGV_and_ENVIRON;
- /* Store the lowest stack address. This is done in ld.so if this is
- the code for the DSO. */
- __libc_stack_end = stack_end;
- # ifdef HAVE_AUX_VECTOR
- /* First process the auxiliary vector since we need to find the
- program header to locate an eventually present PT_TLS entry. */
- # ifndef LIBC_START_MAIN_AUXVEC_ARG
- ElfW(auxv_t) *__unbounded auxvec;
- {
- char *__unbounded *__unbounded evp = ubp_ev;
- while (*evp++ != NULL)
- ;
- auxvec = (ElfW(auxv_t) *__unbounded) evp;
- }
- # endif
- _dl_aux_init (auxvec);
- # endif
- # ifdef DL_SYSDEP_OSCHECK
- if (!__libc_multiple_libcs)
- {
- /* This needs to run to initiliaze _dl_osversion before TLS
- setup might check it. */
- DL_SYSDEP_OSCHECK (__libc_fatal);
- }
- # endif
- /* Performe IREL{,A} relocations. */
- __libc_csu_irel ();
- /* Initialize the thread library at least a bit since the libgcc
- functions are using thread functions if these are available and
- we need to setup errno. */
- __pthread_initialize_minimal ();
- /* Set up the stack checker's canary. */
- uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
- # ifdef THREAD_SET_STACK_GUARD
- THREAD_SET_STACK_GUARD (stack_chk_guard);
- # else
- __stack_chk_guard = stack_chk_guard;
- # endif
- #endif
- /* Register the destructor of the dynamic linker if there is any. */
- if (__builtin_expect (rtld_fini != NULL, 1))
- __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);
- #ifndef SHARED
- /* Call the initializer of the libc. This is only needed here if we
- are compiling for the static library in which case we haven't
- run the constructors in `_dl_start_user'. */
- __libc_init_first (argc, argv, __environ);
- /* Register the destructor of the program, if any. */
- if (fini)
- __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);
- /* Some security at this point. Prevent starting a SUID binary where
- the standard file descriptors are not opened. We have to do this
- only for statically linked applications since otherwise the dynamic
- loader did the work already. */
- if (__builtin_expect (__libc_enable_secure, 0))
- __libc_check_standard_fds ();
- #endif
- /* Call the initializer of the program, if any. */
- #ifdef SHARED
- if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
- GLRO(dl_debug_printf) ("\ninitialize program: %s\n\n", argv[0]);
- #endif
- if (init)
- (*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);
- #ifdef SHARED
- /* Auditing checkpoint: we have a new object. */
- if (__builtin_expect (GLRO(dl_naudit) > 0, 0))
- {
- struct audit_ifaces *afct = GLRO(dl_audit);
- struct link_map *head = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
- for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
- {
- if (afct->preinit != NULL)
- afct->preinit (&head->l_audit[cnt].cookie);
- afct = afct->next;
- }
- }
- #endif
- #ifdef SHARED
- if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
- GLRO(dl_debug_printf) ("\ntransferring control: %s\n\n", argv[0]);
- #endif
- #ifdef HAVE_CLEANUP_JMP_BUF
- /* Memory for the cancellation buffer. */
- struct pthread_unwind_buf unwind_buf;
- int not_first_call;
- not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
- if (__builtin_expect (! not_first_call, 1))
- {
- 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. */
- # ifdef SHARED
- PTHFCT_CALL (ptr__nptl_deallocate_tsd, ());
- # else
- extern void __nptl_deallocate_tsd (void) __attribute ((weak));
- __nptl_deallocate_tsd ();
- # endif
- /* One less thread. Decrement the counter. If it is zero we
- terminate the entire process. */
- result = 0;
- # ifdef SHARED
- unsigned int *ptr = __libc_pthread_functions.ptr_nthreads;
- PTR_DEMANGLE (ptr);
- # else
- extern unsigned int __nptl_nthreads __attribute ((weak));
- unsigned int *const ptr = &__nptl_nthreads;
- # endif
- if (! atomic_decrement_and_test (ptr))
- /* Not much left to do but to exit the thread, not the process. */
- __exit_thread (0);
- }
- #else
- /* Nothing fancy, just call the function. */
- result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
- #endif
- exit (result);
- }
复制代码
2.mainCRTStartup 此处安装的VS2005,对应的文件为: C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\crt0.c mainCRTStartup函数代码为: - __declspec(noinline)
- int
- __tmainCRTStartup(
- void
- )
- {
- unsigned int osplatform = 0;
- unsigned int winver = 0;
- unsigned int winmajor = 0;
- unsigned int winminor = 0;
- unsigned int osver = 0;
- int initret;
- int mainret=0;
- OSVERSIONINFOA *posvi;
- int managedapp;
- #ifdef _WINMAIN_
- _TUCHAR *lpszCommandLine;
- STARTUPINFO StartupInfo;
- __try {
- /*
- Note: MSDN specifically notes that GetStartupInfo returns no error, and throws unspecified SEH if it fails, so
- the very general exception handler below is appropriate
- */
- GetStartupInfo( &StartupInfo );
- } __except(EXCEPTION_EXECUTE_HANDLER) {
- return 255;
- }
- #endif /* _WINMAIN_ */
- /*
- * Dynamically allocate the OSVERSIONINFOA buffer, so we avoid
- * triggering the /GS buffer overrun detection. That can't be
- * used here, since the guard cookie isn't available until we
- * initialize it from here!
- */
- posvi = (OSVERSIONINFOA *)HeapAlloc(GetProcessHeap(), 0, sizeof(OSVERSIONINFOA));
- if (!posvi) {
- fast_error_exit(_RT_HEAP);
- return 255;
- }
- /*
- * Get the full Win32 version
- */
- posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
- if ( !GetVersionExA(posvi) ) {
- HeapFree(GetProcessHeap(), 0, posvi);
- return 255;
- }
- osplatform = posvi->dwPlatformId;
- winmajor = posvi->dwMajorVersion;
- winminor = posvi->dwMinorVersion;
- /*
- * The somewhat bizarre calculations of _osver and _winver are
- * required for backward compatibility (used to use GetVersion)
- */
- osver = (posvi->dwBuildNumber) & 0x07fff;
- HeapFree(GetProcessHeap(), 0, posvi);
- if ( osplatform != VER_PLATFORM_WIN32_NT )
- osver |= 0x08000;
- winver = (winmajor << 8) + winminor;
- _set_osplatform(osplatform);
- _set_winver(winver);
- _set_winmajor(winmajor);
- _set_winminor(winminor);
- _set_osver(osver);
- /*
- * Determine if this is a managed application
- */
- managedapp = check_managed_app();
- if ( !_heap_init(1) ) /* initialize heap */
- fast_error_exit(_RT_HEAPINIT); /* write message and die */
- if( !_mtinit() ) /* initialize multi-thread */
- fast_error_exit(_RT_THREAD); /* write message and die */
- /* Enable buffer count checking if linking against static lib */
- _CrtSetCheckCount(TRUE);
- /*
- * Initialize the Runtime Checks stuff
- */
- #ifdef _RTC
- _RTC_Initialize();
- #endif /* _RTC */
- /*
- * Guard the remainder of the initialization code and the call
- * to user's main, or WinMain, function in a __try/__except
- * statement.
- */
- __try {
- if ( _ioinit() < 0 ) /* initialize lowio */
- _amsg_exit(_RT_LOWIOINIT);
- /* get wide cmd line info */
- _tcmdln = (_TSCHAR *)GetCommandLineT();
- /* get wide environ info */
- _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
- if ( _tsetargv() < 0 )
- _amsg_exit(_RT_SPACEARG);
- if ( _tsetenvp() < 0 )
- _amsg_exit(_RT_SPACEENV);
- initret = _cinit(TRUE); /* do C data initialize */
- if (initret != 0)
- _amsg_exit(initret);
- #ifdef _WINMAIN_
- lpszCommandLine = _twincmdln();
- mainret = _tWinMain( (HINSTANCE)&__ImageBase,
- NULL,
- lpszCommandLine,
- StartupInfo.dwFlags & STARTF_USESHOWWINDOW
- ? StartupInfo.wShowWindow
- : SW_SHOWDEFAULT
- );
- #else /* _WINMAIN_ */
- _tinitenv = _tenviron;
- mainret = _tmain(__argc, _targv, _tenviron);
- #endif /* _WINMAIN_ */
- if ( !managedapp )
- exit(mainret);
- _cexit();
- }
- __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
- {
- /*
- * Should never reach here
- */
- mainret = GetExceptionCode();
- if ( !managedapp )
- _exit(mainret);
- _c_exit();
- } /* end of try - except */
- return mainret;
- }
复制代码
【运行库】
接下来,再简单解释解释运行库。 运行库,即运行时库(Runtime Library),而C语言的运行库,即被称为C运行库(CRT,C Runtime Language)。 最初看到这个缩写,我以为是CRT显示器呢,呵呵。 一个C语言运行库大致包含了如下功能: (1)启动与退出:包括入口函数及入口函数所依赖的其他函数等。 (2)标准函数:由C语言标准规定的C语言标准库所拥有的函数实现。 (3)I/O:I/O功能的封装和实现。 (4)堆:堆的封装和实现。 (5)语言实现:语言中一些特殊的功能。 (6)调试:实现调试功能的代码。 关于每一部分的详细内容,就不多说了。 【引用】
1.《程序员的自我修养 - 链接、装载与库》 俞甲子 石凡 潘爱民 著 |