【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)-2

简介: 【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)-2

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)-1

https://developer.aliyun.com/article/1456976?spm=a2c6h.13148508.setting.27.2e124f0eH7Z99f



3.2 #define

3.2.1 #define 定义标识符

语法:
#define name stuff

全部例子:


#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,       \
                          __DATE__,__TIME__ )

分点叙述:

#define MAX 1000

1e703ac1056042c2902497fb713dcd65.png

#define reg register//为 register这个关键字,创建一个简短的名字

6e0bbe84e66445f8acb05ee5fd3c1320.png



#define do_forever for(;;)//用更形象的符号来替换一种实现


0f66d398967f4eb9bf59df18014ade9d.png

#define CASE break;case //在写case语句的时候自动把 break写上。


c167049ee586448b847371becf86e1b8.png

长行如何拆分,\后面只能是换行,不能是其他


如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。


提问:

在define定义标识符的时候,要不要在最后加上 ; ?

比如:

#define MAX 1000;
#define MAX 1000

建议不要加上 ; , 这样容易导致问题。

比如下面的场景:

if ( condition )
        max = MAX ;
else
        max = 0 ;

这里会出现语法错误.


3.2.2 #define 定义宏  

下面是宏的声明方式:

#define name( parament-list ) stuff
其中的 parament - list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中

注意:

参数列表的左括号必须与 name紧邻 。

如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分

如:

#define SQUARE( x ) x * x

这个宏接收一个参数 x .

如果在上述声明之后,你把

SQUARE ( 5 );

置于程序中,预处理器就会用下面这个表达式替换上面的表达式:

5 * 5

图解:

6e6c046f075d451d8416d26990462e51.png


警告:

这个宏存在一个问题:

观察下面的代码段:

int a = 5 ;
printf ( "%d\n" , SQUARE ( a + 1 ) );

乍一看,你可能觉得这段代码将打印 36 这个值。

事实上,它将打印 11.

为什么?图解:

60f8453db416427ca1815252bddac47c.png


替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

这样就比较清晰了,由替换产生的表达式并没有按照预想的次序进行求值。

在宏定义上加上两个括号,这个问题便轻松的解决了:

#define SQUARE(x) (x) * (x)


这样预处理之后就产生了预期的效果:

printf ( "%d\n" ,( a + 1 ) * ( a + 1 ) );

但是这样也会受到外部因素的影响:

#define DOUBLE(x) (x) + (x)


fe1caad0387545a0b2838de828e00d46.png

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。


int a = 5 ;
printf ( "%d\n" , 10 * DOUBLE ( a ));

这将打印什么值呢?

warning :

看上去,好像打印 100 ,但事实上打印的是 55.

我们发现替换之后:

printf ("%d\n",10 * (5) + (5));

乘法运算先于宏定义的加法,所以出现了55 .

这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE( x)   ( ( x ) + ( x ) )

提示:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

e776767a90ac4a1bb84bc78f68ffe059.png


所以这就涉及到#define的替换规则了


3.2.3#define 替换规则

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤。
        1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。
        2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
        3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程。

注意:

1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。

3.2.4 #和##

如何把参数(变量名)插入到字符串中?


首先我们看看这样的代码:


char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

这里输出的是不是hello bit ?


答案是确定的:是

图解:

acd4ea5d3cf34aca822c0ea677c85c53.png


我们发现字符串是有自动连接的特点的。

       请看下图,有没有那么一条语句,将这三个变量传参传过去,然后实现它们各自的输出呢?


b1cf8fb85d7a459ba9149d0b34b112b0.png


现在定义一个宏


#define print_format(num, format) \ 
                        printf("the value of #num is "format)

并且让它输出:


7ed9099390dd428488a6bfb45b8d28ab.png


这时候发现需要正确输入它们的数值:

c581c144f7d74476820bd97ab2822b62.png



这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。

1. 另外一个技巧是:

使用 # , 把一个宏参数变成对应的字符串 。

比如:

0be867952a8b465fa17f2c971eb86fc4.png


代码中的 #value  会预处理器处理为: "value"

## 的作用
## 可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。


注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。(也就是说这个拼接之后的变量名前提是要初始化一个值)

3.2.5 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

例如

x + 1 ; //不带副作用
x ++ ; //带有副作用

MAX 宏可以证明具有副作用的参数所引起的问题

3a67e065305c4cae9bf4ca11a7eddcb0.png


在vscode2019内调试一下:



bd87261357db4662bc15a82ef86f1f88.png

 

相关文章
|
7月前
|
存储 自然语言处理 编译器
『C语言进阶』程序环境和预处理
『C语言进阶』程序环境和预处理
|
存储 自然语言处理 编译器
C语言进阶-程序环境和预处理(1)
C语言进阶-程序环境和预处理
88 0
|
3月前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
7月前
|
存储 自然语言处理 编译器
c语言的程序环境和预处理(一眼丁真)
c语言的程序环境和预处理(一眼丁真)
|
存储 编译器 程序员
程序环境和预处理 - 带你了解底层的的编译原理
程序环境和预处理 - 带你了解底层的的编译原理
107 1
|
7月前
|
编译器 C语言 C++
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)-1
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)-1
|
7月前
|
编译器 Linux C++
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)
|
存储 自然语言处理 程序员
【C语言进阶】程序环境和预处理(上)
【C语言进阶】程序环境和预处理(上)
【C语言进阶】程序环境和预处理(上)
|
存储 编译器 程序员
C语言——程序环境和预处理(再也不用担心会忘记预处理的知识)
C语言——程序环境和预处理(再也不用担心会忘记预处理的知识)
|
自然语言处理 编译器 Go
揭秘Go语言编译黑盒:从源代码到神奇可执行文件的完整过程
揭秘Go语言编译黑盒:从源代码到神奇可执行文件的完整过程
73 0
下一篇
DataWorks