1. 编译和链接----你真的了解HelloWord吗

本文详细解析了应用程序开发中编译和链接的四个步骤:预处理、编译、汇编和链接,通过实例展示了GCC如何处理C/C++源代码,并揭示了链接器、启动文件和链接参数的作用。

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

  • 平时的应用程序开发,很少需要关注编译和链接的过程,因为通常的开发环境都是流行的集成开发环境(IDE),比如VS等,这些IDE通常将编译和链接的过程一步完成,我们通常将这种合并到一起的过程称为构建。其实即使用命令行来编译一个源代码文件,简单的一句gcc hello.c 命令就包含了非常复杂的过程。
  • 虽然这些默认配置、编译和链接参数对于大部分的应用程序开发已经足够了,但是在开发过程中,我们会被这种便捷所迷惑,软件的运行机制和机理被掩盖了,一些程序莫名其妙的错误我们无法解决,性能瓶颈也束手无策。只能看到现象,看不到本质,所以要深入了解这些机制。

1.编译器到底隐藏了哪些东西

// hello.c
#include <stdio.h>

int main()
{
    printf("Hello World\n");
    return 0;
}

在Linux下,当我们使用GCC来编译该程序时,只需要最简单的命令

gcc hello.c
./a.out

在这里插入图片描述

事实上,上述过程可以分解为4个步骤:预处理、编译、汇编、链接

在这里插入图片描述

1.1 预编译

  • 首先是源代码文件hello.c 和相关的头文件,如stdio.h 等被预编译器cpp 预编译成一个.i 文件。对于c++ 来说,它的源文件扩展名可能是.cpp 或者.cxx,头文件的扩展名可能是.hpp ,而预编译后的文件扩展名是.ii

  • 第一步的预编译过程相当于如下命令:其中-E 表示只进行预编译

    gcc -E hello.c -o hello.i

在这里插入图片描述

或者

cpp hello.c > hello.i

  • 预编译过程主要处理源文件中以# 开始的预编译指令,比如#include, #define 等。规则主要如下:
    • 将所有的#define 删除,并且展开所有的宏定义
    • 处理所有的条件编译指令,比如#if, #ifdef, #elif, #else, #endif 条件编译
    • 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置,注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
    • 删除所有的注释//. /* */
    • 添加行号和文件名标识,比如 #2 "hello.c" 2 ,以便编译时编译器产生调试用的行号的信息,及用于编译时产生编译错误或警告时能够显示行号。
    • 保留所有的#progma编译器指令,因为编译器需要使用他们

在这里插入图片描述

在这里插入图片描述

可以看到简单的几行代码,预编译之后变成了733行!!!

行号也互相匹配

在这里插入图片描述

经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题

1.2 编译

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分,也是最复杂的部分。

  • gcc -S hello.i -o hello.s 或者 gcc -S hello.c -o hello.s

在这里插入图片描述

  • 现在版本的GCC把预编译和编译两个步骤合并成一个步骤,使用一个叫做ccl 的程序来完成这两个步骤。/usr/lib/gcc/x86_64-linux-gnu/9/cc1 ,因此也可以直接调用ccl 来完成

    /usr/lib/gcc/x86_64-linux-gnu/9/cc1 hello.c

对于C语言来说,这个预编译和编译的程序是cc1 ,对于C++来说,有对应的程序叫做cc1plus ;Objective-C是cc1objfortranf771 ;Java是jc1

所以实际上gcc命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译程序cc1, 汇编器as,链接器ld

在这里插入图片描述

1.3 汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,没有复杂的语法,也没有语义,也不需要指令优化,按照对照表一一翻译就行。

汇编过程可以调用汇编器as 来完成

as hello.s -o hello.o

或者

gcc -c hello.s -o hello.o

或者直接使用gcc命令,从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件

gcc -c hello.c -o hello.o

在这里插入图片描述

在这里插入图片描述

2.1.4 链接

  • 为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?
  • 链接过程到底包含了什么内容?
  • 为什么要链接?

下面我们来看看怎样调用ld 才可以产生一个能够正常运行的HelloWorld 程序

ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginT.o
 -L/usr/lib/gcc/x86_64-linux-gnu/9  -L/usr/lib  -L/lib hello.o
 --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/9crtend.o
 /usr/lib/crtn.o

如果把所有的路径都省略掉,那么就是

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc -end-group crtend.o crtn.o

也就是说,我们需要将一大堆文件链接起来才可以看到a.out,也就是最终的可执行文件。

  • 那么crt1.o, crti.o .... 这些文件是什么?
  • 做什么用?
  • -lgcc -lgcc_eh -lc 这些是什么参数?为什么使用?
  • 为什么要将他们和hello.o 链接起来才能得到可执行文件?

这些需要后续的章节一点点看了,未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今儿背单词吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值