宏与内联函数的差异探究----自定义MIN函数引发的错误反省
2014-08-28 15:34
281 查看
在C++编程中,函数(包括内联函数)一般都是小写,而宏定义的“函数”(带参数的宏)往往采用大写。
上面这句话,看似稀松平常,但是不遵循这句话却容易导致意想不到的错误!今天就记录一个典型案例:
由于内联函数和宏十分相似,都是在程序运行之前进行的,都是用函数体取代表达式,都可以规避函数调用带来的开销从而提高效率,因此很容易模糊二者的本质区别,以至于忘记本文开头的话。这不,今天我就这么做了。这样做固然不符合编程的规范,然而并非一定会导致错误,除非内联函数名和带参数的宏重名,这时如果函数形参没有报错的话,将会导致难以排查的错误!下面,我详细说明这个实例。
真是奇怪了,我猜想难道是命名空间冲突的导致的?不对,这里程序并没有报错的。因此只可能是两个头文件里的函数或变量名与A类中的函数或变量名冲突了,但是按理说,如果同名的话,一定是优先调用我自定义的函数的啊!现在唯一可能的错误原因就是A类的成员函数中使用了某个函数或变量名与OpenCV中的函数或变量名在运行之前就已经冲突,即在预编译时或编译时名称冲突了。
仔细检查代码,我发现在A类中我自定义了一个内联函数:
而B类所包含的的imgproc.hpp和highgui.hpp中都有如下语句
为什么会这样?其实很简单,宏定义是在预编译时进行的,而内联函数是在编译时进行的,当重名时,当然是使用宏定义进行替换了!
当然,如果一开始使用宏来自定义MIN,那么也不会导致这个错误。总之,都是内联函数使用大写造成的问题。
编译错误提示:error C2059: syntax error : '?'
可见,预编译器在宏替换时,按顺序提取参数进行替换,如果超出则忽略后面的参数,而如果参数不够,由于替换后的表达式有误,因此导致编译错误。
2. 带参数的宏定义的参数个数可以超出,但是不能不够,否则可能导致编译错误。
上面这句话,看似稀松平常,但是不遵循这句话却容易导致意想不到的错误!今天就记录一个典型案例:
由于内联函数和宏十分相似,都是在程序运行之前进行的,都是用函数体取代表达式,都可以规避函数调用带来的开销从而提高效率,因此很容易模糊二者的本质区别,以至于忘记本文开头的话。这不,今天我就这么做了。这样做固然不符合编程的规范,然而并非一定会导致错误,除非内联函数名和带参数的宏重名,这时如果函数形参没有报错的话,将会导致难以排查的错误!下面,我详细说明这个实例。
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. 带参数的宏定义的参数个数可以超出,但是不能不够,否则可能导致编译错误。
相关文章推荐
- 关于MySQL desc关键字误用为自定义属性引发的错误
- Android填坑之旅(第十四篇)关于软键盘弹出未及时隐藏导致自定义View的onMeasure方法测量错误引发的血案
- 关于MySQL desc关键字误用为自定义属性引发的错误
- 关于MySQL desc关键字误用为自定义属性引发的错误
- assembly打包插件引发的自定义spring标签找不到声明的错误
- IE中自定义标签使用自封闭格式引发错误!
- 自定义错误页面
- 自定义复合控件[2]-引发缓存事件
- 关于诸如window.comfirm()等脚本系统函数和html页面自定义函数重名的错误陷阱的说明
- 操作DropDownList时引发的不能多次设置的错误
- C#WinForm下自定义错误消息
- 错误处理: 从托管的 COM+ 服务器应用中抛出自定义异常类型
- 错误类型自定义
- validators里自定义错误信息
- 一个关于C++ Inline关键字的引发的一个错误
- VC6路径设置错误引发的无法调试的奇怪问题
- 自定义SQL错误提示
- 解决ASP.NET创建的线程的用户改变引发的"拒绝访问"错误
- HOW TO:使用 Visual Basic .NET 在 ASP.NET 中创建自定义的错误报告页
- 用err.raise自定义错误信息