首页 > 代码库 > 宏与内联函数的差异探究----自定义MIN函数引发的错误反省
宏与内联函数的差异探究----自定义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. 带参数的宏定义的参数个数可以超出,但是不能不够,否则可能导致编译错误。
宏与内联函数的差异探究----自定义MIN函数引发的错误反省