目录
一、程序的翻译环境和执行环境
- 翻译环境: 在这个环境中源代码被转换为可执行的机器指令
- 执行环境: 用于实际执行代码
二、编译&&链接
在我们写完代码按下CTRL + F5时,代码会进行:
- 编译:将.c文件变为exe文件
- 运行:将exe文件跑起来,指向里面的逻辑
但这里我们说的这个编译时 “广义” 的编译,这个编译其实还可以再分为很多步
1. 预处理
编译器先对代码进行一个初步的处理:会执行代码中的预处理指令(以#开头的)
输入内容时.c文件,输出内容还是.c文件
2. 编译(狭义的编译)
将 C 语言文件变成一个汇编语言的文件
原因就是:C语言是高级语言,计算机并不能直接认识,这就需要编译器将高级语言转换为机器指令,汇编语言和机器指令可以认为就是 “一一对应” 的关系
3. 汇编
这个过程就是将汇编语言文件,转换成二进制的机器指令
4. 链接
每个 .c 文件都会涉及到对应的机器指令文件,链接的过程就是将若干个 .c 文件生成的结果合并起来。(因为这里面经常会涉及到一个文件中的内容被另一个文件调用, 单纯只编译一个文件是不知道这个函数的定义内容的,为了能够获取到这个函数的实际执行的指令内容,就需要将这两个文件进行最终的合并)
注意:
我们在这里要将 “链接” 和 “连接” 区分开
连接:connection(网络编程中的)
链接:Link (将两个东西建立关联guan'xi)
运行环境:
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
- 程序的执行便开始。接着便调用main函数
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值
- 终止程序。正常终止main函数;也有可能是意外终止。
三、预处理
预定义符号
C 语言的一些预处理符号可以使我们更加方便(以下这些都是C语言内置的)
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
代码演示:
#include <stdio.h>
int main() {
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
return 0;
}
运行结果:
可以看到通过预定义符号,我们很方便的打印出了所编译的源文件名,代码所在行号,代码编译日期,编译时间这些信息
通过__STDC__这个宏可以判断当前的编译器是否遵守 C 标准,如果遵守,结果就是1,不遵守,结果可能是0或者未定义。(但是这个宏在现在已经不常用了)
#define
1. #define定义标识符
如下代码:
int arr[10] = { 0 };
在这里创建了一个数组arr,长度为10,这个10其实就是一个魔幻数字(magic number),在我们后面写的代码多了之后后面的循环啥的都可能还要用这个10,但后面可能就已经不知道这10的具体代表的是什么意思了,并且如果后面要改这个数字10,我们得改很多地方(容易出错),但用了#define定义标识符常量之后就会很方便
#define SIZE 10
int main() {
int arr[SIZE] = { 0 };
return 0;
}
这样的话就会很直观很方便,后面看见SIZE就知道是什么,改起来也很方便
2.给类型定义别名
每次都用unsigned int敲起来就会比较麻烦,通过#define给他重新定义一个名字
#define uint unsigned int
int main() {
uint num = 5;
return 0;
}
3.自定义一些关键字
如下操作
#define and &&
int main() {
int a = 0;
int b = 0;
if (a > 0 and b > 0) {
printf("hello");
}
}
通过这样的操作我们就可以将&&用and代替
4.通过宏作一些编译开关
有的时候我们写的一些代码只是希望它在一些特定情况下去编译,在另外一些情况下不参与编译,这就是条件编译了
用到如下一些命令
#if
#else
#ifdef
#ifndef
#endif
…………
如下代码:
#define TEST 1
#if TEST
int main() {
return 0;
}
#endif
这段代码中,我们在代码前后加了条件编译命令,如果#if后面的条件成立,那么执编译中间的代码,否则不编译,最前面我们用#define做了宏开关,这样就能达到我们想要的效果
典型应用一:一份代码同时兼容开发环境和发布环境
典型应用一:一份代码兼容不同的系统
典型应用一:防止头文件被重复包含
典型应用一:实现多行注释的效果
5.定义一个代码片段
如下代码,可以通过宏来达到和调用函数类似的效果
#define ADD(x, y) x + y
int main() {
int a = 5;
int b = 10;
printf("%d\n", ADD(a, b));
return 0;
}
运行结果:
注意:这个和函数定义还是有很大区别的
- 以#define开头
- ()中的参数是没有类型的
- 表达式部分也没有return语句
- 没有返回值类型
已经有函数了,为什么还要有宏来完成类似的效果?
- 参数可以是任意类型的,一个宏可以针对多个情况来使用
- 避免了函数调用的开销,尤其是传参拷贝这个开销。这个操作速度比函数调用更高效
但是还是不建议去使用宏
宏带来的问题
- 宏展开之后容易出现一些不预期的效果(宏参数带有副作用,括号问题)
- 宏定义的代码必须在同一行上,非常不方便调试,也不方便阅读
- 宏无法递归
- 宏没有参数检查
-----------------------------------------------------------------
-----------C语言 程序环境和预处理部分 完结---------
欢迎大家关注!!!
一起学习交流 !!!
让我们将编程进行到底!!!
--------------整理不易,请三连支持------------------