您的位置:首页 > 其它

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

2014-08-28 15:34 281 查看
在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. 带参数的宏定义的参数个数可以超出,但是不能不够,否则可能导致编译错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: