在C/C++中执行main()——幕后

如何在没有main()函数的情况下编写一个C程序来打印“Hello world”? 一开始,执行一个没有 main() 因为 main() 函数是任何程序的入口点。

null

首先,让我们了解在Linux系统中执行C程序时会发生什么,如何调用main(),以及如何在没有main()的情况下执行程序。

本演示将考虑以下设置。

  • Ubuntu 16.4 LTS操作系统
  • GCC 5.4.0编译器
  • objdump实用程序

从C/C++编程的角度来看,程序的入口点是main()函数。然而,从程序执行的角度来看,情况并非如此。在执行流到达main()之前,很少调用其他函数,这些函数用于设置参数、为程序执行准备环境变量等。

编译C源代码后创建的可执行文件是 可执行和可链接格式(ELF)文件 . 每个ELF文件都有一个ELF头,其中有一个 e_条目 包含程序内存地址的字段,可执行文件将从该地址开始执行。这个内存地址指向 _开始() 作用 加载程序后,loader会查找 e_条目 ELF文件头中的字段。 可执行和可链接格式(ELF) 是UNIX系统中用于可执行文件、目标代码、共享库和核心转储的通用标准文件格式。

让我们用一个例子来看一下。我在创造一个 实例C 文件来演示这一点。

int main()
{
return (0);
}


现在使用以下命令编译

gcc -o example example.c

现在 实例 创建可执行文件后,让我们使用objdump实用程序来检查这一点

objdump -f example

这将在我的机器上输出以下可执行文件的关键信息。看看下面的起始地址,这是指向_start()函数的地址。

example:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000004003e0

我们可以通过解汇编可执行文件来交叉检查这个地址,输出很长,所以我只是粘贴显示这个地址的输出 0x00000000004003e0 指的是

objdump --disassemble  example

输出:

00000000004003e0 <_start>:
  4003e0:    31 ed                    xor    %ebp,%ebp
  4003e2:    49 89 d1                 mov    %rdx,%r9
  4003e5:    5e                       pop    %rsi
  4003e6:    48 89 e2                 mov    %rsp,%rdx
  4003e9:    48 83 e4 f0              and    $0xfffffffffffffff0,%rsp
  4003ed:    50                       push   %rax
  4003ee:    54                       push   %rsp
  4003ef:    49 c7 c0 60 05 40 00     mov    $0x400560,%r8
  4003f6:    48 c7 c1 f0 04 40 00     mov    $0x4004f0,%rcx
  4003fd:    48 c7 c7 d6 04 40 00     mov    $0x4004d6,%rdi
  400404:    e8 b7 ff ff ff           callq  4003c0 
  400409:    f4                       hlt    
  40040a:    66 0f 1f 44 00 00        nopw   0x0(%rax,%rax,1)

我们可以清楚地看到,这是指向_start()函数的。

_start()函数的作用

函数_start()为另一个函数准备输入参数 _libc_start_main() 下一个将被称为。这是我的原型 _libc_start_main() 作用这里我们可以看到由_start()函数准备的参数。

int __libc_start_main( int (*main) ( int , char * *, char * *), /* address of main function*/
int argc, /* number of command line args*/
char ** ubp_av, /* command line arg array*/
void (*init) ( void ), /* address of init function*/
void (*fini) ( void ), /* address of fini function*/
void (*rtld_fini) ( void ), /* address of dynamic linker fini function */
void (* stack_end) /* end of the stack address*/
);


_libc_start_main()函数的作用

_libc_start_main()函数的作用如下——

  • 为程序执行准备环境变量
  • 电话 _init() 函数,它在main()函数启动之前执行初始化。
  • 登记 _菲尼() _rtld_fini() 函数在程序终止后执行清理
      完成所有先决条件操作后,_libc_start_main()调用main()函数。

      在没有main()的情况下编写程序

      现在我们知道如何调用main()。为了说明这一点,main()只不过是启动代码的一个约定术语。我们可以给启动代码起任何名字,它不一定是“main”。由于_start()函数在默认情况下调用main(),如果我们想执行自定义启动代码,就必须更改它。我们可以重写_start()函数,使其调用自定义启动代码not main()。让我们举个例子,另存为 诺曼。C

      #include<stdio.h>
      #include<stdlib.h>
      void _start()
      {
      int x = my_fun(); //calling custom main function
      exit (x);
      }
      int my_fun() // our custom main function
      {
      printf ( "Hello world!" );
      return 0;
      }

      
      

      现在我们必须强制编译器不要使用它自己的_start()实现。在GCC中,我们可以使用 -nostartfiles

    gcc -nostartfiles -o nomain nomain.c
    

    执行nomain中的可执行文件

    ./nomain
    

    输出:

    Hello world!
    

    工具书类

    本文由 阿图尔·库马尔 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以使用 贡献极客。组织 或者把你的文章寄到contribute@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。

    如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享