簡體   English   中英

何時在C中使用類似函數的宏

[英]When to use function-like macros in C

我今天晚上正在閱讀一些用C語言編寫的代碼,文件頂部是類似函數的宏HASH:

#define HASH(fp) (((unsigned long)fp)%NHASH)

這讓我想知道,為什么有人會選擇使用類似函數的宏來實現這種函數,而不是將它作為常規的vanilla C函數實現? 每種實施的優缺點是什么?

謝謝你!

這樣的宏可以避免函數調用的開銷。

看起來似乎並不多。 但在您的示例中,宏將變為1-2個機器語言指令,具體取決於您的CPU:

  • 獲取fp的內存值並將其放入寄存器中
  • 取寄存器中的值,按固定值進行模數(%)計算,並將其保留在同一寄存器中

而功能相當的將是更多的機器語言指令,通常是類似的

  • 將fp的值粘貼在堆棧上
  • 調用該函數,該函數還將下一個(返回)地址放在堆棧上
  • 也許在函數內部構建一個堆棧框架,具體取決於CPU架構和ABI約定
  • 從堆棧中獲取fp的值並將其放入寄存器中
  • 取寄存器中的值,按固定值進行模數(%)計算,並將其保留在同一寄存器中
  • 也許從寄存器中獲取值並將其放回堆棧,具體取決於CPU和ABI
  • 如果構建了堆棧框架,請將其展開
  • 將返回地址彈出堆棧並繼續執行指令

還有更多代碼,嗯? 如果你正在做一些事情,比如在GUI中的窗口中渲染成千上萬個像素中的每一個像素,那么如果使用宏,事情就會快得多。

就個人而言,我更喜歡使用C ++內聯作為更具可讀性和更不容易出錯的內容,但內聯也更多地是對編譯器的暗示,它不必采取。 預處理器宏是編譯器無法爭辯的大錘。

基於宏的實現的一個重要優點是它不依賴於任何具體的參數類型。 C語言中的函數式宏在很多方面都是C ++中的模板函數(C ++中的模板誕生為“更文明”的宏,BTW)。 在這種特殊情況下,宏的參數沒有具體類型。 它可能絕對是可轉換為unsigned long類型的任何東西。 例如,如果用戶如此滿意(並且如果他們願意接受實現定義的結果),他們可以將指針類型傳遞給此宏。

無論如何,我不得不承認這個宏不是宏的類型獨立靈活性的最好例子,但總的來說,靈活性經常被派上用場。 同樣,當某個功能由某個功能實現時,它僅限於特定的參數類型。 在許多情況下,為了將類似的操作應用於不同的類型,有必要提供具有不同類型的參數的幾個函數(和不同的名稱,因為這是C),而同樣可以通過僅一個類似函數的宏來完成。 例如,宏

#define ABS(x) ((x) >= 0 ? (x) : -(x))

適用於所有算術類型,而基於功能的實現必須提供相當多的算法類型(我暗示標准的abslabsllabsfabs )。 (是的,我知道傳統上提到的這種宏的危險。)

宏不是完美的,但是關於“由於內聯函數而不再需要類似函數的宏”的流行格言只是簡單的無稽之談。 為了完全替換類似函數的宏,C將需要函數模板(如在C ++中)或至少需要函數重載(如在C ++中那樣)。 如果沒有那個類似函數的宏,並且仍將是C中非常有用的主流工具。

一方面,宏是壞的,因為它們是由預處理器完成的,它不了解語言並進行文本替換。 他們通常有很多限制。 我不能在上面看到一個,但通常宏是丑陋的解決方案。

另一方面,它們有時甚至比static inline方法更快。 我正在大力優化一個簡短的程序,發現與宏相比,調用static inline方法需要大約兩倍的時間(只是開銷,而不是實際的函數體)。

人們使用宏(在“普通舊C”中)給出的最常見(也是最常見的錯誤)原因是效率論證。 如果您實際上已經分析了代碼並且正在優化真正的瓶頸(或者正在編寫可能有一天可能成為某個人的瓶頸的庫函數),那么使用它們來提高效率是很好的。 但是大多數堅持使用它們的人並沒有真正分析任何東西,只是在沒有任何好處的情況下制造混亂。

宏也可以用於常規C語言不具備的一些方便的搜索和替換類型替換。

我在維護宏濫用者編寫的代碼時遇到的一些問題是,宏看起來很像函數,但是沒有出現在符號表中,所以試圖將它們追溯到它們在龐大的代碼集中的起源可能會非常煩人(其中這個東西定義了嗎?!)。 在ALL CAPS中編寫宏顯然對未來的讀者有所幫助。

如果它們不僅僅是相當簡單的替換,如果您必須使用調試器逐步跟蹤它們,它們也會產生一些混淆。

你的例子根本不是一個功能,

#define HASH(fp) (((unsigned long)fp)%NHASH)
//  this is a cast ^^^^^^^^^^^^^^^
//  this is your value 'fp'       ^^
//  this is a MOD operation          ^^^^^^

我認為,這只是一種編寫更具可讀性的代碼的方法,其中轉換和mod操作包含在單個宏“ HASH(fp) ”中


現在,如果您決定為此編寫一個函數,它可能看起來像,

int hashThis(int fp)
{
  return ((fp)%NHASH);
}

對於一個函數來說相當難以理解,

  • 介紹一個呼叫點
  • 介紹了調用堆棧的設置和恢復

C預處理器可用於創建內聯函數。 在您的示例中,代碼似乎調用函數HASH,而只是內聯代碼。

當C ++引入內聯函數時,消除了執行宏函數的好處。 許多較舊的API(如MFC和ATL)仍然使用宏函數來執行預處理器技巧,但它只會使代碼變得復雜且難以閱讀。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM