2008年3月3日 星期一

不定參數的 C 函式

只要寫過 C 程式的人,都用過 printf 這個函式,也都知道它可是一個
不定參數的函式,了解它的運作方式,你也可以實作自己的不定參數的
函式。
先看一下 printf 的原型:
int printf( const char *format [, argument]... );
在format後面的參數不但不定個數且型態也是不定的。
這些參數是one bye one 的堆在stack裡面。
printf這個函式如何正確的取出這些參數?
秘訣就在 format,函式根據format裡的格式("%d %f...")
依序把堆在stack裡的參數取出。而這取出的動作就是用到
va_arg, va_end, va_start這三個macro再加上va_list。
va_list 事實上是一個 char * 的型態:
typedef char * va_list;
其它的三個macro的例子(不同的平台會有不太一樣的macro)
/* A guess at the proper definitions for other platforms */
#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 )

這三個macro若看得懂,對了解它們的運作會很有幫助;看不太懂也沒關係
,看一下下面的說明也差不可以了。
首先 va_start 把取出的指標(va_list)指到第一個不定參數。
然後就可以用 va_arg 以指定的型態從va_list取出資料並把va_list
指標移到下一個位置(例如取走了一個int 4 bytes 的資料, va_list便會加 4)
當取完資料後便可使用 va_end 把 va_list 歸零(這個macro應看得懂吧)。
其實對大部份的應用來說 va_end 做不做是沒太大的關係的。

下面是一個不定參數函式的範例(轉載自MSDN):
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;

va_start( marker, first ); /* Initialize variable arguments. */
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker ); /* Reset variable arguments. */
return( sum ? (sum / count) : 0 );
}

註:_INTSIZEOF 是 int size memory alignment,如果你不知那是什麼
  就把 _INTSIZEOF 看成這樣的定義會比較容易理解:
#define _INTSIZEOF(n) sizeof(n)
為什麼不直接用sizeof(n)就好了,使用 macro 是為了跨平台時能保持運
作正確。若傳了一個2 bytrs的不定參數(如 short),但在一個 32 bits的
平台推入堆疊就會是4個bytes(32 bits)不是2 bytes,所以參數的取出
就要非常的小心。使用_INTSIZEOF 在於保證引數指標移動的正確。

=================================================
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

這應屬概數問題吧!?
有n個蘋果,無條件進位取x的倍數,會是幾個蘋果?
A. (n+(x-1)) - ((n+(x-1))%x)
如果x是2的m次方(x=1,2,4,8...),那麼上面的A.公式可簡化為:
B. (n+(x-1)) & ~(x-1)
因為 :
令 N=(n+(x-1)) 那麼 A.公式可以說成
N減去N除以x的餘數
如果x是2的幾次方(1,2,4,8...),N減去N除以x的餘數可以寫成:
& ~(x-1)
why?
x=1-->N減去N除以1的餘數
= N & (0xFF..FF)
= N & ~(1-1)

x=2-->N減去N除以2的餘數
= N & (0xFF..FE)
= N & ~(2-1)

x=4-->N減去N除以4的餘數
= N & (0xFF..FC)
= N & ~(4-1)

::::::::::::::::::::::::::::::::::
最後把B.公式以 n代入sizeof(n), x代入sizeof(int)就得到
C. (sizeof(n)+(sizeof(int)-1)) & ~(sizeof(int)-1)
這個解釋是不是老太婆的纏腳布又臭又長?這是為什麼我沒在該篇文章
說明的原因。
_INTSIZEOF(n) 目的是要得到參數n無條件進位取sizeof(int)的倍數。
為什麼是無條件進位取sizeof(int)的倍數?因為這是一個較通用版本的
macro,一般的編譯器的定的int 型態的大小和堆疊存取的單位是相同的
(但不是絕對的,所以才又有特殊版本)

REF:http://ehome.hifly.to/showthread.php?s=&threadid=329

沒有留言: