`
xinklabi
  • 浏览: 1586820 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

C语言中宏的一些特别用法

 
阅读更多

转载
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。

关于#和##

在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。比如下面代码中的宏:
#define WARN_IF(EXP)    \
    do{ if (EXP)    \
            fprintf(stderr, "Warning: " #EXP "\n"); }   \
    while(0)
那么实际使用中会出现下面所示的替换过程:
WARN_IF (divider == 0);

	被替换为


do {
    if (divider == 0)
		fprintf(stderr, "Warning" "divider == 0" "\n");
} while(0);
这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:

struct command
{
	char * name;
	void (*function) (void);
};

#define COMMAND(NAME) { NAME, NAME ## _command }

// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:

struct command commands[] = {
	COMMAND(quit),
	COMMAND(help),
	...
}
COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 这里这个语句将展开为:
// 	typedef struct _record_type name_company_position_salary;

关于...的使用

...在C宏中称为Variadic Macro,也就是变参宏。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)

	// 或者

#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定 义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面的宏中我们只能提供第一个参数templt 时,C标准要求我们必须写成:
myprintf(templt,);
的形式。这时的替换过程为:
myprintf("Error!\n",);

	替换为:

	
fprintf(stderr,"Error!\n",);
这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:
myprintf(templt);
而它将会被通过替换变成:
fprintf(stderr,"Error!\n",);
很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:
myprintf(templt);

	被转化为:


fprintf(stderr,templt);
这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

错误的嵌套-Misnesting

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

由操作符优先级引起的问题-Operator Precedence Problem

由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:
#define ceil_div(x, y) (x + y - 1) / y
那么
a = ceil_div( b & c, sizeof(int) );
将被转化为:
a = ( b & c  + sizeof(int) - 1) / sizeof(int);
	// 由于+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:
MY_MACRO(x);
但是如果是下面的情况:
#define MY_MACRO(x) {	\
	/* line 1 */	\
	/* line 2 */	\
	/* line 3 */ }
	
//...

if (condition())
	MY_MACRO(a);
else
	{...}
这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:
#define MY_MACRO(x) do {
	/* line 1 */	\
	/* line 2 */	\
	/* line 3 */ } while(0)
这样只要保证总是使用分号,就不会有任何问题。

Duplication of Side Effects

这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:
#define min(X,Y) ((X) > (Y) ? (Y) : (X))

	//...
	
c = min(a,foo(b));
这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:
#define min(X,Y) ({	\
	typeof (X) x_ = (X);	\
	typeof (Y) y_ = (Y);	\
	(x_ < y_) ? x_ : y_; })
({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

分享到:
评论

相关推荐

    C语言中宏定义"#"和"##"符号的用法

    C语言中宏定义"#"和"##"符号的用法 在C语言中,宏定义是一种非常有用的机制,可以帮助开发者简化代码、提高效率和可读性。其中,"#"和"##"符号是两个非常重要的宏符号,它们可以帮助开发者在宏定义中实现字符串化和...

    C语言中宏定义技巧.doc

    标题:C语言中宏定义技巧 描述:本文深入探讨了C语言中宏定义的各种实用技巧,旨在提升代码的可读性、可移植性和错误预防能力。宏定义在C语言编程中扮演着至关重要的角色,它不仅可以帮助我们避免常见的编程错误,...

    C语言中宏定义&quot;#&quot;和&quot;##&quot;符号的用法.doc

    在C语言中,宏定义是预处理器的一种特性,它允许我们创建代换文本,从而简化代码或实现特定功能。在宏定义中,“#”和“##”是两个特殊的符号,它们各自有不同的用途。 1. “#”(双引号)符号: 这个符号被称为...

    C语言中宏、函数和Enum的优与劣.pdf

    对于C语言初学者而言,理解宏、函数和枚举常量的用法及其优缺点对于提升编程能力有着重要作用。在学习过程中,不仅要学会如何编写代码,更要学会如何编写高效、可读、易维护的代码。同时,通过对这些基础知识点的...

    C语言之宏定义用法

    适于初学者了解C语言中宏定义的用法,其中包含define、tydefine的使用

    C语言中宏定义的妙用方法

    本文将深入探讨C语言中宏定义的一些巧妙应用方法。 首先,宏定义的基本语法是`#define 宏名 值`,其中宏名是自定义的标识符,值可以是常量、表达式甚至是函数调用的语句。宏定义在编译时进行展开,而不是运行时,...

    C语言中宏定义使用的小细节

    本文将深入探讨C语言中宏定义的一些小细节,以及如何在某些情况下避免使用宏定义而选择函数。 首先,我们来看`#pragma`预处理指令。`#pragma`是C语言中的一个特殊指令,它允许程序员向编译器提供特定于编译器的信息...

    C语言高级宏定义技术

    C语言作为一种广泛使用的编程语言,在嵌入式系统、操作系统及高性能应用开发等领域占据着重要地位。宏定义作为C语言的一项重要特性,不仅可以帮助开发者简化代码编写过程,还能够提高程序的可维护性和可移植性。本文...

    如何解决C语言,函数名与宏冲突

    您可能感兴趣的文章:详解C语言中的#define宏定义命令用法简单讲解C语言中宏的定义与使用如何在C语言的宏中使用类型关键字C语言中的内联函数(inline)与宏定义(#define)详细解析C语言中宏定义使用的小细节C语言宏定义...

    单片机C语言中变量的定义方法解析.docx

    #### C语言中宏定义的方法 宏定义是C语言中的一个重要特性,它可以帮助简化代码并提高程序的可读性和可维护性: ```c #define push { \ m_acc = _acc; \ m_status = _status; \ m_mp0 = _mp0; \ m_mp1 = _mp1;...

    带参宏替换计算给定年份的二月天数_C语言_带参宏替换计算给定年份的二月天数_chapterbgn_

    通过学习这个例子,初学者可以理解C语言中宏的用法,以及如何结合宏来解决实际问题。同时,也能了解到基本的闰年判断算法,这在编程实践中是非常常见的。此外,还会接触到Visual Studio的项目文件结构,这对于使用...

    20个C语言中常用宏定义总结

    ### 20个C语言中常用宏...通过以上宏定义,我们可以看到C语言中宏的应用非常广泛,不仅能够简化代码,提高程序的可读性和可维护性,还能增强代码的灵活性和兼容性。掌握这些宏定义对于编写高效、可靠的C程序至关重要。

    C语言宏定义[归类].pdf

    以下是对C语言中宏定义的一些关键知识点的详细解释: 1. **防止头文件重复包含**: 使用`#ifndef`、`#define`和`#endif`创建的条件编译块可以防止头文件被多次包含,避免产生编译错误。例如,`#ifndef COMDEF_H`会...

    (1)2011年计算机二级C语言考试(试题及答案详解).pdf

    例如,题目中未给出具体的宏定义代码,但在C语言中宏定义通常用来定义常量或者简化复杂的表达式。 ### 指针的高级用法 1. **指针与数组的相互转换**:在C语言中,数组名可以视为指针,指针也可以指向数组元素。在...

    C语言数据结构试卷

    - 题目7中宏定义`#define CIR(r) r*r`用于计算半径r的圆面积,但在实际使用时,由于没有括号包裹表达式,导致计算结果不符合预期。正确答案为`7`,因为宏展开后的表达式为`(1+b)*b`。 ### 8. 数据结构的概念 **...

    零基础学FPGA(五)Verilog语法基础(下)

    文章最后提到了编译预处理`define,这是类似于C语言中宏定义的语法,允许定义常量和宏指令,可以在代码中重复使用,简化代码的编写和管理。 整体来看,本文对Verilog语法基础的下半部分进行了详细讲解,尤其强调了...

    DSP中CSL头文件中宏中#和##的用法介绍

    在数字信号处理(DSP)领域,常常需要使用到C语言扩展的预处理器宏来实现代码的复用和灵活配置。在CSL(Control/Status Registers Library)头文件中,我们经常会遇到宏定义中使用到的`#`和`##`运算符。这两个运算符...

    宏定义#define用法总结

    C语言中宏定义#define用法总结 在 C 语言中,宏定义(#define)是一种非常重要的预处理器指令,它可以让我们在编译前对代码进行修改和扩展。本文将对宏定义的用法进行总结,包括简单的宏定义、带参数的宏、宏的特殊...

Global site tag (gtag.js) - Google Analytics