這是一個使用可變函數參數的樣本程式,如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <Stdarg.h>
typedef char * va_list;
//#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 )
void va_start
(char * var_arg, int n_values)
{
int i = (sizeof(n_values) + sizeof(int) - 1) & ~(sizeof(int) - 1);
var_arg = (char*)&n_values + i;
}
template <class T>
T va_arg(char* var_arg, T arg_kind)
{
int i = (sizeof(T) + sizeof(int) - 1) & ~(sizeof(int) - 1);
return *(T *)((var_arg += i) - i);
}
void va_end(char* var_arg)
{
var_arg = (char*)0;
}
float
average( int n_values, ... )
{
char *var_arg;
intcount;
floatsum = 0;
int int_kind;
/*
** Prepare to access the variable arguments.
*/
va_start
( var_arg, n_values );
/*
** Add the values from the variable argument list.
*/
for( count = 0; count < n_values; count += 1 )
{
sum += va_arg( var_arg, int_kind );
}
/*
** Done processing variable arguments.
*/
va_end( var_arg );
return sum / n_values;
}
void main( void )
{
float a = average(2, 1, 3);
printf("a=%f/n", a);
return ;
}
我們知道使用可變參數的函數需要包含Stdarg.h檔案,在X86平台下主要是使用了Stdarg.h檔案的如下部分(將它們直接拷貝到程式中可以省略對Stdarg.h檔案的包含):
typedef char * va_list;
#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 )
為了理解這些宏定義,我在樣本程式中將它們注釋掉,並且添加了三個函數(這樣可以進行跟蹤調試,宏定義是不行的):
void va_start
(char * var_arg, int n_values);
T va_arg(char* var_arg, T arg_kind);
void va_end(char* var_arg);
這個樣本程式能夠編譯通過,但是運行時發生錯誤。
那位幫我看看到底是什麼原因?或者幫我解釋一下那些宏定義,謝謝了!
可變參哦,呵呵!
va_start
指定第一個參數的位置
va_arg獲得參數的值(需要指定類型)
va_end結束可變參數的擷取
可變參數
像printf 輸出函數是可變參函數一樣,在函數實現裡面會用到
va_start
指定第一個參數的位置
va_arg獲得參數的值(需要指定類型)
va_end結束可變參數的擷取
va_start
指定第一個參數的位置
va_arg獲得參數的值(需要指定類型)
va_end結束可變參數的擷取
謝謝樓上的,這些我都知道。我想問的是:
( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
比如這句話是一個什麼意思?這樣當出現類似的問題而又沒有現成的解決方案(前人沒有做過類似的工作)的時候,自己可以找到解決的辦法。同時通過解析c的源碼,達到向大師們學習的目的。
-
C/C++ code
-
((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int)-1))
是位元組對齊,這裡是應該是32位對齊(32bit)
樓上,請問如何看出是32位對齊?
在Stdarg.h中的注釋說:
要注意char,short隱式上升為int型;float隱式上升為double型。
((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int)-1))
在本常式中表示32位對齊,但也可以支援64位對齊。
我想知道這句話為什麼實現了資料對齊的功能(看不懂哇,想將宏轉換為函數調試一下,還發生了異常),謝謝:-)
guanzhu xuexi
你自己隨便找個數字,然後拿筆計算一下就知道了,這種對齊方法在一些原代碼中常見。
呵呵,等我給樓主一個詳細的解釋吧。順便給我的變參筆記一個完美的結尾:-)
所謂變參,是被調函數直接操作棧空間的一種應用。
原本的操作宏,不改變棧空間。
你定義的函數,修改了棧空間。
這就是你改的“函數”方式的變參操作不能用的原因。
-
C/C++ code
-
#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 )
仔細分析這段定義,你會發現:
⒈ 對 _INTSIZEOF(n) :
在 32 位系統(32位編譯器)上,sizeof(int) 尺寸為 4 ,該宏計算出 4 位元組對齊
後儲存 n 的資料所需位元組數。
在其他系統(相應的編譯器)上類似。你可以自己分析。
⒉ 對 va_start
(ap,v) :
考慮 v 是第一個函數參數。ap 得到的是(對齊後) v 後面的相鄰參數的地址。
實際上也就是將“第一個可變的參數”地址存進 ap 。
⒊ 對 va_arg(ap,t) :
先 += 對齊後 t 類型資料所需位元組數,再 - 該位元組數,再轉換成指標取值。
換句話說,返回 *(t*)ap ,再修改 ap ,增量為 t 類型的資料尺寸。
這時, ap 就是“下一個可變參數”地址。
⒋ va_end 類似。你可以自己分析。
對於函數方式的實現。
你可以考慮將參數傳遞改成引用的方式。
在此基礎上,再把變參邏輯實現進去。
goodluck
位元組對齊
的學習筆記
一、問題的提出
兩年之前我寫過一篇可變參數學習筆記,裡面曾經簡單的解釋過一句:
代碼
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
的作用是在考慮位元組對齊
的因素下計算第一個可變參數的起始地址。
當時限於時間和水平,未能做更詳細的解釋。
今天(2007-11-26)在csdn論壇上看到了一個文章
http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257
問題:CRT源碼分析中一個關於可變函數參數的問題
提問者:Sun_Moon_Stars
裡面又問到了這個宏,於是決定抽出半天時間,把這個問題詳細的說清楚。也算是把我的那篇文章
做一個完美的結尾。
二、引子
先看一個日常生活中的問題,
問題1:假設有要把一批貨物放到集裝箱裡,貨物有12件,
一個箱子最多能裝6件貨物,求箱子的數目。
解答:顯然我們需要12/6=2個箱子,並且每個箱子都是滿的。這個連小學生都會算:-)
問題2: 把問題1的條件改一下,假設一個箱子最多能裝5件貨物,那麼現在的箱子數是多少?
解答: 12/5=2.4個,但是根據實際情況,箱子的個數必須為整數,(有不知道這個常識的就不要再往下看了,
回小學重讀吧,呵呵)自然我們就要取3,
下面把問題一般化
三、一般數學模型
問題3:設一個箱子最多可以裝M件貨物,且現有N件貨物,
則至少需要多少個箱子,給出一般的計算公式。
這裡要注意兩點
1、箱子的總數必須為整數
2、N不一定大於M,很顯然,即使N <M,也得需要一隻箱子
四、通項公式
1、預備知識
在討論之問題3的解答之前,我們先明確一下/運算子的含義。
定義/運算為取整運算,即
對任意兩個整數N,M,必然有且只有唯一的整數X,滿足
X*M <= N < (X+1)*M,那麼記N/M=X。
這個也正是c裡/運算的確切含義。x的存在性和唯一性的嚴格證明可以見數論教材。
以後如無額外說明,/運算的含義均和本處一致。
/運算有一個基本的性質
若N=MX+Y,則N/M=X+Y/M,證明略
注意:N不是可以隨便拆的,設N=A+B,那麼一般情況下N/M 不一定等於 A/M+B/M,
A和B至少有一個是M的倍數才行。
2、分步討論
根據上面的/運算子的定義,我們可以得到問題三的解答,分情況討論一下
已知N/M=X,那麼當
(1)、當N正好是M的倍數時即N=M*X時,那麼箱子數就是X=N/M
(2)、如果N不是M的倍數,即N=M*X+Y(1 <=Y <M)時
那麼顯然還要多一個箱子來裝餘下的Y件貨物,
則箱子總數為X+1 = N/M+1
3、一般公式
上面的解答雖然完整,但是用起來並不方便,因為每次都要去判斷N和M的倍數關係,
我們自然就要想一個統一的公式,於是,下面的公式出現了
箱子數目為 (N+M-1)/M
這個式子用具體數字去驗證是很簡單的,留給讀者去做。
我這裡給一個完整的數學推導:
現在已經假定 /運算的結果為取整(或者說模數),即
N/M=X,則XM <=N <(X+1)M
那麼,
(1)、當N=MX時,(N+M-1)/M= MX/M+(M-1)/M=X
(2)、當N=MX+Y(1 <=Y <M)時,
由1 <=Y < M,同時加上M-1,得到M <= Y-1+M <= 2M-1 <2M
根據 /運算的定義 (Y-1+M) /M = 1
所以 (N+M-1)/M = (MX+Y+M-1)/M= MX/M+(Y+M-1)/M= X+1
顯然 公式 (N+M-1)/M與2中的分步討論結果一致。
可能有的讀者還會問,這個公式是怎麼想出來的,怎麼就想到了加上那個M-1?
這個問題可以先去看看數論中的餘數理論。
五、對齊代碼的分析
有了上面的數學基礎,我們再來看看開頭所說的對齊代碼的含義
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
意義就很明顯了
這裡。機器字長度sizeof(int)相當於箱子的容量M,變數的真實位元組大小相於
貨物總數N,整個代碼就是求n所佔的機器字數目。
順便仔細的解釋一下
~(sizeof(int)-1))
這裡用到了一個位元運算的技巧,即若M是2的冪,那麼
N/M = N &(~(M-1)),
這個讀者可以用具體的數自己驗證這個結論。
這裡給出數學的解釋:
設N,M都是位元字,且M為2的冪,即M=power(2,Y);
那麼必有
N=M*X+Z(1 < =Y < M)
而注意到這裡的N,M,Z都是二進位表示,所以把N的最右邊的Y位元字就是餘數Z.
剩下的左邊數字就是模X.我們的任務就是把左邊的模求出來就可以。
注意:
(1)這裡最關鍵的一點就是M必須是2的冪(有人常常理解成2的倍數也可以,那是不對的),
否則上面的結論是不成立的
(2) ~(M-1)更專業的叫法就是掩碼(mask)。因為數字和這個掩碼進行與運算後,數位最右邊Y位的
數字被置0("掩抹"掉了).即掩碼最右邊的0有多少位,數字最右邊就有多少位被清0。
小結:
1、位元組對齊
的數學本質就是數論中的模數運算。在電腦上的含義就是求出一個對象佔用的機器字數目。
2、在c中/運算可以用位元運算和掩碼來實現以加快速度,前提是機器字長度必須為2的冪。
ls是個有愛心的大叔,pfpf
好貼
我將你的程式稍加改動,可以實現變參的要求.
你主要的問題就是為了和標準宏調用方式一致而採用了傳值的方式,而這樣一來,變數在棧中的儲存方式已經不是你想象的那樣了.
我下面的程式就是在你的基礎之上將值傳遞改為了地址傳遞,我試過了,執行起來應該沒有什麼問題,你可以參考一下,呵呵
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char *va_list;
float average( int &n_values, ... );
void va_start
( char **var_arg, int &n_values );
template < class T > T va_arg( char **var_arg, T arg_kind );
void va_end( char **var_arg );
int main( void )
{
int nCount = 2;
int nTemp1 = 1;
int nTemp2 = 300;
float fAverage = average( nCount, nTemp1, nTemp2 );
printf( "average = %f/n", fAverage );
return 0;
}
float average( int &n_values, ... )
{
char *var_arg;
int count;
float sum = 0;
int int_kind;
// Prepare to access the variable arguments.
va_start
( &var_arg, n_values );
// Add the values from the variable argument list.
for( count = 0; count < n_values; count += 1 )
{
sum += va_arg( &var_arg, int_kind );
}
// Done processing variable arguments.
va_end( &var_arg );
return sum / n_values;
}
void va_start
( char **var_arg, int &n_values )
{
int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
*var_arg = ( char* )&n_values - i;
}
template < class T >
T va_arg( char **var_arg, T arg_kind )
{
int i = ( sizeof(T) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
return *( T * )(( *var_arg -= i ) + i );
}
void va_end( char **var_arg )
{
*var_arg = ( char* )0;
}
對了
template < class T >
T va_arg( char **var_arg, T arg_kind )
{
int i = ( sizeof(T) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 );
return *( T * )(( *var_arg -= i ) + i );
}
裡面的 *var_arg -= i ,需要說明一下:
因為我所用的環境儲存變數的方法是,對於先定義的變數存在棧的下部,後定義的放在棧的上部,因為nCount是最後定義的,nTemp1,nTemp2都在它上面,所以用的是-=,而不是你用的+=.
呵呵,還有關於int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 ); 的分析,
看了樓上各位大蝦的分析,我也來說一下我自己的看法:
首先,變參函數的標準宏定義是直接對棧進行操作,參考如下代碼
double sum(int lim,...)
{
va_list ap; // declare object to hold arguments
double tot = 0;
int i;
va_start
(ap, lim); // initialize ap to argument list
for (i = 0; i < lim; i++)
tot += va_arg(ap, double); // access each item in argument list
va_end(ap); // clean up
return tot;
}
1)首先建立一個參數列表ap,也就是一個字元型指標;
2)初始化參數列表,其中va_start
(ap, lim)中的lim不僅指定了該變參函數中參數的個數(次要的),更主要的是將其地址賦給ap,以便其能操作棧中的各個運算元(理想情況下在棧中會以此儲存lim,及...所代表的各參數),但問題在於ap指向的是lim,而非我們真正要用到的參數,於是
int i = ( sizeof(n_values) + sizeof(int) - 1 ) & ~(
sizeof(int) - 1 );
就起作用了,它計算出了lim在棧中所佔用的位元組數n,然後使ap位移n各位置,就能找到我們所要操作的數了.並不是樓主所說的位元組對齊
.
我說的僅僅是個大概的意思,也是今天看了這個文章之後給我的啟發.如果有說得不對的地方還請大家指出來,一起學習.
經典,mark一下