编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

C|深入理解库中随处可见的宏(c|深入理解库中随处可见的宏有哪些)

gowuye 2024-04-25 04:45 8 浏览 0 评论

C预处理器在程序编译之前查看程序(故称之为预处理器)。根据程序是的预处理指令,预处理器把符号缩写替换成其表示的内容。预处理器可以包含程序所需的其他文件,可以选择让编译器查看哪些代码。

预处理指令以#开头,到后面的第一个换行符为止。也就是说,指令的长度仅限于一个逻辑行(预处理前,编译器会将多行物理行处理为一个逻辑行)。

编译器在预处理前的翻译处理:

I 编译器把源代码中出现的字符映射到源字符集,包括处理多字节字符和三字符序列。

II 代码物理行换行的处理,编译器定位每个反斜杆后面跟着换行符(按下Enter键在源代码文件中换行所生成的字符,而不是指符号表征\n)的实例,并删除它们,把多个物理行转换成一个逻辑行。因为预处理表达式的长度必须是一个逻辑行(一个逻辑行可以是多个物理行)。

III 编译器把文本划分成预处理记号(token,由空格、制表符或换行符分隔的项)序列、空白序列(一个空格替换所有空白序列,但不包括换行符)和注释序列(用一个空格替换)。

以上三步后,开始进行预处理阶段。

#define一般用来定义宏,做宏替换或头文件的保护性定义,分三类:

宏的名称不允许有空格,遵循C变量的命名规则。

替换体也叫替换列表,一旦预处理器在程序中找到宏的实例后,会用替换体代替该宏。

从宏变成最终替换文本的过程称为宏展开(macro expansion)。

#define PRINTX printf("x is %d\n",x)可以理解为:

define a macro, named PRINTX, replaced by printf("x is %d\n",x)

1 宏定义符号常量

#define指令可以用来定义明示常量(manifest constant,也叫符号常量)

下面是使用宏来定义符号常量的实例:

/* simple preprocessor examples */
#include <stdio.h>
#define TWO 2        /* you can use comments if you like   */
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde" /* a backslash continues a definition to the next line */
#define FOUR  TWO*TWO
#define PRINTX printf("X is %d.\n", x)
#define FMT  "X is %d.\n"

int main(void)
{
    int x = TWO;        /* int x = 2;               */
    
    PRINTX;             /* printf("x is %d\n",x);   */
    x = FOUR;           /* X = TWO*TWO → X = 2*2 只做替换,包括循环替换,不做计算 */
    printf(FMT, x);     /* printf("X is %d.\n", x); */
    printf("%s\n", OW); /* printf("%s\n", 2);       */
    printf("TWO: OW\n");/* 双引号中的字符,即使有定义宏,也不做替换,#宏参数例外*/
    getchar();
    return 0;
}
/*
X is 2.
X is 4.
Consistency is the last refuge of the unimaginative. - Oscar Wilde
TWO: OW
*/

对于常量(或字面量)替换,也可以使用const定义。但用作静态数组大小时,一些编译器并不支持const定义的常量。

那么,何时使用字符常量?对于绝大部分数字常量,应该使用字符常量。如果在算式中使用字符常量代替数字,常量名能更清楚地表达该数字的含义。如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数。如果数字是系统代码(如EOF),用符号常量表示的代码更容易移植(只需要改变EOF的定义)。助记、易更改、可移植,这些都是符号常量很有价值的特性。

2 函数宏(带参数的宏)

函数宏(带参数的宏)的定义和使用看起来都像函数,都使用圆括号,但是两者却完全不同,特别是宏参数和函数参数不完全相同(宏参数替换不做计算,不求值,只替换字符序列),使用函数宏还可能会有一些陷阱。

/* macros with arguments */
#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X)   printf("The result is %d.\n", X)

int main(void)
{
    int x = 5;
    int z;
    
    printf("x = %d\n", x);
    z = SQUARE(x);          
    printf("Evaluating SQUARE(x): ");
    PR(z);
    z = SQUARE(2);
    printf("Evaluating SQUARE(2): ");
    PR(z);
    printf("Evaluating SQUARE(x+2): ");
    PR(SQUARE(x+2));
    printf("Evaluating 100/SQUARE(2): ");
    PR(100/SQUARE(2));
    printf("x is %d.\n", x);
    printf("Evaluating SQUARE(++x): ");
    PR(SQUARE(++x));
    printf("After incrementing, x is %x.\n", x);
    getchar();
    return 0;
}
/*
x = 5
Evaluating SQUARE(x): The result is 25.
Evaluating SQUARE(2): The result is 4.
Evaluating SQUARE(x+2): The result is 17.
Evaluating 100/SQUARE(2): The result is 100.
x is 5.
Evaluating SQUARE(++x): The result is 49.
After incrementing, x is 7.
*/

SQUARE(x+2)展开后:

SQUARE(x+2*x+2),

因为运算符的优先级不一样,所以计算的结果不同于预期。如果是函数参数,会先计算x+2,就不存在优先级的区别。是否有改善的办法呢?我们知道,圆括号可以有最高的优先级,所以需要用足够多的圆括号来确保最高优先级(就像预先做了求值一样),单个参数使用圆括号,整体也使用圆括号:

#define SQUARE(X) ((X)*(X))

但对于SQUARE(++x),展开后是:

++x*++x

x经过了两次自增,并不是期望的一次自增。所以,一般尽量不要做宏中使用自增运算符。

3 用宏参数创建字符串:#运算符

在第一个实例中说到双引号中的字符,即使有定义宏,也不做替换,宏参数例外,C允许在字符串中包含宏参数,但宏参数需以字符#开头,就像特殊的转义字符一样。

看下面的实例:

/* substitute in string */
#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))

int main(void)
{
    int y = 5;
    
    PSQR(y);
    PSQR(2 + 4);
    getchar();
    return 0;
}
/*
The square of y is 25.
The square of 2 + 4 is 36.
*/

3 用宏参数连结字符串:##运算符

与#运算符类似,##运算符可用于类函数宏的替换部分,能够连结字符串,组成一个新的标识符:

// use the ## operator
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void)
{
    int XNAME(1) = 14;  // becomes int x1 = 14;
    int XNAME(2) = 20;  // becomes int x2 = 20;
    int x3 = 30;
    PRINT_XN(1);        // becomes printf("x1 = %d\n", x1);
    PRINT_XN(2);        // becomes printf("x2 = %d\n", x2);
    PRINT_XN(3);        // becomes printf("x3 = %d\n", x3);
	getchar();
    return 0;
}
/*
x1 = 14
x2 = 20
x3 = 30
*/

4 变参宏:...和__VA_ARGS__

stdarg.h提供的可变参数使用的就是宏定义:

typedef char *  va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )

C通过把宏参数列表中最后的参数写成...来实现这一功能,这样,预定义宏可用在替换部分中,表明省略号代表什么。

// variadic.c -- variadic macros
#include <stdio.h>
#include <math.h>
#define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)

int main(void)
{
    double x = 48;
    double y;
    
    y = sqrt(x);
    PR(1, "x = %g\n", x);
    PR(2, "x = %.2f, y = %.4f\n", x, y);
    getchar();
    return 0;
}
/*
Message 1: x = 48                                                                             
Message 2: x = 48.00, y = 6.9282 
*/

5 宏和函数的选择

有些编程任务既可以用带参数的宏完成,也可以用函数完成。各自有适应的一些场合。

使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。

宏和函数的选择实际上是时间和空间的权衡。宏展开产生内联代码,略占空间,但没有普通函数的跳转,时间较省。特别是用在嵌套循环体内时。当然,在C++中,有一种更优的替换方案,就是使用inline内联函数。

C作为一种强类型语言,普通函数需要明确数据类型,而宏不存在这一限制,不用担心变量类型,因为其本身就只是做字符替换,并不做变量类型检查,当然这也是宏的另一种缺陷。

对于一些简单的函数,程序员通常使用宏:

#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define ABS(X) ((X) < 0 ? -(X) : (X))
#define ISSIGN(X) ((X) == '+' || (X) == '-' ? 1 : 0)

6 取消宏定义

#undef指令可以取消宏定义,如:

#define LIMIT 444
#undef LIMIT

7 头文件的保护性定义

#ifndef指令判断后面的标识符是否未定义,可以防止相同的宏被重复定义。但更常用来防止多次(重复)包含一个文件,也就是保护性定义:

#ifndef _HEADERNAME_H_
#define _HEADERNAME_H_
... // 头文件内容
#endif

8 预定义宏

C编译器有一些预定义宏和标识符:

// predef.c -- predefined identifiers
#include <stdio.h>
void why_me();

int main()
{
    printf("The file is %s.\n", __FILE__);
    printf("The date is %s.\n", __DATE__);
    printf("The time is %s.\n", __TIME__);
    printf("The version is %ld.\n", __STDC_VERSION__);
    printf("This is line %d.\n", __LINE__);
    printf("This function is %s\n", __func__);
    why_me();
    getchar();
    return 0;
}

void why_me()
{
    printf("This function is %s\n", __func__);
    printf("This is line %d.\n", __LINE__);
}
/*
This is line 11.                                                                              
This function is main                                                                         
This function is why_me                                                                       
This is line 21. 
*/

9 宏的经典应用

在MFC的消息映射,就是宏的一个经典应用:

#define DECLARE_MESSAGE_MAP() \
private: \
	static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
	static AFX_DATA const AFX_MSGMAP messageMap; \
	static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

#define BEGIN_MESSAGE_MAP(theClass, baseClass) /
      const AFX_MSGMAP* theClass::GetMessageMap() const /
            { return &theClass::messageMap; } /
      AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = /
      { &baseClass::messageMap, &theClass::_messageEntries[0] }; /
      AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = /
      { /

#define END_MESSAGE_MAP() /
            {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } /
      }; /

宏的定义一般放到头文件中,如一些库中就通常包含有宏定义。

宏替换表面看起来有很多缺陷,但在一些库中却用得很普通,如果不是很熟悉的话,一些源代码还真是看得云里雾里的。


-End-

相关推荐

Nginx 响应提速10倍,你需要知道的缓存性能优化——FastCGI调优
Nginx 响应提速10倍,你需要知道的缓存性能优化——FastCGI调优

Nginx缓存优化是帮助大家提升网站性能的重要操作之一,proxy_cache主要用于反向代理时,对后端内容源服务器进行缓存;fastcgi_cache主要用于...

2024-05-20 14:44 gowuye

王者荣耀天魔缭乱和逐梦之音返场活动地址 3月22日开启返场活动
王者荣耀天魔缭乱和逐梦之音返场活动地址 3月22日开启返场活动

王者荣耀官方终于确定了天魔缭乱和逐梦之音的返场活动,这让不少小伙伴乐开了花,返场活动将会在3月22日开启,下面就带来王者荣耀天魔缭乱和逐梦之音返场活动地址!王者...

2024-05-20 14:44 gowuye

常见的嵌入式web服务器有哪些?

嵌入式WEB服务器常见的有:Lighttpd,Shttpd,Thttpd,Boa,Mini_httpd,Appweb,Goahead。Lighttpd地址:http://www.light...

简述几款常见的嵌入式web服务器
简述几款常见的嵌入式web服务器

嵌入式web服务器,是web服务器当中的一种,是基于嵌入式系统而实现的web服务器。指的是在嵌入式系统(通俗点就是单片机系统)上实现的一个web服务器,可以通过...

2024-05-20 14:44 gowuye

教你如何利用fastcgi_cache缓存加速WordPress

在使用nginx缓存之前,必须在nginx里面加载专门的模块,这个模块叫做ngx_cache_purge。添加ngx_cache_purge模块下载ngx_cache_purge模块ngx_cache...

扫描WordPress漏洞

检测已知漏洞WPScan是一款广泛使用的WordPress安全扫描工具,它的一项重要功能是检测已知漏洞。在这篇文章中,我们将深入探讨WPScan如何检测已知漏洞,并结合实际示例,帮助读者更好地理解和应...

消灭 Bug!推荐几个给力的开源 Bug 跟踪工具
消灭 Bug!推荐几个给力的开源 Bug 跟踪工具

在这个充满bug的世界里,最遥远的距离不是生与死,而是你亲手制造的bug就在你眼前,你却怎么都找不到它。因此本文准备了7款优秀的开源bug跟踪系...

2024-05-20 14:43 gowuye

生物信息分析入门全攻略

生物信息学是生命科学研究的重大前沿领域,未来将占据生命科学研究的半壁江山。已经有越来越多的小伙伴投入到生物信息的学习中,但是入门难、深入慢、摸不到方向等都成为持续学习的拦路虎。本文根据生物信息技术大牛...

elkb实践经验,再赠送一套复杂的配置文件
elkb实践经验,再赠送一套复杂的配置文件

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。宝剑锋从磨砺出,梅花香自苦寒来。诗人白居易,三月下江南,看到沿路开放的桃花,心潮澎湃...

2024-05-20 14:43 gowuye

超详细从0到1 搭建ELK监控
超详细从0到1 搭建ELK监控

监控分类?Metrics用于记录可聚合的数据。例如,1、队列的当前深度可被定义为一个度量值,在元素入队或出队时被更新;HTTP请求个数可被定义为一个计数器,...

2024-05-20 14:42 gowuye

嵌入式开发 之Web配置页面开发
嵌入式开发 之Web配置页面开发

1.PHP是最好的语言??开发动态页面首选的语言是PHP,村村不能在这里忽悠人,如果你的硬件性能允许切略懂PHP,看到这里就可以退出了。本文面向的受众是Linu...

2024-05-20 14:42 gowuye

Python开发一个网站目录扫描工具用来检测网站是否有漏洞?
Python开发一个网站目录扫描工具用来检测网站是否有漏洞?

开发一个网站目录扫描工具是用来检测网站是否有非法目录请求的一个常见需求之一,我们要通过这个扫描工具来找到通过某个域名可以访问到的网站路径,可能对于有些系统来讲,...

2024-05-20 14:42 gowuye

创建一个类似Youtube的Id——使用PHP/Python/JS/Java/SQL

id通常都是用数字,不巧的是只有10个数字来使用,所以如果你有很多的记录,id往往变得非常冗长。当然对于计算机来说无所谓,但我们更希望id尽可能短。所以我们如何能使id变短?我们可以利用字母让它们附加...

快速云:有助于移动应用安全开发的五条妙计
快速云:有助于移动应用安全开发的五条妙计

许多企业不断地向其开发团队提供培训。但是某些漏洞,如早在十多年前就发现的SQL注入,如今仍广泛存在于各种应用中。因而,安全培训永不过时。在开发移动应用时,开发者...

2024-05-20 14:41 gowuye

洛杉矶国际电影节最佳动画短片奖影片《G’DAY》正式全网上映
洛杉矶国际电影节最佳动画短片奖影片《G’DAY》正式全网上映

7月2日,由M&CSaatchi创作,由深受好评的澳大利亚导演迈克尔·格雷西执导的动画短片《G’day》,正式在全网上映。该影片因其出色的创意赢得了洛...

2024-05-20 14:41 gowuye

取消回复欢迎 发表评论: