【C 语言】程序环境和预处理(你写的代码究竟遭遇了什么?)

目录

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

二、编译&&链接

1. 预处理

2. 编译(狭义的编译)

3. 汇编

4. 链接

注意:

三、预处理

预定义符号

#define

1. #define定义标识符

2.给类型定义别名

3.自定义一些关键字

4.通过宏作一些编译开关

5.定义一个代码片段

已经有函数了,为什么还要有宏来完成类似的效果?

宏带来的问题


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

  1. 翻译环境:  在这个环境中源代码被转换为可执行的机器指令
  2. 执行环境:  用于实际执行代码

二、编译&&链接

在我们写完代码按下CTRL + F5时,代码会进行:

  1. 编译:将.c文件变为exe文件
  2. 运行:将exe文件跑起来,指向里面的逻辑

但这里我们说的这个编译时 “广义” 的编译,这个编译其实还可以再分为很多步

1. 预处理

编译器先对代码进行一个初步的处理:会执行代码中的预处理指令(以#开头的)

输入内容时.c文件,输出内容还是.c文件

2. 编译(狭义的编译)

将 C 语言文件变成一个汇编语言的文件

原因就是:C语言是高级语言,计算机并不能直接认识,这就需要编译器将高级语言转换为机器指令,汇编语言和机器指令可以认为就是 “一一对应” 的关系

3. 汇编

这个过程就是将汇编语言文件,转换成二进制的机器指令

4. 链接

每个 .c 文件都会涉及到对应的机器指令文件,链接的过程就是将若干个 .c 文件生成的结果合并起来。(因为这里面经常会涉及到一个文件中的内容被另一个文件调用, 单纯只编译一个文件是不知道这个函数的定义内容的,为了能够获取到这个函数的实际执行的指令内容,就需要将这两个文件进行最终的合并)

注意:

我们在这里要将 “链接”“连接” 区分开

连接:connection(网络编程中的)

链接:Link (将两个东西建立关联guan'xi)

运行环境:

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
  2. 程序的执行便开始。接着便调用main函数
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值
  4. 终止程序。正常终止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;
}

运行结果:

 注意:这个和函数定义还是有很大区别

  1. 以#define开头
  2. ()中的参数是没有类型的
  3. 表达式部分也没有return语句
  4. 没有返回值类型

已经有函数了,为什么还要有宏来完成类似的效果?

  1. 参数可以是任意类型的,一个宏可以针对多个情况来使用
  2. 避免了函数调用的开销,尤其是传参拷贝这个开销。这个操作速度比函数调用更高效

但是还是不建议去使用宏

宏带来的问题

  1. 宏展开之后容易出现一些不预期的效果(宏参数带有副作用,括号问题)
  2. 宏定义的代码必须在同一行上,非常不方便调试,也不方便阅读
  3. 宏无法递归
  4. 宏没有参数检查

-----------------------------------------------------------------

-----------C语言 程序环境和预处理部分 完结---------

欢迎大家关注!!!

一起学习交流 !!!

让我们将编程进行到底!!!

--------------整理不易,请三连支持------------------

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_错错错

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值