c语言编译中间过程详细解释

c语言编译中间过程详细解释C 语言编译过程就是将高级语言代码转换为计算机可以理解的机器代码 注 图来自 https zhuanlan zhihu com p C 语言的编译过程包括四个步骤 1 预处理 展开头文件 宏替换 去掉注释 条件编译 te

大家好,欢迎来到IT知识分享网。

C语言编译过程就是将高级语言代码转换为计算机可以理解的机器代码。

c语言编译中间过程详细解释

注:图来自https://zhuanlan.zhihu.com/p/

C 语言的编译过程包括四个步骤:

(1)预处理:展开头文件/宏替换/去掉注释/条件编译(test.i main .i)。

(2)编译 :检查语法,生成汇编 ( test.s main .s)。

(3)汇编:汇编代码转换机器码(test.o main.o)。

(4)链接:链接到一起生成可执行程序 a.out/a.exe。

下图就是C程序编译的完整过程:

c语言编译中间过程详细解释

注:图来自https://zhuanlan.zhihu.com/p/


1.预处理

编译过程的第一步就是预处理,主要是完成删除所有的注释、宏扩展和文件包含的作用。

gcc -E hello.c -o hello.i

预处理结束后会产生一个后缀为(.i)的临时文件,这一步由预处理器完成。

(1)删除所有的注释:预处理器会在编译过程中删除所有注释,因为注释不属于程序代码,它们对程序的运行没有特别作用。

(2)宏扩展:宏是使用 #define 指令定义的一些常量值或表达式。宏调用会导致宏扩展。预处理器创建一个中间文件,其中一些预先编写的汇编级指令替换定义的表达式或常量(基本上是匹配的标记)。为了区分原始指令和宏扩展产生的程序集指令,在每个宏展开语句中添加了一个“+”号。

c语言编译中间过程详细解释

注:图来自https://blog.csdn.net/weixin_/article/details/

宏优点 (1)代码复用性 (2)提高性能 宏缺点 (1)不可调试(预编译阶段进行了替换)。 (2)无类型安全检查。 (3)可读性差,容易出错。

这里附上《c和指针》中的一张表格,总结宏和函数十分到位:

c语言编译中间过程详细解释

注:图来自https://blog.csdn.net/weixin_/article/details/

但是,define宏在某些方面真的是非常好用。

(1)替代路径

#define ENG_PATH_1 C:\Program Files (x86)

(2)针对编译器版本不兼容报错。

#define _CRT_SECURE_NO_WARNINGS 1

(3)条件编译。

#ifdef 标识符 程序段 1 #else 程序段 2 #endif

(4)使用库中的宏。

还有其他许多重要的预处理。

比如include

#include 
   

尖括号是预处理到系统规定的路径中去获得这个文件(即 C 编译系统所提供的并存放在指定的子目录下的头文件)。找到文件后,用文件内容替换该语句。如stdio.h

#include“filename”

“”则是预处理我们自己第三方的文件,如程序员小郭写的Date.h,我们就可以include“Date.h”。

#error 预处理,#line 预处理,#pragma 预处理。

1.#error 预处理指令的作用是,编译程序时,只要遇到 #error 就会生成一个编译错误提示消息,并停止编译。

2.#line 的作用是改变当前行数和文件名称,如#line 28 liu 。

3.#pragma 是比较重要且困难的预处理指令。

#pragma once

这个的做用就是防止头文件多次包含。当然,还有另外一种风格,防止被包含是巧妙地利用了define宏。

#ifndef _SOME_H #define _SOME_H ...//(some.h头文件内容) #endif

(3)文件包含:C语言中的文件包含是在预处理期间将另一个包含一些预写代码的文件添加到我们的C程序中。它是使用#include指令完成的。在预处理期间包含文件会导致在源代码中添加文件名的全部内容,从而替换#include
<文件名>
指令,从而创建新的中间文件。

预处理如锲子中所言,是一种展开,下表是常用的一些预处理命令。


2.编译

编译阶段使用内置编译器软件将 (.i) 临时文件转换为具有汇编级指令(低级代码)的汇编文件 (.s)。为了提高程序的性能,编译器将中间文件转换为程序集文件。

gcc -S hello.i -o hello.s

汇编代码是一种简单的英文语言,用于编写低级指令(在微控制器程序中,使用汇编语言)。整个程序代码由编译器软件一次性解析(语法分析),并通过终端窗口输出源代码中存在的任何语法错误或警告。

编译阶段是检查语法,生成汇编,这个属于程序员的必备知识,学习一门语言第一步就是知晓语法,其中比较生涩的有左值右值,指针的使用,内存的管理,数据结构的使用,这将会是一场持久战 ,贯穿在整个学习生涯。

在这里截取优先级问题,这个可能会通过编译但是不一定达到程序员想要的结果。引用《c语言深度解剖》中的一张表格。

c语言编译中间过程详细解释

注:图来自https://blog.csdn.net/weixin_/article/details/


3.汇编

使用汇编程序将程序集级代码(.s 文件)转换为机器可理解的代码(二进制/十六进制形式)。汇编程序是一个预先编写的程序,它将汇编代码转换为机器代码。它从程序集代码文件中获取基本指令,并将其转换为特定于计算机类型(称为目标代码)的二进制/十六进制代码。

生成的文件与程序集文件同名,在 window 中称为扩展名为 .obj 的对象文件,在 UNIX 操作系统中扩展名为 .o。程序集文件 hello.s 将转换为具有相同名称但扩展名不同的对象文件 hello.o。

gcc -c hello.s -o hello.o 

4. 链接

链接是将库文件包含在程序中的过程。库文件是一些预定义的文件,其中包含机器语言中的函数定义,是写好的,现有的,成熟的,可以复用的代码。本质上来说,库文件是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。

Linux系统中{静态库扩展名为.a},{动态库(共享库)扩展名为.so}。

Window系统中{静态库扩展名.lib},{动态库的扩展名.dll}。

静态库与动态库的区别

(1)静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。

(2)静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行;动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。

静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。静态库链接的时候把库直接加载到程序中,而动态库链接的时候,它只是保留接口,将动态库与程序代码独立,这样就可以提高代码的可复用度和降低程序的耦合度。

1.静态(函数)库

一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。

这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。

2.动态(函数)库

动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。

与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。

静态库与动态库优缺点

1、静态库 优点: ①静态库被打包到应用程序中加载速度快 ②发布程序无需提供静态库,移植方便 缺点: ①相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存。 ②库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。

2、动态库

优点: ①可实现不同进程间的资源共享。 ②动态库升级简单,只需要替换库文件,无需重新编译应用程序。 ③可以控制何时加载动态库,不调用库函数动态库不会被加载 。 缺点: ①加载速度比静态库慢。 ②发布程序需要提供依赖的动态库。

链接的作用将编译出来的目标文件和代码所用到的库文件一起打包成一个可执行文件的过程。例如hello.c中的打印函数printf,这个函数不是凭空出现的,在链接的过程中就要连同对应库文件一起打包,最终可执行文件才能正常运行。在 window 中扩展名为 .exe,在 UNIX 操作系统中为 .out。无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。

下图显示了链接阶段如何工作的示例,我们有一个具有机器级代码的对象文件,它通过链接器传递,链接器将库文件与对象文件链接以生成可执行文件。

c语言编译中间过程详细解释


5.举例

接下来,通过一个例子详细看看C编译过程中涉及的所有步骤。第一步先写一个简单的C程序并保存为hello.c

#include 
       
         int main() { // printf() is a output function which prints // the passed string in the output console printf("Hello World!"); return 0; } 
       

接着执行编译命令对hello.c进行编译:

gcc -save-temps hello.c -o compilation

-save-temps 选项会保留所有编译过程中产生的中间文件,总共会生成四个文件。

c语言编译中间过程详细解释

hello.i 预处理器产生的文件 hello.s 编译器编译后产生的文件 hello.o 汇编程序翻译后的目标文件 hello.exe 可执行文件(Linux系统会产生hello.out文件)
c语言编译中间过程详细解释

首先,C程序的预处理开始,注释从程序中删除,因为该程序中没有宏指令,因此宏扩展不会发生,我们还包含了一个stdio.h头文件,并且在预处理期间,标准输入/输出函数(如printf(),scanf()等)的声明被添加到C程序中。

打开预处理阶段产生的hello.i 文件就可以看到类似下面这样的代码。

# 1 "hello.c" # 1 "" # 1 "" # 1 "hello.c" # 1 "C:/Program Files (x86)/CodeBlocks/MinGW/include/stdio.h" 1 3 # 293 "C:/Program Files (x86)/CodeBlocks/MinGW/include/stdio.h" 3 int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) fprintf (FILE*, const char*, ...); int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) printf (const char*, ...); int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) sprintf (char*, const char*, ...); int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) scanf (const char*, ...); int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) sscanf (const char*, const char*, ...); ... ... ... int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) putw (int, FILE*);
# 3 "hello.c" 2 int main() { printf("Hello World!"); return 0; }

从上面的代码中可以看到,预编译过后,所有的注释没有了,#include

也被它的头文件内容代替了。

接下来就是编译阶段,编译器接收到hello.i 文件将它转化为汇编代码hello.s文件。这过程中发生了以下几件事:

1.编译器检查语法错误。 2.将源代码翻译中间代码,例如汇编代码。 3.对代码进行优化。

编译结束后产生的hello.s 文件:

.file "hello.c" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "Hello World!\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB12: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp call ___main movl $LC0, (%esp) call _printf movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE12: .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0" .def _printf; .scl 2; .type 32; .endef

接下来,汇编程序把hello.s 文件转换为二进制代码,并在Windows环境中生成对象文件 hello.obj,在 Linux系统中生成 hello.o文件。

接着,链接器使用库文件将所需的定义添加到对象文件中,并在Windows环境中生成一个可执行文件 hello.exe,在 Linux操作系统中生成 hello.out文件。

当我们运行 hello.exe/hello.out 时,会在屏幕上 输出 Hello World!。

C语言编译过程中程序的流程图:

c语言编译中间过程详细解释

注:图来自https://zhuanlan.zhihu.com/p/


总结

1.C中的编译过程也称为将人类可理解代码(C程序)转换为机器可理解代码(二进制代码)的过程。

2.C语言的编译过程包括四个步骤:预处理、编译、汇编和链接。

3.预处理器执行删除注释、宏扩展、文件包含。这些命令在编译过程的第一步执行。

4.编译器可以提高程序的性能,并将中间文件转换为汇编文件。

5.汇编程序有助于将汇编文件转换为包含机器代码的对象文件。

6.链接器用于将库文件与对象文件链接。这是编译中生成可执行文件的最后一步。


6.参考内容

[1]知乎作者Lion-Zwart的文章《C语言的编译过程详解》,文章链接为:https://zhuanlan.zhihu.com/p/

[2] CSDN作者木槿花better的文章《c语言编译过程详解,预处理,编译,汇编,链接(干货满满)(高级语言不一定都有生成汇编这一步骤)》,文章链接为:

https://blog.csdn.net/weixin_/article/details/

[3] CSDNz作者胖墩会武术的文章《静态库和动态库的区别以及在C/C++中的调用方法》,文章链接为:

https://blog.csdn.net/shinuone/article/details/

[4]知乎作者 沐歌爱编程的文章《C语言丨静态库与动态库的区别,你知道多少?》,文章链接为:

https://zhuanlan.zhihu.com/p/

[5] CSDN作者雨荔@秋垣的文章《静态库与动态库的区别与优缺点》,文章链接为:

https://blog.csdn.net/weixin_/article/details/

本文内容来源于网络,仅供参考学习,如内容、图片有任何版权问题,请联系处理,24小时内删除。


作 者 | 郭志龙

编 辑 | 郭志龙
校 对 | 郭志龙

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/94982.html

(0)
上一篇 2026-03-17 15:45
下一篇 2026-03-17 18:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信