在C及C++语言中允许用一个标识符来表示一个字符串,称为宏,该字符串可以是常数、表达式、格式串等。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。若字符串是表达式,我们称之为函数式宏定义,那函数式宏定义与普通函数有什么区别呢?我们以下面两行代码为例,展开描述:
函数式宏定义:#define MAX(a,b) ((a)>(b)?(a):(b))
普通函数 : MAX(a,b) { return a>b?a:b;}
(1)函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。
(2)调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。
如果MAX是个普通函数,那么它的函数体return a > b ? a : b; 要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。
(3)函数式宏定义要注意格式,尤其是括号。
如果上面的函数式宏定义写成 #define MAX(a, b) (a>b?a:b),省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层括号也是不能省的。若函数中是宏替换为 ++MAX(a,b),则宏展开就成了 ++(a)>(b)?(a):(b),运算优先级也是错了。
(4)若函数参数为表达式,则普通函数的调用与函数式宏定义的替换过程是不一样的。
普通函数调用时先求实参表达式的值再传给形参,如果实参表达式有Side Effect,那么这些SideEffect只发生一次。例如MAX(++a, ++b),如果MAX是普通函数,a和b只增加一次。但如果MAX函数式宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。所以若参数是表达式,替换函数式宏定义时一定要仔细看好。
(5)函数式宏定义往往会导致较低的代码执行效率。
看下面一段代码:
int a[]={9,3,5,2,1,0,8,7,6,4};
int max(n)
{
return n==0?a[0]:MAX(a[n],max(n-1));
}
int main()
{
max(9);
return 0;
}
若是普通函数,则通过递归,可取的最大值,时间复杂度为O(n)。但若是函数式宏定义,则宏展开为( a[n]>max(n-1)?a[n]:max(n-1) ),其中max(n-1)被调用了两遍,这样依此递归下去,时间复杂度会很高。
尽管函数式宏定义和普通函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。
(http://www.cnblogs.com/dong008259/archive/2011/12/27/2302625.html)
分享到:
相关推荐
若字符串是表达式,我们称之为函数式宏定义,那函数式宏定义与普通函数有什么区别呢?我们以下面两行代码为例,展开描述:函数式宏定义:#define MAX(a,b) ((a)>(b)?(a):(b))普通函数 :MAX(a,b) { return a>b?a:b;}...
7. **变长参数(Variable Length Argument List)**:使用`...`来表示函数可以接受任意数量的参数,通常与`va_list`, `va_start`, `va_arg`和`va_end`宏一起使用,但这种做法不常见,且不利于编译器进行类型检查。...
本资源提供了C和C++经典面试题,涵盖了变量声明和定义、bool、int、float、指针变量与“零值”比较、sizeof和strlen的区别、static关键字、malloc和new的区别、宏定义、volatile关键字、指针和数组等多个知识点。...
7. **宏和元编程**:CPS的语法特性使得编写元编程工具变得容易,例如,可以编写宏来自动转换普通函数调用为CPS形式,以简化编程工作。 8. **与生成器和协程的关联**:虽然CPS本身不是生成器或协程,但它提供了创建...
本文总结了50个C语言C++常见面试题及答案,涵盖了变量声明和定义、bool、int、float、指针变量的比较、sizeof和strlen的区别、static关键字的使用、malloc和new的区别、宏定义的使用、volatile指针的使用等多方面的...
7. 宏定义与计算:宏定义中的参数替换和表达式计算。 这些题目全面地考核了考生对C语言基础知识、程序设计能力和计算机系统理解。对于备考的考生来说,不仅需要掌握C语言的语法和编程技巧,还要熟悉计算机系统的...
* 宏定义:宏定义是C语言中的一种预编译指令,我们可以使用宏定义来定义常量和函数。 * 自定义类型:自定义类型是C语言中的一种数据类型,我们可以使用自定义类型来定义城市之间的关系,包括距离、权值等。 * 函数的...
`gmock`是一个用于C++的模拟库,它可以让你定义期望的行为,如函数调用的次数、返回值或副作用。这对于测试那些依赖于难以或不可控外部因素的函数特别有用。通过`ON_CALL`和`EXPECT_CALL`,你可以指定模拟对象的行为...
- 特殊形式不遵循普通函数调用规则,如 `if` 在评估时需要根据条件分支执行不同的表达式。 7. **扩展性与宏** - Lisp 通过宏系统允许用户定义新的语法,这是其强大之处。虽然初级版本可能不包含宏支持,但理解宏...
目前从事软件需求工程、网络协议验证形式化方法以及函数式语言等方面的研究。 译者序: 我们愿意向广大的读者推荐W. Richard Stevens关于TCP/IP的经典著作(共3卷)的中译本。本书是其中的第2卷:《TCP/IP详解 卷2:...
2. 避免使用`#define`宏来创建函数式宏,因为它们可能会引入副作用和未预期的行为。例如,如果宏包含副作用或依赖于函数调用的顺序,这可能导致难以调试的问题。更好的做法是使用内联函数(`inline`)。 3. 当定义如...
- **配置头文件**: Autoconf可以自动创建配置头文件`config.h`,其中包含预处理器宏定义,用于在源代码中根据编译时的配置条件来启用或禁用某些特性。 - **对程序的选择**: Autoconf可以检测系统上是否安装了特定的...
函数是C++中的基本代码组织单位,分为普通函数、内联函数、带默认参数值的函数、重载函数以及函数模板。函数模板支持编译时的泛型编程,提供了代码复用的一种手段。 6. 类与对象 类是C++的核心概念之一,是一个包含...
此代码段展示了实验中使用的部分头文件和宏定义,如MAX表示保留字符的最大长度,MAXSIZE用于保留字和特殊符号表的大小,而NUM则是标识符和常数的数量上限。这些定义为后续的词法分析提供了必要的数据结构和参数。 ...