程序环境和预处理1

目录

一、 程序的翻译环境和执行环境:

二、 详解编译+链接:

2.1 翻译环境:

2.1.1、如何在预处理结束后停止进行观察呢?

2.1.2、如何在编译结束后停止进行观察呢?

2.1.3、如何在汇编结束后停止进行观察呢?

2.2 编译过程的划分:

三、 执行环境(运行环境):

一、 程序的翻译环境和执行环境:

当写出一个 test.c 源文件的时候,如何通过编译链接产生一个可执行程序 test.exe 呢?

在本节课开始之前,我们是通过使用 ctrl+F5 来产生该可执行程序的,但是具体如何把一个源文件编译链接产生成一个可执行程序的过程,

将在本节课中进行阐述、

ANSI C(任何一个标准C的程序) 的任何一种实现中,存在 两个不同 的环境。
第1种是 翻译环境 ,在这个环境中 源代码被转换为可执行的机器指令。
第2种是 执行环境 ,它用于 实际执行代码、

二、 详解编译+链接:

2.1 翻译环境:

1、 组成一个程序的 每个源文件 通过 编译 过程分别转换成 目标代码 object code )。
2、每个目标文件 链接器 linker )捆绑在一起,形成一个单一而完整的 可执行程序
3、 链接器同时也会引入 标准C函数库 中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也 链接到程序
中、

在一个工程中,可能存在多个源文件,即 .c 文件,此时,工程中的每一个源文件都需要单独经过翻译环境进行处理,即,每一个源文件都

单独经过编译器的处理,生成各个源文件对应的目标文件,即在windows系统下的 .obj 文件,当生成多个目标文件的时候,这些目标文

件一起再经过链接器,同时链接器在链接的时候,也会把链接库链接进去,即,链接器会把这些多个目标文件以及链接库连接在一起,

后生成可执行程序、

代码中使用到一些库函数的时候,就会把该库函数下的 .lib 文件,即静态库,这就是所谓的链接库,该链接库与多个目标文件,同时被链

接器连接在一起,生成最后的可执行程序这些链接库中就包含了对应的库函数的信息、

这样不能观察到翻译环境中编译和链接过程中的细节,应该怎么做才可以观察到里面的细节呢?

2.1.1、如何在预处理结束后停止进行观察呢?

使用指令gcc test.c -E

使用该指令后,屏幕上就会输出一堆东西,比较凌乱,此时就可以使用指令:gcc test.c -E > test.i ,就可以把 > 前面的指令所产生的所有

的结果输出重定向重新输出到一个其他的文件中去,此时,test.i 文件中就会保存 gcc test.c -E 指令所产生的所有的结果,然后再使用指令:

vim test.i 来打开该文件,就可以观察到其中的内容、

这一步就代表着完成了头文件的包含,头文件包含的具体过程就是把 stdio.h 中的内容拷贝了一份放在了该文件 test.i 中去了,所以,上图

中的红色框里面的内容以及上面省略掉的部分均是头文件 stdio.h 中的内容,被拷贝了一份放在该文件中、 

如果更改源文件 test.c 内容为:

此时,通过指令 gcc test.c -E 得到预处理后的结果,然后再使用指令:gcc test.c -E > test.i 输出重定向到文件 test.i 中,然后再使用指令:

vim test.i 得到下面的结果:

所以,在预处理阶段主要完成了:

1、头文件的包含,即:#include<xxxxx>、

2、完成了#define定义的符号和宏的替换、

3、完成了注释的删除、

......

以上操作都是文本操作,在预处理之后的过程中,就看不到符号和宏了,已经被替换掉了,同时也看不到注释,因为已经被删除掉了、

2.1.2、如何在编译结束后停止进行观察呢?

使用指令:gcc test.i(test.c) -S  就可以生产 test.s 文件,该文件就是在编译过程停止后的到的文件、

该文件的内容为:

预编译结束后,仍是C语言的代码,,而在编译结束后变成了汇编代码,也就意味着,该步骤将C语言代码替换成了汇编代码, 此转换过程主要包括:

1、语法分析、

2、词法分析、

3、语义分析、

4、符号汇总、

即把所有的源文件中的全局符号,包括函数名,全局变量等等的符号都汇总起来、

2.1.3、如何在汇编结束后停止进行观察呢?

使用指令:gcc test.s -c  生成 test.o (linux系统下)文件,该文件类似于 windows系统 下的  .obj 文件、

使用指令:vim test.o 打开该文件得到: 

这不是文本文件,而是二进制文件,所以,在汇编过程中,将文本文件转化成为二进制文件,即,把汇编代码,转换成机器指令(二进制

指令) ,此时的二进制指令可由机器和一些工具来读懂,除此之外,该过程还生成了符号表、

其中,test.o 文件的格式为:elf格式,即文件的组成形式,是一种格式每个目标文件,即 linux 系统下的 .o 文件,都会被分为一个个的

,所有的 .o 文件划分的段是相同的,但是,不同的是,每个段中的内容是不相同的,此时,使用工具readelf就可以读懂二进制指令的内

容、

VS编辑器下把加法的运算拆成两个文件来实现、 

如上图所示,两个源文件,即 test.c 和 Add.c 文件都需要单独经过编译器处理:

此时,通过编译阶段就会进行符号汇总,把所有的符号汇总起来,再通过汇编阶段生成符号表,而符号表的使用是在链接阶段中使用、

如果把 Add.c 中的程序删除掉的话,那么经过编译,再经过汇编,链接,由于 Add.o 中没有符号,所以得到的符号表仅来源于 test.o

件,再经过合并与重定位符号表得到的仍是 test.o 文件中的符号表,但是由于该文件中的符号 Add 的地址是无效的,所以在运行程序的时

候,就会报错,即无法解析的外部符号,_Add、

2.2 编译过程的划分:

sum.c

int g_val = 2016;
void print(const char *str) 
{
 printf("%s\n", str);
}


test.c

#include <stdio.h>
int main()
{
 extern void print(char *str);
 extern int g_val;
 printf("%d\n", g_val);
 print("hello bit.\n");
 return 0; 
}

如何查看编译期间的每一步发生了什么呢?

1、 预处理 选项 gcc -E test.c -o test.i   预处理完成之后就停下来,预处理之后产生的结果都放在 test.i 文件中。

2.、编译      选项 gcc -S test.c                 编译完成之后 就停下来, 结果保存在 test.s  中。
3.、汇编               gcc -c test.c                汇编完成之后 就停下来, 结果保存在 test.o   中。

三、 执行环境(运行环境):

程序执行的过程:
1、  程序必须载入 内存 中,只有把程序载入到内存中才可以去 执行 ,在 有操作系统的环境中 :一般这个动作由 操作系统 完成。在 独立的环境
       中,程序的载入必须由 手工 安排,也可能是通过 可执行代码置入只读内存 来完成、
2、 程序的执行便开始,接着便调用 main函数、
3、开始 执行程序代码 ,这个时候程序将使用一个 运行时堆栈(stack) ,存储函数的 局部变量和返回地址, 程序同时也可以使用 静态
   (static)内存 ,存储于静态内存中的变量在程序的 整个执行过程一直保留他们的值、
任何一个函数 的调用都要为其分配 内存空间 ,而为该函数分配的内存空间的 大小由编译器决定 ,为每个函数分配的内存空间也称为该函数
      的运行时堆栈, 运行时堆栈又可以称为函数栈帧 堆栈就是指的栈区,而堆才是所谓的堆区、
4、 终止程序 正常终止 main 函数,也有可能是 意外终止、

 后期会陆续更新关于程序环境和预处理的知识,感谢观看,谢谢大家,记得点赞收藏哦~

猜你喜欢

转载自blog.csdn.net/lcc11223/article/details/122806214