程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 可變參數的函數和可變參數宏

可變參數的函數和可變參數宏

編輯:關於C語言
 

我們對現實生活中很多事情已習以為常,比如娛樂圈的潛規則,中國足球的假球,土的掉渣的printf()。殊不知這背後蘊藏著重重玄機,復雜的機制。現在我們就試著即開printf的神秘面紗。
學習c之後很多年都沒想過printf有那麼多不同之處,可變參數函數的實現一點都不簡單,就像沒有無緣無故的愛,它的實現不是天然的。
先看看,c為可變參函數提供的幾個利器,以宏的形式實現。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

 

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //第一個可選參數地址

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一個參數地址

#define va_end(ap) ( ap = (va_list)0 ) // 將指針置為無效
va_list用於定義一個變量獲取可變參數指針
va_start用於將va_list定義的指針進行初始化
va_arg用於獲取對應指針的真實類型數據
va_end用於清空va_list定義的指針

擒賊先擒王,在這裡只解決va_arg,別的不需懼怕。曾經,我看不懂這個。看懂之後感受到了造物主的偉大。

ap+=_INTSIZEOF(t);這裡就使得ap指向了下一個可變參數的起始地址。然後

ap+=_INTSIZEOF(t)-_INTSIZEOF(t)整個表達式的結果回到當前這個可變參數的起始地址,但是注意到ap已經指向下一個可變參數地址了。這就方便後續的處理 。

我們有必要了解一下C函數的調用規則了,在調用一個函數之前,調用方會將這個函數參數push(修改ESP指針),並且push規則是先push最後一個參數,最後push第一個參數,因此ESP指針最後應該是指向第一個參數。可變參數就是利用了這一點,一旦獲取到第一個參數的地址後,就能夠通過地址向前查找所有的參數。(注意:x86上的堆棧是反向的,push會使ESP的值減少,而不是增加)。

看到這裡,相信大家應該可以編寫一個簡單的可變參數函數了。我還是直接copy一個,呵呵,關鍵時候要的就是穩健,請主寬恕我。


#include<stdio.h>

#include<stdarg.h>

 

void simple_va_fun(int i, ...)

{va_list arg_ptr;

int j=0; va_start(arg_ptr, i);

j=va_arg(arg_ptr, int);

va_end(arg_ptr);

printf("%d %d\n", i, j);

} int main(void)

{

simple_va_fun(100);

simple_va_fun(100,200);

simple_va_fun(100,200,300);

return 0;

 

}
只有第二個是正確的,其實答案可想而知,這跟我們所寫的程序有關。由於沒有類型和參數的檢查,最多也就只能這樣了。那為什麼人家的printf便可以辨識呢??那是因為函數printf是從固定參數format字符串來分析出參數的類型,再調用va_arg的來獲取可變參數的。這下應該理清楚了。

可變參數宏就不說了吧,簡單的畫個妝而已,不過依然很強大,呵呵。

 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved