宏与内联函数的差异探究----自定义MIN函数引发的错误反省

在C++编程中,函数(包括内联函数)一般都是小写,而宏定义的“函数”(带参数的宏)往往采用大写。

上面这句话,看似稀松平常,但是不遵循这句话却容易导致意想不到的错误!今天就记录一个典型案例:

由于内联函数和宏十分相似,都是在程序运行之前进行的,都是用函数体取代表达式,都可以规避函数调用带来的开销从而提高效率,因此很容易模糊二者的本质区别,以至于忘记本文开头的话。这不,今天我就这么做了。这样做固然不符合编程的规范,然而并非一定会导致错误,除非内联函数名和带参数的宏重名,这时如果函数形参没有报错的话,将会导致难以排查的错误!下面,我详细说明这个实例。

1. 宏与内联函数的先后顺序

一开始,我自定义了一个类A,通过测试A确定是没有问题的,后来在扩展A类时,其成员函数需要调用另一个类B,程序没有报错,但是计算结果却明显出错了。通过调试,最后把问题锁定在了这个问题:A类定义中包含了B类的头文件,而B类的头文件中包含了OpenCV相关的两个头文件,也使用了cv的命名空间:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;

真是奇怪了,我猜想难道是命名空间冲突的导致的?不对,这里程序并没有报错的。因此只可能是两个头文件里的函数或变量名与A类中的函数或变量名冲突了,但是按理说,如果同名的话,一定是优先调用我自定义的函数的啊!现在唯一可能的错误原因就是A类的成员函数中使用了某个函数或变量名与OpenCV中的函数或变量名在运行之前就已经冲突,即在预编译时或编译时名称冲突了。

仔细检查代码,我发现在A类中我自定义了一个内联函数:

	inline float MIN(float a, float b, float c, float d)
	{
		float t1,t2;
		t1 = a<b?a:b;
		t2 = c<d?c:d;
		return t1<t2?t1:t2;
	}

而B类所包含的的imgproc.hpp和highgui.hpp中都有如下语句

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/types_c.h"

而在types_c.h中我又找到了:

#ifndef MIN
#  define MIN(a,b)  ((a) > (b) ? (b) : (a))
#endif

因此,错误的原因是:OpenCV对MIN进行了宏定义,而程序在运行之前调用了OpenCV的宏对该表达式进行了替换,而没有用我自定义的内联函数进行展开。

为什么会这样?其实很简单,宏定义是在预编译时进行的,而内联函数是在编译时进行的,当重名时,当然是使用宏定义进行替换了!

当然,如果一开始使用宏来自定义MIN,那么也不会导致这个错误。总之,都是内联函数使用大写造成的问题。

2. 带参数的宏的参数个数问题

这里还有一个疑问,既然预编译时利用OpenCV的MIN替换了我的表达式,那么我传递了4个参数而不是宏定义中的2个参数,为什么仍然可以计算结果?答曰,VS2005这个编译器确实可以做到这一点,但是如果只传递一个参数,就会报错。有实验为证:

#define MINTEST(a,b) (a<b?a:b)
void main()
{
	float a = MINTEST(12,9,8);
	cout<<a<<endl;
}

输出结果:9

#define MINTEST(a,b) (a<b?a:b)
void main()
{
	float a = MINTEST(12);
	cout<<a<<endl;
}

编译错误提示:error C2059: syntax error : ‘?‘

可见,预编译器在宏替换时,按顺序提取参数进行替换,如果超出则忽略后面的参数,而如果参数不够,由于替换后的表达式有误,因此导致编译错误。

3. 总结

1. 遵循命名规范,不给内联函数使用大写是很有意义的。

2. 带参数的宏定义的参数个数可以超出,但是不能不够,否则可能导致编译错误。

时间: 2024-10-10 00:41:33

宏与内联函数的差异探究----自定义MIN函数引发的错误反省的相关文章

宏与内联函数

第一部分:宏 为什么要使用宏呢? 因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方.这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行.因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率.而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率. 但是宏也有很多的不尽人意的地方. 在C语言中: 1.宏容易出现一

C语言(C++)宏、内联函数、函数的区别和使用情形总结

(1)参数传递:宏可以很方便的继承之前代码的变量,函数,内联函数都要靠传参和全局变量 (2)代码生成 宏和内联函数生成时候会替换,没有函数调用时的压栈,因此执行效率会比函数高一些,没有栈溢出的风险,但会生成更多的代码占用更多空间. (3)函数和内联函数要改变输入的参数必须用引用或指针 关于宏 #define STR(str) #str 表示把str加上"" #define STR(str) x##str 表示把str连接上x 已经特殊的符号不能作为宏的参数.

C/C++之宏、内联函数和普通函数的区别

内联函数的执行过程与带参数宏定义很相似,但参数的处理不同.带参数的宏定义并不对参数进行运算,而是直接替换:内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算求值,然后把表达式的值传递给形式参数. 内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定:而带参数宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患. 使用内联函数时,应注意以下问题:     1)内联函数的定义性声明应

C99语法之可变参宏和内联函数

可变参宏: 1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define MYPRINT(...) printf(__VA_ARGS__) 5 6 int main(int argc, char **argv) 7 { 8 MYPRINT("%d,%s", 10, "hello china"); 9 getchar(); 10 return 0; 11 } 使用 ... 来指明多参,使用宏 __A_

MATLAB学习笔记(2):求矩阵最大值max函数,求矩阵最小值min函数

举例用的矩阵A: A=[1 3 5; 0 4 6; 3 4 0] 一.格式1 B=min(A):获得矩阵A每一列的最小值,返回值B为一个行向量,其第i列对应A矩阵第i列的最小值. C=max(A) :获得矩阵A每一列的最大值,返回值C为一个行向量,其第i列对应A矩阵第i列的最大值. 二.格式2 [B,index]=min(A):返回行向量B和index,B向量记录A的每列的最大值,index向量记录每列最大值的行号. [C,index]=max(A):返回行向量C和index,C向量记录A的每列

内联函数和宏比较

函数内联用内联取代宏代码----------------C++ 语言支持函数内联,其目的是为了提高函数的执行效率(速度).在C程序中,可以用宏代码提高执行效率.宏代码本身不是函数,但使用起来象函数.预处理器用复制宏代码的方式代替函数调用,省去了参数压栈.生成汇编语言的CALL调用.返回参数.执行return等过程,从而提高了速度. 使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应. 对于C++ 而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员. 让我们看看

深入探讨 内联函数和宏定义的区别

内联函数的执行过程与带参数宏定义很相似,但参数的处理不同.带参数的宏定义并不对参数进行运算,而是直接替换:内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算求值,然后把表达式的值传递给形式参数. 内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定:而带参数宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患. 使用内联函数时,应注意以下问题: 1)内联函数的定义性声明应该出现在

嵌入式C语言自我修养 10:内联函数探究

10.1 属性声明:noinline & always_inline 这一节,接着讲 attribute 属性声明,attribute可以说是 GNU C 最大的特色.我们接下来继续讲一下跟内联函数相关的两个属性:noinline 和 always_inline.这两个属性的用途是告诉编译器:编译时,对我们指定的函数内联展开或不展开.它们的使用方法如下. static inline __attribute__((noinline)) int func(); static inline __att

内联函数详解

什么是内联性和外联函数 类的成员函数可以分为内联函数和外联函数.内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内.而说明在类体内,定义在类体外的成员函数叫外联函数.外联函数的函数体在类的实现部分. 内联函数在调用时不是像一般的函数那样要转去执行被调用函数的函数体,执行完成后再转回调用函数中,执行其后语句,而是在调用函数处用内联函数体的代码来替换,这样将会节省调用开销,提高运行速度. 内联函数与前面讲过的带参数的宏定义进行一下比较,它们的代码效率是一样的,但是内联函数要优于宏定义