關於va和位元組對齊

來源:互聯網
上載者:User

這是一個使用可變函數參數的樣本程式,如下:
#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一下

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.