宏與內聯函數的差異探究----自定義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. 帶參數的宏定義的參數個數可以超出,但是不能不夠,否則可能導致編譯錯誤。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章