1. 程式人生 > >Linux kernel 4.x中的min和max巨集

Linux kernel 4.x中的min和max巨集

minmax是兩個很常用的操作,一般都是用巨集實現的,不過想要寫出一個很完善的巨集定義還是要考慮很多問題的,本文就來分析下Linux Kernel中的實現方法。文中僅考慮minmax的結構與其完全相同,只要修改下大於小於號即可。

巨集定義中要將整體和變數都加上括號的意義此處就不多說了,據此我們可以寫出一個最基本的形式:

#define min(a, b) ((a) < (b) ? (a) : (b))

然而這種寫法是有副作用的,考慮min(a++, b)這樣的用法,其展開後的形式為:

((a++) < (b) ? (a++) : (b++))

a<b時,a++

會被執行兩次,這顯然不是我們所希望的,為了解決這一問題,我們可以使用下面這個稍顯複雜的巨集定義

#define min(a, b) ({ \
    typeof(a) __min1__ = (a);  \
    typeof(b) __min2__ = (b);  \
    (void)(&__min1__ == &__min2__);  \
    __min1__ < __min2__ ? __min1__ : __min2__;})

這裡用到了GCC的一個擴充套件特性,形如({ ... })這樣的程式碼塊會被視為一條語句,其計算結果是{ ... }中最後一條語句的計算結果。故上述巨集定義展開後的結果就是第5行返回的結果。注意,這個擴充套件特性不是所有編譯器都有的,如果用VS編譯上述程式碼,是無法通過編譯的。
這個巨集定義中,先根據a, b的型別生成了兩個區域性變數__min1__

__min2__,之後比較其大小,返回較小的一個,這樣就保證了巨集引數只會被執行一次,避免了上述副作用。另外,第4行程式碼其實是沒有實際作用的,其意義在於,若__min1____min2__的型別不同,比較其地址時編譯器會給出一個Warning,這樣可以避免一些潛在的錯誤發生。

以上巨集定義就是網上普遍流傳的Linux Kernel中的實現方法,然而,我實際閱讀了當前4.9版本的Kernel原始碼,發現實際的實現方法要更復雜一些。在引入實際的實現方法前,我們先思考一下以上巨集定義還存在什麼漏洞。

考慮以下程式碼段:

int __min1__ = 10;
int __min2__ = 20
; int min_val = min(__min1__--, __min2__++);

期望得到的結果應該是__min1__ = 9, __min2__ = 21, min_val = 10。然而實際情況是,__min1__ = 10, __min2__ = 20min_val的值則是不確定的。造成這一結果的原因在於輸入引數和巨集定義內部使用的區域性變數重名了,這樣就會導致在巨集定義的語句塊內,外層同名變數的作用域被內層區域性變數的作用域所遮蔽,展開後的程式碼就成了這樣:

int main_val = ({typeof(__min1__--) __min1__ = (__min1__--); /* 省略 */ })

這就類似於int a = a這樣的語句,執行完後a的值是不確定的,且因為展開後__min1__成了巨集體內的區域性變數,__min1__–的自減操作對於外層變數來說也是無效的。

知道問題的原因後解決方法就很清晰了,只要避免重名就可以了,其實上述巨集定義中使用__min1__這樣的名字也是為了避免重名,然而,靠起特殊的名字這種方法不是那麼的優雅,故實際新版的Linux Kernel中使用了編譯器產生的唯一名稱來解決這一問題:

/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)

#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

/*
 * min()/max()/clamp() macros that also do
 * strict type-checking.. See the
 * "unnecessary" pointer comparison.
 */
#define __min(t1, t2, min1, min2, x, y) ({      \
    t1 min1 = (x);                  \
    t2 min2 = (y);                  \
    (void) (&min1 == &min2);            \
    min1 < min2 ? min1 : min2; })
#define min(x, y)                   \
    __min(typeof(x), typeof(y),         \
          __UNIQUE_ID(min1_), __UNIQUE_ID(min2_),   \
          x, y)

可以看到這是一個多重的巨集巢狀結構,主要區別就在於__UNIQUE_ID(min1_)上,__UNIQUE_ID可以生成一個唯一的名字。唯一性是由編譯器提供的__COUNTER__巨集保證的,這也是GCC的一個擴充套件,GCC文件中對其的說明如下:

This macro expands to sequential integral values starting from 0. In conjunction with the ## operator, this provides a convenient means to generate unique identifiers.

簡而言之,__COUNTER__會被展開為一個從0開始的整數,且每次呼叫後其值都會加一,這也就保證了其唯一性。

至於__PASTE巨集這是用來實現兩個token連線的,之所以要兩重巨集定義和巨集的巢狀展開規則有關,可以參考我之前寫的總結文章。呼叫__UNIQUE_ID(min1_)後產生的就是形如__UNIQUE_ID_min1_0這樣的變數名,這就確保了此名稱不會和傳入變數的名稱重複了。當然,我們還是可以通過刻意構造這樣一個特殊名稱來實現衝突的,只是程式是程式設計師自己寫的,相信也沒有程式設計師這麼無聊……故我們只需要保證正常情況下不會發生衝突即可。

相關推薦

Linux kernel 4.xminmax巨集

min和max是兩個很常用的操作,一般都是用巨集實現的,不過想要寫出一個很完善的巨集定義還是要考慮很多問題的,本文就來分析下Linux Kernel中的實現方法。文中僅考慮min,max的結構與其完全相同,只要修改下大於小於號即可。 巨集定義中要將整體和變數都

c++ minmax 函式

包含在c++標準庫中標頭檔案中,在標頭檔案<windows.h>中定義了min,max的巨集,若在包含的同時包含<windows.h>會導致函式無法使用。 <windows.h>提供了_cpp_min等函式來代替min函式的功能。 C++11標準:

Linux Kernel 4 9TCP BBR演算法的科普解釋

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux Kernel 4.9BBR擁塞控制演算法的優勢

本文轉載自《Linux Kernel 4.9 中的 BBR 演算法與之前的 TCP 擁塞控制相比有什麼優勢?》 中國科大 LUG 的 @高一凡 在 LUG HTTP 代理伺服器上部署了 Linux 4.9 的 TCP BBR 擁塞控制演算法。從科大的移動出口到新加坡 D

MySQLminmax查詢優化

MySQL max() 函式的需掃描where條件過濾後的所有行: 在測試環境中重現: 測試版本:Server version:        5.1.58-log MySQL Community Server (GPL) testtable表中的索引 mysq

Visual C++min()max()函式的使用

標準庫在<algorithm>頭中定義了兩個模板函式std::min() 和 std::max()。通常用它可以計算某個值對的最小值和最大值。 可惜在 Visual C++ 無法使用它們,因為沒有定義這些函式模板。原因是名字min和max與<windows.

Appium+Robotframework實現iOS應用的自動化測試-4:OS X安裝啟動RIDE

有兩種方式進行Appium測試: 1. 遠端方式進行Appium測試:在Windows系統中啟動RIDE並執行測試,這種方式的好處是方便,簡單,但在一開始可能因為配置的問題測試跑不起來; 2.本地方式

Linux Mint 安裝 Linux Kernel 4.12(穩定版)

linuxLinus Torvalds 發布了 Linux 內核 4.12。你可以從這裏直接下載相關的 deb 包來安裝。或者,繼續閱讀本文,按下面的步驟安裝新內核。警告:Linux 內核是系統的關鍵元素。在某個硬件設備不正常工作時,可以嘗試執行升級,新的內核可能會解決此問題。 但同樣的,非必須地更新一個新

Hive分析窗體函數之SUM,AVG,MINMAX

align 4.5 版本 bottom pos right adding track mat Hive中提供了非常多的分析函數,用於完畢負責的統計分析。本文先介紹SUM、AVG、MIN、MAX這四個函數。環境信息:Hive版本號為apache-hive-0.14.0-b

Linux Kernel系列一:開篇Kernel啟動概要

mis misc 跳轉 line global 最終 width lin 通過 前言 最近幾個月將Linux Kernel的大概研究了一下,下面需要進行深入詳細的分析。主要將以S3C2440的一塊開發板為硬件實體。大概包括如下內容: 1 bootloader分析,以uboo

HGDB4.3.2在CentOS 6.x安裝解除安裝指導手冊

目錄 文件用途 詳細資訊 相關文件 文件用途 介紹HGDB4.3.2在CentOS 6.8系統上的安裝和解除安裝流程。 詳細資訊 1.安裝版本及注意事項 1.1 安裝版本 作業系統 CentOS 6.8 x86_64

Linux Kernel 0.01 的編譯執行

Linux Kernel 0.01 的編譯和執行 本文操作環境均在 Linux 系統中實現。 ================================================================================== 一、準備工作 下面說明的內容要

Postgresql 另類的minmax

測試表請引數,資料量為一千六百萬. 常規方法 explain (analyze,verbose,costs,buffers,timing) select min(objectid),max(objectid) from test; --Execution time: 0.190 m

HGDB4.1.1在RHEL6.x安裝解除安裝指導手冊

[[email protected] ~]$ cd /upload/hgdb4.1.1_installer_redhat6.x_Standard_64 [[email protected] hgdb4.1.1_installer_redhat6.x_Standard_64]

Linux Kernel 4.20 RC6 釋出

   Linux Kernel 4.20 RC6 釋出了。 Linus 在郵件中指出該版本看起來極其普通,只有不到一半的補丁是關於驅動程式的,包括 gpu、網路、nvdimm、block 和媒體等,其它的是核心網路工具(主要是 bpf 自檢)和文件更新,還有一些檔案系統、核心核

OpenCV3.4.XNonfree模組的使用-SURF為例

作者使用OpenCV3.4.3+VS2015+CMake3.8.2編譯了包含opencv3.4.3以及opencv_contrib的完整的OpenCV庫,因而能夠使用tracking、saliency等非穩定模組。 在後續工作中又需要利用SURF進行實驗,程式碼如下: P

linux kernel 4.18.16 Makefile註釋

前言: 本人的作業系統作業,要求註釋最新版本的GNU Linux kernel 的Makefile檔案,我還沒用過Linux,一開啟這個Makefile真的是下了一跳……好多東西要註釋啊,忙活了一個下午,成果就寫個部落格吧。 參考: Makefile: # S

Netty5.x新增值得注意的點

最近事情多,OneCoder折騰了好幾天,總算翻譯完成了。 該文件會列出在Netty新版本中值得注意變化和新特性列表。幫助你的應用更好的適應新的版本。 不像Netty3.x和4.x之間的變化,5.x沒有那麼大的變化,不過也取得了

聖誕快樂!Linux Kernel 4.20 正式釋出

   在經過數個測試版本後,Linux Kernel 4.20 終於在聖誕前夕迎來了正式版本。Linus Torvalds 在郵件中寫道:“看起來似乎沒有理由再推遲 4.20 版本的釋出,因為大家要準備休息了,聖誕節快樂!” 圖片來自 itsfoss 4.20 版本的更新亮點

linux kernel裝置樹的編譯反編譯

在使用Nvidia TX2平臺時使用到裝置樹的編譯和反編譯命令,記錄如下:1.裝置樹的編譯命令有以下兩種方式:(1)將裝置樹檔案拷貝到核心原始碼的arch/*(處理器平臺)/boot/dts/*(廠家)