天天看點

【C語言進階剖析】46、函數與宏分析

1 函數與宏

1.1 宏的副作用

1.2 宏的妙用

2 小結

看下面這兩段代碼有什麼差別與聯系。

【C語言進階剖析】46、函數與宏分析

上面這兩段代碼的功能都是将指針 p 所指的數組元素置為 0,一個是用宏實作的,一個用函數實作的。

宏與函數的差別:

宏是由預處理期直接替換展開的,編譯器不知道宏的存在,函數是由編譯器直接編譯的實體,調用行為由編譯器決定

多次使用宏會導緻最終可執行程式的體積增大

函數是跳轉執行的,記憶體中隻有一份函數體存在

宏的效率比函數高,因為宏是直接展開,無調用開銷

函數調用時會建立活動記錄,有入棧出棧操作,效率不如宏

執行個體分析:

上面的代碼很簡單,就是将數組 array 中每個位元組的資料都置為0,然後列印數組元素。将第 17 行的 reset(array, len); 換成 reset(array, len); 效果是一樣的。

如果将改成 reset(5, len); 重新編譯運作:

編譯器沒有給出任何警告,位址為 5 的空間是不能通路的,是以運作結果為段錯誤,由于宏定義沒有參數的類型檢查,是以編譯時編譯器沒有給出任何編譯或者警告。

将第 17 行改成 reset(5, len); 重新編譯:

編譯器給出了警告。

宏沒有類型檢查,函數有類型檢查,是以函數比宏更加安全。

宏的效率比函數高,但是其副作用巨大

宏時文本替換,參數無法進行類型檢查

可以用函數完成的功能絕對不用宏

宏的定義中不能出現遞歸定義

執行個體分析:宏的副作用

上面的代碼從字面意思分析,第一條列印語句 _mul_(_add_(1, 2), _add_(3, 4)) 想求解的是(1+2)*(3+4),結果應該是 21

第二條列印語句 _min_(i++, j) 想求解的是,i++ 和 j 的最小值,結果應該是 1。

下面編譯運作:

可以看到結果和我們想象的不一樣,下面單步編譯一下:

打開檔案 46-2.i

可以看到宏定義直接被文本替換了, _mul_(_add_(1, 2), _add_(3, 4)) 被替換為 1 + 2 * 3 + 4,結果為 11。

_min_(i++, j) 被替換為 (i++) < (j) ? (i++) : (j),結果為 2。

上面使用函數絕對不會出現這樣的情況,這就是宏定義的副作用,使用時要注意文本直接展開之後可能會出現和最初定義的含義不一樣的情況。

使用宏可以封裝函數,可以将參數的類型作為宏參數,這是函數辦不到的

下面看一個執行個體分析:

第 4 行将參數類型作為宏的參數,我們可以使用這個宏申請不同參數類型的空間。第 5 行在釋放空間時,同時将将指針置為 null,避免了野指針

函數第 7 行到第 11 行,将變量的名稱列印出來,同時列印變量的值。

第13 行到 15 行提供了一種周遊的方式,看不懂沒關系,後面将展開講解

第 18 行将 int 作為參數,申請了 5 個 int 空間。

第 20,21 行将變量的名稱及變量的值列印出來。

第 22 至 25 行,對申請的空間周遊,并指派

第 27 至 31 行,周遊取出數組中的值并連通變量名列印出來。

編譯運作結果如下:

對上面的代碼是不是還是不太明白呢,下面我們将第 2,3 行注釋(為了避免不必要的資訊),單步編譯:

打開檔案 46-3.i,如下:

可以看到,語句 log_string(str); 被展開成 printf("%s = %s\n", “str”, str);

第 22 至 25 行展開後如下所示:

第 27 至 31 行同理,展開之後和我們平時寫的代碼完全一樣,隻不過通過宏使得代碼變得簡潔了。

1、宏和函數并不是競争對手

2、宏能接受任何類型的參數(包括參數的類型),效率高,容易出錯

3、函數的參數必須時固定類型,效率低,不易出錯

4、宏可以實作函數不能實作的功能