程序员自我修养-是否真的要main函数??

本文详细解析了C语言程序的启动过程,从操作系统创建进程到main函数的调用,再到程序结束时的清理工作。同时介绍了运行库的作用及初始化过程,并解释了为何必须定义main函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先c语言程序是否真的需要一个main函数?

double foo()
{
	return 1.0;
}

double g=foo();
int main()
{
	return0;
}

在main函数之前,程序启动器帮我们初始化好了g这个全局变量。

void foo()
{
	printf("foo\n");
}

int main()
{
	atexit(&foo);
	printf("end\n");
}

这个会打印:
	end
	foo

程序结束后运行库是不是有动了什么手脚?

还有如果在c语言中不定义一个main,就会出错说找不到main函数,为什么?那能不能换成别的名字?

一个典型的程序的运行步骤:

1.      操作系统在创建进程后,把控制权交给程序的入口,这个入口是运行库中的某一个函数

2.      入口函数对运行库和程序运行的环境进行初始化,堆,I/O,线程,全局变量的构造

3.      入口函数完成后在调用main函数,正式开始主体程序 //到这里才是真的main函数的开始,main函数也是被人家调用的,为什么程序中要有一个main函数,下面就知道了

4.      等main函数完成后回到入口函数,进行清理工作,包括全局变量的结构,堆的销毁,关闭I/O等等,然后结束进程

 

Glib静态用于可执行文件:

         首先注意:参数和环境变量是由连接器压入栈中的,这个在_start前就已经完成了。

入口函数的实现,为什么要写一个main函数???

真正的入口函数,在_start中:

         伪代码:

void _start()
{
	%ebp=0;						xor %ebp %ebp

	int argc=pop from stack		popl %si
	
	char** argc=top of stack	movl %esp,%ecx
	
	__libc_start_main(main,argc,argv,__libc_csu_init,__libc_csu_fini,edx,top of stack);
//这里会调用main函数

}

_start:
xor    %ebp,%ebp
pop    %esi
mov    %esp,%ecx
and    $0xfffffff0,%esp
push   %eax
push   %esp
push   %edx
push   $__libc_csu_fini
push   $__libc_csu_init
push   %ecx
push   %esi
push   $main
call  __libc_start_main

_start后的栈的结构:
Stack Top     -------------------
                        main		                               main
                     ------------------- 
                        esi                                            argc
                     ------------------- 
                        ecx                                           argv 
                    ------------------- 
                       __libc_csu_init 				  _init
                     ------------------- 
                       __libc_csu_init                             _fini
                     ------------------- 
                        edx     //这个是动态加载的收尾  _rtlf_fini
                     ------------------- 
                        esp                                         stack_end
                     ------------------- 
                        eax                                         this is 0

__libc_start_main的原型:
extern int BP_SYM (__libc_start_main) (int (*main) (int, char **, char **),
		int argc,
		char *__unbounded *__unbounded ubp_av,
		void (*init) (void),
		void (*fini) (void),
		void (*rtld_fini) (void),
		void *__unbounded stack_end)
__attribute__ ((noreturn));

bounded指针:	
	占用3个指针的空间,第一个空间保存原指针的值,第二个存储下界值,第三个保存上界值

MSVS中的crt入口函数:   

         主要工作:堆的初始化,I/O的初始化

MSVC 中的IO初始化主要工作:

1.      建立打开列表

2.      如果能够继承,从父进程中继承

3.      初始化标准的输入输出



下面是源代码;
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]; //把ubp_ev指向环境变量
  INIT_ARGV_and_ENVIRON; //因为有不同版本的bounded,所以定义了这个宏
  /* 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

  /* 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 ();
# 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);  //注册main执行完后的清空代码

  /* 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);
}


、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
int __tmainCRTStartup(void)
{
        int initret;
        int mainret=0;
        int managedapp;
#ifdef _WINMAIN_
        _TUCHAR *lpszCommandLine;
        STARTUPINFOW StartupInfo;

        GetStartupInfoW( &StartupInfo ); //启动取得信息
#endif  /* _WINMAIN_ */

#ifdef _M_IX86
        /*
         * Enable app termination when heap corruption is detected on
         * Windows Vista and above. This is a no-op on down-level OS's
         * and enabled by default for 64-bit processes.
         */

        if (!_NoHeapEnableTerminationOnCorruption)
        {
            HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
        }
#endif  /* _M_IX86 */

        /*
         * Determine if this is a managed application
         */
        managedapp = check_managed_app();

        if ( !_heap_init() )                /* initialize heap */ //初始化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 初始化IO*/ 
                _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 c语言库的设置*/
            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); //这个才是最后调用的man函数
#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;
}



int __cdecl _heap_init (void)
{
        ULONG HeapType = 2;

        //  Initialize the "big-block" heap first.
        if ( (_crtheap = HeapCreate(0, BYTES_PER_PAGE, 0)) == NULL )
            return 0;

#ifdef _WIN64
        // Enable the Low Fragmentation Heap by default on Windows XP and
        // Windows Server 2003.  It's the 8 byte overhead heap, and has
        // generally better performance charateristics than standard heap,
        // particularly for apps that perform lots of small allocations.

        if (LOBYTE(GetVersion()) < 6)
        {
            HeapSetInformation(_crtheap, HeapCompatibilityInformation,
                               &HeapType, sizeof(HeapType));
        }
#endif  /* _WIN64 */
        return 1;
}




int __cdecl _ioinit (void)
{
        STARTUPINFOW StartupInfo;
        int cfi_len;
        int fh;
        int i;
        ioinfo *pio;
        char *posfile;
        UNALIGNED intptr_t *posfhnd;
        intptr_t stdfh;
        DWORD htype;

        GetStartupInfoW( &StartupInfo ); //得到父亲的文件句柄

        /*
         * Allocate and initialize the first array of ioinfo structs. This
         * array is pointed to by __pioinfo[0]
         */
        if ( (pio = _calloc_crt( IOINFO_ARRAY_ELTS, sizeof(ioinfo) ))
             == NULL )
        {
            return -1;
        }

        __pioinfo[0] = pio;
        _nhandle = IOINFO_ARRAY_ELTS;

        for ( ; pio < __pioinfo[0] + IOINFO_ARRAY_ELTS ; pio++ ) { //进行初始化
            pio->osfile = 0;
            pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
            pio->pipech = 10;                   /* linefeed/newline char */
            pio->lockinitflag = 0;              /* uninitialized lock */
            pio->textmode = 0;
            pio->unicode = 0;
            pio->pipech2[0] = 10;
            pio->pipech2[1] = 10;
            pio->dbcsBufferUsed = FALSE;
            pio->dbcsBuffer = '\0';
        }

        /*
         * Process inherited file handle information, if any
         */

        if ( (StartupInfo.cbReserved2 != 0) &&
             (StartupInfo.lpReserved2 != NULL) ) //如果从父亲那边得到了文件句柄
        {
            /*
             * Get the number of handles inherited.
             */
            cfi_len = *(UNALIGNED int *)(StartupInfo.lpReserved2);

            /*
             * Set pointers to the start of the passed file info and OS
             * HANDLE values.
             */
            posfile = (char *)(StartupInfo.lpReserved2) + sizeof( int ); //两个指针将划分成,n:表示数量,句柄的属性,句柄数组,见最后图
            posfhnd = (UNALIGNED intptr_t *)(posfile + cfi_len);

            /*
             * Ensure cfi_len does not exceed the number of supported
             * handles!
             */
            cfi_len = __min( cfi_len, _NHANDLE_ );

            /*
             * Allocate sufficient arrays of ioinfo structs to hold inherited
             * file information.
             */
            for ( i = 1 ; _nhandle < cfi_len ; i++ ) { //分配空间

                /*
                 * Allocate another array of ioinfo structs
                 */
                if ( (pio = _calloc_crt( IOINFO_ARRAY_ELTS, sizeof(ioinfo) ))
                    == NULL )
                {
                    /*
                     * No room for another array of ioinfo structs, reduce
                     * the number of inherited handles we process.
                     */
                    cfi_len = _nhandle;
                    break;
                }

                /*
                 * Update __pioinfo[] and _nhandle
                 */
                __pioinfo[i] = pio;
                _nhandle += IOINFO_ARRAY_ELTS;

                for ( ; pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ; pio++ ) {
                    pio->osfile = 0;
                    pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
                    pio->pipech = 10;
                    pio->lockinitflag = 0;
                    pio->textmode = 0;
                    pio->pipech2[0] = 10;
                    pio->pipech2[1] = 10;
                    pio->dbcsBufferUsed = FALSE;
                    pio->dbcsBuffer = '\0';
                }
            }

            /*
             * Validate and copy the passed file information
             */
            for ( fh = 0 ; fh < cfi_len ; fh++, posfile++, posfhnd++ ) {//copy文件句柄到现在的进程中
                /*
                 * Copy the passed file info iff it appears to describe
                 * an open, valid file or device.
                 *
                 * Note that GetFileType cannot be called for pipe handles
                 * since it may 'hang' if there is blocked read pending on
                 * the pipe in the parent.
                 */
                if ( (*posfhnd != (intptr_t)INVALID_HANDLE_VALUE) &&
                     (*posfhnd != _NO_CONSOLE_FILENO) &&
                     (*posfile & FOPEN) &&
                     ((*posfile & FPIPE) ||
                      (GetFileType( (HANDLE)*posfhnd ) != FILE_TYPE_UNKNOWN)) )
                {
                    pio = _pioinfo( fh );
                    pio->osfhnd = *posfhnd;
                    pio->osfile = *posfile;
                    /* Allocate the lock for this handle. */
                    if ( !InitializeCriticalSectionAndSpinCount( &pio->lock,
                                                                 _CRT_SPINCOUNT ))
                        return -1;
                    pio->lockinitflag++;
                }
            }
        }

        /*
         * If valid HANDLE-s for standard input, output and error were not
         * inherited, try to obtain them directly from the OS. Also, set the
         * appropriate bits in the osfile fields.
         */
        for ( fh = 0 ; fh < 3 ; fh++ ) {//初始化标准输入输出,0,1,2

            pio = __pioinfo[0] + fh;

            if ( (pio->osfhnd == (intptr_t)INVALID_HANDLE_VALUE) ||
                 (pio->osfhnd == _NO_CONSOLE_FILENO)) {
                /*
                 * mark the handle as open in text mode.
                 */
                pio->osfile = (char)(FOPEN | FTEXT);

                if ( ((stdfh = (intptr_t)GetStdHandle( stdhndl(fh) )) != (intptr_t)INVALID_HANDLE_VALUE) &&
                     (stdfh!=((intptr_t)NULL)) &&
                     ((htype = GetFileType( (HANDLE)stdfh )) != FILE_TYPE_UNKNOWN) )
                {
                    /*
                     * obtained a valid HANDLE from GetStdHandle
                     */
                    pio->osfhnd = stdfh;

                    /*
                     * finish setting osfile: determine if it is a character
                     * device or pipe.
                     */
                    if ( (htype & 0xFF) == FILE_TYPE_CHAR )
                        pio->osfile |= FDEV;
                    else if ( (htype & 0xFF) == FILE_TYPE_PIPE )
                        pio->osfile |= FPIPE;

                    /* Allocate the lock for this handle. */
                    if ( !InitializeCriticalSectionAndSpinCount( &pio->lock,
                                                                 _CRT_SPINCOUNT ))
                        return -1;
                    pio->lockinitflag++;
                }
                else {
                    /*
                     * For stdin, stdout & stderr, if there is no valid HANDLE,
                     * treat the CRT handle as being open in text mode on a
                     * device with _NO_CONSOLE_FILENO underlying it. We use this
                     * value different from _INVALID_HANDLE_VALUE to distinguish
                     * between a failure in opening a file & a program run
                     * without a console.
                     */
                    pio->osfile |= FDEV;
                    pio->osfhnd = _NO_CONSOLE_FILENO;
                }
            }
            else  {
                /*
                 * handle was passed to us by parent process. make
                 * sure it is text mode.
                 */
                pio->osfile |= FTEXT;
            }
        }

        /*
         * Set the number of supported HANDLE-s to _nhandle
         */
        (void)SetHandleCount( (unsigned)_nhandle );

        return 0;
}

 总结;

运行库:

1.      启动退出

2.      标准函数:由c语言标准规定的标准

3.      I/O

4.      堆:初始化,实现封装

5.      语言功能

6.      调试

注意:

1.      不要在不同的CRT中一个申请内存一个释放,因为不能的CRT有不同的堆

2.      不同的CRT中不要在A中打开的file,在B中使用

3.      如果用vs2005等编译的程序在别人电脑上不能使用是因为使用了manifest机制,可以直接使用静态编译,或者和crt一起发布

4.      尽量在同一个crt下开发程序,保存一致性


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值