hello程序是如何被编译出来的?(转)(经典)

作者:守望,Linux应用开发者,目前在公众号【编程珠玑】 分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。

前言

hello程序几乎是我们每个人学习C语言写的第一个程序,但是它是如何从.c文本变成可以打印出”hello world“的可执行文件的呢?本文将简单介绍其过程。

Hello World

hello world程序我们再熟悉不过:

/*include head file*/
#include<stdio.h>
/*the main function*/
int main(int argc,char *argv[])
{
    printf("Hello World!
");
    return 0 ;
}

编译并运行:

 gcc -o helloWorld helloWorld.c 
 ./helloWorld
 Hello World!

整个过程一气呵成,但是实际上上面的过程并非像看起来那么简单。它可以大体分为4个步骤:预处理,编译,汇编,链接。接下来我们一一简单介绍这四个步骤做了什么。

预处理

预处理主要是处理源代码中以#开头的指令(#pragma 除外),例如本文hello world程序中的#include,预处理之后会将stdio.h的内容插入到预处理指令的位置。

想要只生成预处理之后的内容,可以使用下面的方式:

gcc -E -o helloWorld.i helloWorld.c #-E参数表示只进行预处理

生成的helloWorld.i即为预处理之后的内容,有兴趣的可以打开文件查看里面的内容,会发现stdio.h的位置被其实际内容所替代。预处理之后,注释内容也会被删除,宏定义会被展开。

编译

预处理之后就需要对生成的预处理文件进行词法分析,语法分析,语义分析,最终产生汇编代码文件,说白点可以简单理解为将C代码“翻译”成汇编代码。该过程是核心同时也是较复杂的一个过程。我们可以通过命令:

gcc -S -o helloWorld.s helloWorld.c #-S参数表示只到生成汇编为止
cat helloWorld.s
    .file   "helloWorld.c"
    .section    .rodata
.LC0:
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits

上面的内容即为编译之后得到的汇编代码。

汇编

汇编是将汇编代码翻译成机器可执行的指令,生成目标文件。整个过程较为简单,几乎只是按照汇编指令和机器指令进行一一翻译。我们可以用下面的命令获得汇编后的内容:

gcc  -o  helloWorld.o   -c helloWorld.c
od helloWorld.o  #查看二进制内容
0000000 042577 043114 000402 000001 000000 000000 000000 000000
0000020 000001 000076 000001 000000 000000 000000 000000 000000
0000040 000000 000000 000000 000000 001260 000000 000000 000000
0000060 000000 000000 000100 000000 000000 000100 000015 000012
0000100 044125 162611 101510 010354 076611 044374 072611 137760
(其他内容未显示)

链接

链接是以某种方式将各个目标文件整个在一起,生成最后的可执行文件。我们的hello程序中调用了printf函数,但是并不存在于helloWorld.o中,而是存在于libc.so或libc.a中,因此我们需要通过链接将它们融合在一起。

gcc -o helloWorld helloWorld.c

执行上面的命令之后,就得到了我们的helloWorld程序了,在linux下,它是一种ELF格式的文件,后面的文章我们会更多地介绍到。

我们通过ldd命令看到helloWorld程序链接了系统的库:

ldd helloWorld
    linux-vdso.so.1 =>  (0x00007ffe9ef11000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d9f038000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f0d9f402000)

有兴趣的也可以尝试一下,如果删除系统中的libc.so库(记得事先备份),发现能够编译过,却在最后链接失败。

总结

  • 我们总结整个编译过程大致如下:

 

 

而正是由于整个编译过程分阶段进行,我们可以看到不同类型的问题在不同阶段出现并且有先后顺序。正因如此,链接问题在编译的最后阶段才会出现。

  • gcc编译系统本身调用了很多其他相关工具,可以加上--verbose观察其详细编译过程,发现gcc命令调用了预处理器,编译器,汇编器,链接器等命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值