如何编写不带main()函数的C程序来打印“ Hello world”?
首先,在没有main()函数的情况下执行程序似乎是不切实际的,因为main()函数是任何程序的入口点。
首先让我们了解在Linux系统中执行C程序时,幕后情况,如何调用main()以及如何在没有main()的情况下执行程序。
该演示将考虑以下设置。
- Ubuntu 16.4 LTS操作系统
- GCC 5.4.0编译器
- objdump实用程序
从C / C++编程角度看,程序入口点是main()函数。但是,从程序执行的角度来看,事实并非如此。在执行流程到达main()之前,将进行对其他几个函数的调用,这些函数会设置参数,为程序执行准备环境变量等。
编译C源代码后创建的可执行文件是可执行和可链接格式(ELF)文件。
每个ELF文件都有一个ELF头,其中有一个e_entry字段,该字段包含程序存储器地址,从该地址开始执行可执行程序。该内存地址指向_start()函数。
加载程序后,加载程序会从ELF文件头中查找e_entry字段。可执行和可链接格式(ELF)是UNIX系统中用于可执行文件,目标代码,共享库和核心转储的一种通用标准文件格式。
让我们来看一个例子。我正在创建一个example.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()函数启动之前执行初始化。
- 注册_fini()和_rtld_fini()函数以在程序终止后执行清理
- 完成所有先决条件操作后,_libc_start_main()调用main()函数。
编写没有main()的程序
现在我们知道如何对main()进行调用了。为了明确起见,main()只是启动代码的约定术语。对于启动代码,我们可以使用任何名称,而不必一定是“ main”。由于_start()函数默认调用main(),因此如果要执行我们的自定义启动代码,则必须对其进行更改。我们可以重写_start()函数,使其调用自定义启动代码而不是main()。让我们举个例子,将其另存为nomain.c –
#include
#include void _start() { int x = my_fun(); //calling custom main function exit(x); } int my_fun() // our custom main function { printf("Hello world!\n"); return 0; } 现在我们必须强制编译器不要使用它自己的_start()实现。在GCC中,我们可以使用-nostartfiles来实现
gcc -nostartfiles -o nomain nomain.c
执行可执行文件nomain
./nomain
输出:
Hello world!
参考
- http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
- Milan Stevanovic的高级C / C++编译
要从最佳影片策划和实践问题去学习,检查了C++基础课程为基础,以先进的C++和C++ STL课程基础加上STL。要完成从学习语言到DS Algo等的更多准备工作,请参阅“完整面试准备课程” 。