一.C语言的编译和链接
我们的.c文件是如何一步步成为我们的可执行文件的呢?这就是我们的前言要讨论的了。
首先我们的.c文件会经过我们的编译器生成一个后缀为.obj的目标文件,再会和我们存放在静态库中的库函数等一系列函数一同经过我们的链接器生成我们后缀为.exe的可执行程序(在windos系统下后缀是.exe)用一张图来表示就是:
每一个.c文件都会单独进入一次编译器生成相对的.obj目标文件如test.c就会生成一个test.obj的文件
我们先写一句进行执行
再去该文件的目录下寻找Debug文件夹里面我们会发现有一个test.obj的目标文件
再去上一级文件夹里寻找Debug文件夹我们会发现生成了一个.exe的可执行程序
(因为我还没上传git所以这里有红色的感叹号~)
而编译里又有三个模块儿分别是,预编译(预处理),编译,汇编
1.预编译
预编译要处理的事情就是:
1.将所有的注释删除
2.就是将所有的头文件进行包含
3.就是将#define定义的常变量或者宏进行一个替换
…
所有的预处理指令都在预编译的阶段完成
2.编译
就是将我们的文本C语言转化成汇编指令因为我们的计算机只认识二进制文件,我们平时在写代码的时候,写的都是文本文件,所有再编译的阶段会将其转化成汇编指令且进行词法分析,语法分析,语义分析,符号汇总
3.汇编
将汇编指令翻译成二进制文件,并且形成符号表
4.链接
进行合并段表和符号表的重定位
这里的符号表就是在编译过程中会将一些特殊的符号比如printf,scanf,add等一系列的特殊符号进行汇总,在汇编的时候生成符号表,在链接的时候就会通过这个符号表去寻找该特殊符号的地址来进行相关操作,所谓的函数未定义就是在链接的过程中发现的,因为通过这个符号找不到有效地址就会报错
二.C语言中的宏与函数
#define定义的宏其写法类似函数但也有所不同比如宏就没有参数类型
但是使用宏的时候我们也需要注意一个非常重要的点就是宏只是将参数进行替换除此之外任何操作都不做
我们来看一段代码
#define ADD(x,y) x+y
int main()
{
int a = 3;
int b = 2;
int c = ADD(a, b) * 3;
printf("%d\n", c);
return 0;
}
我们一般想的就是(3+2)3 = 15,可是真是这样的吗?我们刚刚才说过,#define只进行替换其余的一切都不会进行操作所以这里的ADD(a,b)被转化成了3+2所以式子就转化成了3+23也就是9
细心的你们应该也发现了,我们在对#define声明的无论是宏还是常变量我们的命名都是采取的全大写为了与函数进行区分
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。 - 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意: - 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
区别
一共有7处
三.C语言中的预处理指令
#undef
这条指令用于移除一个宏定义
条件编译 #if #elif #endif
如它的名字一样,这里的#if 与我们常常写的if不一样我们的if条件判断语句是无论满不满足的语句都会去参加编译,但是我们的条件编译,如果这里的条件不满足我们将不会对其进行编译,在预编译的过程中我们就会直接将这段代码删除,这里的用法在于如果我们要实现一个跨平台的文件,我们会写两份代码,比如一份是windos的代码一端是Linux系统的代码,我们就会运用到#if来判断这里要使用的代码,但是#if和#endif是配套使用的,不可以只写一端,#if是可以与#elif一块儿使用,在使用的时候和普通的if语句一样使用但是功能则截然不同,还有一个点就是在头文件的包含中
我们在此会发现一个头文件被我们包含了两次,在预编译的时候我们就发现,头文件就是在预编译的过程中被包含的,所以多次包含我们的头文件会导致我们的资源更多的占用和资源浪费那我们如何解决这个问题呢?
答案是条件编译:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
这里就预防了我们头文件的多次使用或者是文件的多次引用相比大家也许在不同的地方都见过这个代码
但是目前VS2019会在我们每一次创建头文件的开头加一句
#pragma ones来预防我们的头文件多次包含