菜鸟笔记
提升您的技术认知

没有了 main 函数,程序还能跑吗?

刑天

刑天,是中国远古神话传说人物,手使一柄巨斧和盾牌,身强力壮,体型巨大的上古巨人,炎帝手下大将,和黄帝争位,被斩去头颅,失了首级后,以双乳为眼,肚脐为口,再战黄帝。

刑天没有了头仍然可以战斗,程序没有了 main 函数,还能跑吗?

答案是:可以的。

代码

nomain.c

#include <stdio.h>
#include <stdlib.h>

void nomain()
{
  
    printf("hello world\n");
    exit(0);
}

编译

$ gcc -nostartfiles nomain.c -o nomain.out
/usr/bin/ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000001050

忽略警告
-nostartfiles 选项是让链接器在链接时不使用标准启动文件

运行

$ ./nomain.out 
hello world

探索

我们使用 -S 参数将 c 程序编译成汇编,一探究竟

$ gcc -S -nostartfiles nomain.c
liyongjun@Box:~/project/c/C_study/others/ld$ cat nomain.s 
        .file   "nomain.c"
        .text
        .section        .rodata
.LC0:
        .string "hello world"
        .text
        .globl  nomain
        .type   nomain, @function
nomain:
.LFB6:
        .cfi_startproc
        endbr64
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %edi
        call    exit@PLT
        .cfi_endproc
...

可以看到程序的入口点确实是 nomain,所以我们的程序可以正常运行。

疑问

如果代码里有两个函数,程序该选择哪个作为入口呢?

#include <stdio.h>
#include <stdlib.h>

void nomain()
{
    printf("hello world\n");
    exit(0);
}

void nomain_2()
{
    printf("hello world 2\n");
    exit(0);
}
$ gcc -nostartfiles nomain.c -o nomain.out
/usr/bin/ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000001050
$ ./nomain.out 
hello world

从执行的结果可以看到程序选择了 nomain() 函数作为了程序入口点,看下汇编的内容:

$ gcc -S -nostartfiles nomain.c
liyongjun@Box:~/project/c/C_study/others/ld$ cat nomain.s 
        .file   "nomain.c"
        .text
        .section        .rodata
.LC0:
        .string "hello world"
        .text
        .globl  nomain
        .type   nomain, @function
nomain:
.LFB6:
        .cfi_startproc
        endbr64
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        movl    $0, %edi
        call    exit@PLT
        .cfi_endproc
.LFE6:
        .size   nomain, .-nomain
        .section        .rodata
.LC1:
        .string "hello world 2"
        .text
        .globl  nomain_2
        .type   nomain_2, @function
nomain_2:
.LFB7:
        .cfi_startproc
        endbr64
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC1(%rip), %rdi
        call    puts@PLT
        movl    $0, %edi
        call    exit@PLT
        .cfi_endproc
...

显然,在 c 程序中,nomain 函数在 nomain_2 函数之前,编译成汇编后,nomain 依然在前面,就被选择作为了程序的入口点。如果我们把 nomain_2 写在前面,那么 nomain_2 就会被选为函数的入口点,验证如下:

#include <stdio.h>
#include <stdlib.h>

void nomain_2()
{
  
    printf("hello world 2\n");
    exit(0);
}

void nomain()
{
  
    printf("hello world\n");
    exit(0);
}
$ gcc -nostartfiles nomain.c -o nomain.out
/usr/bin/ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000001050
$ ./nomain.out 
hello world 2

指定

在不改变代码的情况下,我们可以使用 gcc 的 -e 选项来指定程序的入口函数

#include <stdio.h>
#include <stdlib.h>

void nomain_2()
{
  
    printf("hello world 2\n");
    exit(0);
}

void nomain_3()
{
  
    printf("hello world 3\n");
    exit(0);
}

void nomain()
{
  
    printf("hello world\n");
    exit(0);
}
$ gcc -nostartfiles -e nomain_3 nomain.c -o nomain.out
$ ./nomain.out 
hello world 3