C 语言编程 — 程序的编译流程
目录
文章目录
《C 语言编程 — GCC 工具链》
《C 语言编程 — 程序的编译流程》
《C 语言编程 — 静态库、动态库和共享库》
《C 语言编程 — 程序的装载与运行》
《计算机组成原理 — 指令系统》
《C 语言编程 — 结构化程序流的汇编代码与 CPU 指令集》
C 程序的编译流程
虽然我们称 GCC 是 C 语言的编译器,但由 C 语言源代码文件到生成可执行文件的过程不仅仅是编译的过程,而是要经历以下四个相互关联的步骤:
- 预处理(Preprocessing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
示例代码:
#include<stdio.h>
int main(void)
{ printf("Hello World!\n"); return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
预处理
预处理(Preprocessing):GCC 首先调用预处理程序 cpp 进行预处理,在预处理过程中,.c 文件中的文件包含(include)、预处理语句(e.g. 宏定义 define 等)进行分析,并替换成为真正的内容。
- 执行所有的预处理器指令,并且展开所有的宏定义。
- 删除所有注释。
- 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
- 保留所有的
#pragma
预处理器指令,后续编译过程需要使用它们。
gcc -E -I . hello.c -o hello.i
- 1
- -E 是让编译器在预处理之后就退出
- -I 指定头文件目录
- -o 指定输出文件名
下面可以看到预处理后,代码从 7 行扩展到了 845 行。.i 文件里面包含了所有 include 和宏定义的真正内容。
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int
main(void)
{
printf("Hello World!" "\n");
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
- 词法分析:解析代码的 Tokens。
- 语法分析:建立抽象语法树。
- 语义分析:编译器通过语法树来理解代码的含义。检测出非法的语义。
gcc -S -I . hello.i -o hello.s
- 1
- -S 让编译器在编译之后停止
下面可以看到编译后的汇编代码:
main:
.LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
汇编
汇编过程对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为 .o 的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编过程相对于编译过程比较简单。通过调用 Binutils 工具中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可。一般来讲,.S 文件和 .s 文件经过预处理和汇编之后都会生成以 .o 的目标文件。
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成 .o 目标文件后,才能进入下一步的链接工作。
注意:目标文件已经是最终程序的某一部分了,但是在链接之前还不能执行。
注意:hello.o 目标文件为 ELF(Executable and Linkable Format)格式的可重定向文件。
$ gcc -c hello.s -o hello.o
# or
$ as hello.s -o hello.o
$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
- 1
- 2
- 3
- 4
- 5
- 6
链接
链接也分为静态链接和动态链接:
-
静态链接:是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
-
动态链接:则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
-
动态链接:用动态库进行链接。
gcc hello.c -o hello
- 1
- 静态链接:用静态库进行链接。
gcc -static hello.c -o hello
- 1
可以使用 Binutils 的 size 命令查看生成的 ELF 可执行文件的大小,可以使用 Binutils 的 ldd 命令查看链接的动态库。
链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常被链接为不同的段,常见的段譬如 .text、.data、.rodata、.bss 等。
编译多个源文件
- main.c
#include "hello.h"
int main(void)
{ print("hello world"); return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- hello.c
#include "hello.h"
void print(const char *str)
{ printf("%s\n", str);
}
- 1
- 2
- 3
- 4
- 5
- 6
- hello.h
#ifndef _HELLO_H
#define _HELLO_H
#include <stdio.h>
void print(const char *str);
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
一次性编译:
$ gcc -Wall hello.c main.c -o main
$ ./main
- 1
- 2
- 3
独立编译:
$ gcc -Wall -c main.c -o main.o
$ gcc -Wall -c hello.c -o hello.o
$ gcc -Wall hello.o main.o -o newmain
$ ./newmain
- 1
- 2
- 3
- 4
- 5
文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。
原文链接:is-cloud.blog.csdn.net/article/details/106912436
- 点赞
- 收藏
- 关注作者
评论(0)