在C语言的代码编写完成到我们观察到程序执行效果的过程中,主要经历了两个不同的环境,分别是编译环境和执行环境。
目录
一、程序的翻译环境
翻译环境即源代码被转换为可执行的机器指令所处的环境。
翻译环境中主要的执行过程的编译和链接
1. 编译
编译的过程又可细分为预处理、编译、汇编
(1)预处理
在此个过程中,每个源文件(.c)各自经过编译器的处理,生成各自的目标文件(.obj)
由于在VS编译器中此过程不易被观察到,在此使用Linux环境进行演示
首先创建一个文件test.c,内容如下:
#include <stdio.h>
#define M 10
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = M;
int b = 2;
int c = Add(a, b);
// This line is used to print the value of c
printf("%d\n", c);
return 0;
}
然后使用 如下命令将test.c预编译,并将生成的内容保存到test.i中:
| gcc test.c -E |
随后打开text.i,可观察到如下:
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 375 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 392 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 393 "/usr/include/sys/cdefs.h" 2 3 4
# 376 "/usr/include/features.h" 2 3 4
# 399 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
# 10 "/usr/include/gnu/stubs.h" 3 4
# 1 "/usr/include/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/gnu/stubs.h" 2 3 4
# 400 "/usr/include/features.h" 2 3 4
# 28 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 1 3 4
# 212 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 3 4
typedef long unsigned int size_t;
# 34 "/usr/include/stdio.h" 2 3 4
...
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 943 "/usr/include/stdio.h" 3 4
# 2 "test.c" 2
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 2;
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
我们可以观察到,在经过预处理后,代码中发生了三点变化
a. 完成了头文件的替换
(为了便于观察,将替换后的stdio.h的代码进行了省略)
b. 完成了#define定义的符号的替换,和宏的替换(与函数的替换相似)
c. 注释的内容被删除,不会参与编译
(2)编译
编译的过程就是将预处理之后的C语言代码转换为汇编代码
使用 gcc test.i -S 将预处理之后的代码转换为汇编代码,如下:
汇编代码较为复杂,本文不做解读
(3)汇编
即将汇编代码转换为机器指令(二进制指令)
使用 gcc test.s -c 生成目标文件
使用readelf工具下的 readelf test.o -s 查看内容

2.链接
在整个编译完成后,在项目中把生成的目标文件与链接库通过连接器生成可执行文件。
以Linux为例,后缀为.out为可执行文件(windows中,为.exe)
此处我们将test.c中的Add函数单独写入另一个源文件add.c中,并执行与test.c同样的编译方式生成add.o文件。完成后目录内的文件如下:

由于一个项目中只能有一个main函数(位于test.c中),所以只需要执行test.c即可将add.o也加入到链接中。使用gcc test.c 生成可执行文件

可见生成了可执行文件a.out
二、程序的执行环境
在程序编译完成后,我们需要执行生成的可执行文件来观察程序的运行结果
使用./a.out来运行编译生成的可执行文件,如下:
![]()
1.执行过程
这里我们仅是观察到程序的运行结果,程序的执行过程主要有以下几点:
(1)程序必须载入内存中。在由操作系统的环境中时,有操作系统完成;独立环境中必须手动安排。
(2)程序的执行开始后,接着调用main函数。
(3)开始执行。程序使用一个运行时堆栈,存储所用到的变量和返回值。
(4)终止程序。正常终止main函数,也可能是意外终止。
本文详细介绍了C语言程序从源代码到可执行文件的编译过程,包括预处理、编译、汇编和链接。在Linux环境下,通过实例展示了预处理如何替换头文件和宏,编译如何生成汇编代码,以及汇编如何转化为机器指令。此外,还阐述了程序的执行环境,重点解释了程序加载内存、调用main函数以及运行时堆栈的工作原理。

被折叠的 条评论
为什么被折叠?



