網站首頁 編程語言 正文
可變參數
可變參數是C語言提供的一種參數可變的機制,咱希望函數帶有可變數量的參數,而不是預定義數量的參數。它允許咱定義一個函數,能根據具體的需求接受可變數量的參數,比如這種:
int Max(int num,...) { va_list arg; va_start(arg,num); int max = va_arg(arg,int); for(int i = 1;i<num;i++) { int sid = va_arg(arg,int); } if(sid > max) { max = sid; } va_end(arg); return max; } int main() { int a = Max(5,1,2,3,4,5); printf("%d\n",a); return 0; }
如上形式Max函數就用到了可變參數,注意!使用可變參數時,Max內首元素 ‘ 5 ’代表元素個數
那么問題來了,如果函數沒有形式參數,可以給函數傳遞嗎?答案是可以的,在C語言中,只要發生了函數調用并調用了參數,必定會形成臨時變量;所謂臨時拷貝(變量)的本質,也就是在棧幀內部形成的(從右向左形成臨時拷貝(變量)).
宏觀過程
va_list定義了可以訪問可變參數部分的變量,他的本質是一個 char 類型指針。va_start 使 b 指向可變參數部分,va_end 是用來完成收尾工作的,本質就是將參數arg置為空,避免野指針。
掐頭去尾,我們看看主體部分。首先 arg 指針先讓我的數據入棧,我們打開反匯編能看到棧頂 esp 位置,再在內存窗口找到 esp 位置,就會看到這個經典的一幕,倒著入棧連著幾個數據入棧是壓在一起的,這種結構對我們查找元素就非常友好了。
宏觀的框架就是我們傳入的變量 num 就代表第一個參數 5,va_start 就是讓 arg 原本指向5的 ,再讓他指向有效部分,比如 1,根據指向 1 的起始地址, va_start 指向他的可變部分(去掉已指向的有效部分),具體如何實現見下文;最后 va_arg 就是根據類型 int ,從起始地址開始連續讀取找到某一個元素,這樣最終會把所需要的 max 的值讀出來。
原理
可變參數列表對應的函數,最終調用也是函數調用,也要形成棧幀,棧幀形成前,臨時變量會先入棧,根據咱之前總結的,參數位置都是相對固定的;在可變參數中 ,如果是短整型,一般都要進行整型提升,比如參數傳入的是 char 類型,但實際傳出的是 int 類型,這就是我們的 va_arg(arg,int)為什么是 int 而不是 char,所以在 va_arg 中指定了錯誤的類型,那結果無法預測。
要注意:
1.可變參數必須從頭到尾逐個進行訪問,如果你訪問了幾個可變參數后想半途而廢,是可以做到的,但如果一開始就想訪問中間某個元素的話,噠咩!
2.參數列表中至少有一個命名參數,如果連一個參數都沒有,就沒辦法使用 va_start;
3.這些宏是沒辦法直接判斷實際存在的參數數量的,也無法判斷每個參數的類型
格局打開
#define _crt_va_start(ap,v) (ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) #define _crt_va_arg(ap,t) (*(t*)((ap += _INTSIZEOF(t) - _INTSIZEOF(t)) #define _crt_va_start(ap) (ap = (va_list)0) )
談完原理就要談原理的原理,可變參數的幾個宏就給出了他的運作原理,ap 就相當于 arg, v 就相當于變量 num,va_list 相當于 char *,這里 ADDRESS 相當于取地址,所以就是在對 char 指針強轉之后,此時就有了一個指針以一字節為單位,指向入棧的第一個有效元素。要想繼續指向后面可變部分,就要繼續向下移動四個字節,加上他本身大小就能移動到可變部分。
第二個宏也是特別有意思,ap是va_arg(arg,int),t 是我們的類型—— int ,括號里的部分:(ap += _INTSIZEOF(t))其中 INTSIZEOF 計算了int 的大小,這里讓 ap 先 += 四個字節,就讓 ap 直接指向了下一個元素的位置,后面再減去 int 的大小讓他又回到了第一個元素
注意減的過程并沒有賦給 ap,ap指向的是 2,而整個表達式指向的是 4,(t) 將這個 char 類型指針強轉成 int 類型指針再解引用,通過強制轉換,提取出符合類型大小的數據。整個過程就是把第一個元素分離出來了。這個設計可謂非常優秀,不僅指針下移了,元素也訪問了,屬實美哉。
end宏很好理解,ap = 0了再強轉成 char* ,他的實際意義就是將指針歸0,避免野指針。
四字節對齊
INTSIZEOF 是如何實現的?我們將 INTSIZEOF 轉到定義,下面這段宏在函數內部就開始進行使用了為什么還要進行四字節對齊呢?因為從首元素到第二個元素中間的空間是可以訪問的,我不限制大小就有可能訪問不到第二個元素,所以在形參被運用時除了發生整型提升還有就是四字節對齊。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1)& ~(sizeof(int) - 1))
比如我是個 char 類型,sizeof(char)+sizeof(4)-1 &~ (sizeof(4)-1)就是 4 &~ 3,0000……0100 & 1111……1100 = 4 , 如此就能實現以最小的方式向上四字節取整,完成四字節對齊。從可讀性上講,這是真的麻煩,我們其實直接寫成(n+4-1)& -(4-1)也無妨,這種簡潔版不香嗎是吧。
原文鏈接:https://blog.csdn.net/qq_61500888/article/details/122213547
相關推薦
- 2022-10-16 Python?結構化字符串中提取數據詳情_python
- 2022-07-16 feign調用傳遞請求頭
- 2022-09-26 Redis?哈希Hash底層數據結構詳解_Redis
- 2022-09-14 Python深入淺出分析enum枚舉類_python
- 2022-12-29 一文詳細談談GoLang的panic和error_Golang
- 2022-08-03 Python?權限控制模塊?Casbin_python
- 2022-03-24 C++關于指針,繼承和多態介紹_C 語言
- 2022-08-15 如何實現響應式(自適應)網頁
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支