国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

C語(yǔ)言中可變參數(shù)va_list/va_start/value_arg/va_end的理解

2019-11-06 06:04:21
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

va_list/va_start/va_arg/va_end這幾個(gè)宏,都是用于函數(shù)的可變參數(shù)的。

我們來(lái)看看在vs2008中,它們是怎么定義的:

   1:  ///stdarg.h
   2:  #define va_start _crt_va_start
   3:  #define va_arg _crt_va_arg
   4:  #define va_end _crt_va_end
   5:   
   6:  ///vadefs.h
   7:  #define _ADDRESSOF(v)   ( &reinterPRet_cast<const char &>(v) )
   8:  typedef char *  va_list;
   9:  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
  10:  #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
  11:  #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
  12:  #define _crt_va_end(ap)      ( ap = (va_list)0 )
 
再看看各個(gè)宏的功能是什么?va_list用于聲明一個(gè)變量,我們知道函數(shù)的可變參數(shù)列表其實(shí)就是一個(gè)字符串,所以va_list才被聲明為字符型指針,這個(gè)類型用于聲明一個(gè)指向參數(shù)列表的字符型指針變量,例如:va_list ap;//ap:arguement pointerva_start(ap,v),它的第一個(gè)參數(shù)是指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)函數(shù)的第一個(gè)參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個(gè)數(shù)。va_arg(ap,t),它的第一個(gè)參數(shù)指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)的類型。va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL).

  先來(lái)了解C語(yǔ)言中可變參數(shù)函數(shù)實(shí)現(xiàn)原理

C函數(shù)調(diào)用的棧結(jié)構(gòu)

 可變參數(shù)函數(shù)的實(shí)現(xiàn)與函數(shù)調(diào)用的棧結(jié)構(gòu)密切相關(guān),正常情況下C的函數(shù)參數(shù)入棧規(guī)則為_(kāi)_stdcall, 它是從右到左的,即函數(shù)中的最右邊的參數(shù)最先入棧。例如,對(duì)于函數(shù):

  void fun(int a, int b, int c)  {        int d;        ...  }

其棧結(jié)構(gòu)為

    0x1ffc-->d

    0x2000-->a

    0x2004-->b

    0x2008-->c

對(duì)于在32位系統(tǒng)的多數(shù)編譯器,每個(gè)棧單元的大小都是sizeof(int), 而函數(shù)的每個(gè)參數(shù)都至少要占一個(gè)棧單元大小,如函數(shù) void fun1(char a, int b, double c, short d) 對(duì)一個(gè)32的系統(tǒng)其棧的結(jié)構(gòu)就是

    0x1ffc-->a  (4字節(jié))(為了字對(duì)齊)

    0x2000-->b  (4字節(jié))

    0x2004-->c  (8字節(jié))

    0x200c-->d  (4字節(jié))

因此,函數(shù)的所有參數(shù)是存儲(chǔ)在線性連續(xù)的棧空間中的,基于這種存儲(chǔ)結(jié)構(gòu),這樣就可以從可變參數(shù)函數(shù)中必須有的第一個(gè)普通參數(shù)來(lái)尋址后續(xù)的所有可變參數(shù)的類型及其值。

先看看固定參數(shù)列表函數(shù):

void fixed_args_func(int a, double b, char *c){        printf("a = 0x%p/n", &a);        printf("b = 0x%p/n", &b);        printf("c = 0x%p/n", &c);}

對(duì)于固定參數(shù)列表的函數(shù),每個(gè)參數(shù)的名稱、類型都是直接可見(jiàn)的,他們的地址也都是可以直接得到的,比如:通過(guò)&a我們可以得到a的地址,并通過(guò)函數(shù)原型聲明了解到a是int類型的。

   但是對(duì)于變長(zhǎng)參數(shù)的函數(shù),我們就沒(méi)有這么順利了。還好,按照C標(biāo)準(zhǔn)的說(shuō)明,支持變長(zhǎng)參數(shù)的函數(shù)在原型聲明中,必須有至少一個(gè)最左固定參數(shù)(這一點(diǎn)與傳統(tǒng)C有區(qū)別,傳統(tǒng)C允許不帶任何固定參數(shù)的純變長(zhǎng)參數(shù)函數(shù)),這樣我們可以得到其中固定參數(shù)的地址,但是依然無(wú)法從聲明中得到其他變長(zhǎng)參數(shù)的地址,比如:

void var_args_func(const char * fmt, ...) {    ... ... }

這里我們只能得到fmt這固定參數(shù)的地址,僅從函數(shù)原型我們是無(wú)法確定"..."中有幾個(gè)參數(shù)、參數(shù)都是什么類型的。回想一下函數(shù)傳參的過(guò)程,無(wú)論"..."中有多少個(gè)參數(shù)、每個(gè)參數(shù)是什么類型的,它們都和固定參數(shù)的傳參過(guò)程是一樣的,簡(jiǎn)單來(lái)講都是棧操作,而棧這個(gè)東西對(duì)我們是開(kāi)放的。這樣一來(lái),一旦我們知道某函數(shù)幀的棧上的一個(gè)固定參數(shù)的位置,我們完全有可能推導(dǎo)出其他變長(zhǎng)參數(shù)的位置。

我們先用上面的那個(gè)fixed_args_func函數(shù)確定一下入棧順序。

復(fù)制代碼
int main() {    fixed_args_func(17, 5.40, "hello world");    return 0;}a = 0x0022FF50b = 0x0022FF54c = 0x0022FF5C復(fù)制代碼

從這個(gè)結(jié)果來(lái)看,顯然參數(shù)是從右到左,逐一壓入棧中的(棧的延伸方向是從高地址到低地址,棧底的占領(lǐng)著最高內(nèi)存地址,先入棧的參數(shù),其地理位置也就最高了)。

我們基本可以得出這樣一個(gè)結(jié)論:

 c.addr = b.addr + x_sizeof(b);  /*注意:  x_sizeof !=sizeof */ b.addr = a.addr + x_sizeof(a);

有了以上的"等式",我們似乎可以推導(dǎo)出 void var_args_func(const char * fmt, ... ) 函數(shù)中,可變參數(shù)的位置了。起碼第一個(gè)可變參數(shù)的位置應(yīng)該是:first_vararg.addr = fmt.addr + x_sizeof(fmt);  根據(jù)這一結(jié)論我們?cè)囍鴮?shí)現(xiàn)一個(gè)支持可變參數(shù)的函數(shù):

復(fù)制代碼
#include <stdarg.h>#include <stdio.h>void var_args_func(const char * fmt, ...) {    char    *ap;    ap = ((char*)&fmt) + sizeof(fmt);    printf("%d/n", *(int*)ap);              ap =  ap + sizeof(int);    printf("%d/n", *(int*)ap);    ap =  ap + sizeof(int);    printf("%s/n", *((char**)ap));}int main(){    var_args_func("%d %d %s/n", 4, 5, "hello world");   return 0;}復(fù)制代碼

期待輸出結(jié)果:45hello world


  先來(lái)解釋一下這個(gè)程序。我們用ap獲取第一個(gè)變參的地址,我們知道第一個(gè)變參是4,一個(gè)int 型,所以我們用(int*)ap以告訴編譯器,以ap為首地址的那塊內(nèi)存我們要將之視為一個(gè)整型來(lái)使用,*(int*)ap獲得該參數(shù)的值;接下來(lái)的變參是5,又一個(gè)int型,其地址是ap + sizeof(第一個(gè)變參),也就是ap + sizeof(int),同樣我們使用*(int*)ap獲得該參數(shù)的值;最后的一個(gè)參數(shù)是一個(gè)字符串,也就是char*,與前兩個(gè)int型參數(shù)不同的是,經(jīng)過(guò)ap + sizeof(int)后,ap指向棧上一個(gè)char*類型的內(nèi)存塊(我們暫且稱之tmp_ptr, char *tmp_ptr)的首地址,即ap -> &tmp_ptr,而我們要輸出的不是printf("%s/n", ap),而是printf("%s/n", tmp_ptr); printf("%s/n", ap)是意圖將ap所指的內(nèi)存塊作為字符串輸出了,但是ap -> &tmp_ptr,tmp_ptr所占據(jù)的4個(gè)字節(jié)顯然不是字符串,而是一個(gè)地址。如何讓&tmp_ptr是char **類型的,我們將ap進(jìn)行強(qiáng)制轉(zhuǎn)換(char**)ap <=> &tmp_ptr,這樣我們?cè)L問(wèn)tmp_ptr只需要在(char**)ap前面加上一個(gè)*即可,即printf("%s/n",  *(char**)ap);


   一切似乎很完美,編譯也很順利通過(guò),但運(yùn)行上面的代碼后,不但得不到預(yù)期的結(jié)果,反而整個(gè)編譯器會(huì)強(qiáng)行關(guān)閉(大家可以嘗試著運(yùn)行一下),原來(lái)是ap指針在后來(lái)并沒(méi)有按照預(yù)期的要求指向第二個(gè)變參數(shù),即并沒(méi)有指向5所在的首地址,而是指向了未知內(nèi)存區(qū)域,所以編譯器會(huì)強(qiáng)行關(guān)閉。其實(shí)錯(cuò)誤開(kāi)始于:ap =  ap + sizeof(int);由于內(nèi)存對(duì)齊,編譯器在棧上壓入?yún)?shù)時(shí),不是一個(gè)緊挨著另一個(gè)的,編譯器會(huì)根據(jù)變參的類型將其放到滿足類型對(duì)齊的地址上的,這樣棧上參數(shù)之間實(shí)際上可能會(huì)是有空隙的。(C語(yǔ)言內(nèi)存對(duì)齊詳解(1) C語(yǔ)言內(nèi)存對(duì)齊詳解(2) C語(yǔ)言內(nèi)存對(duì)齊詳解(3))所以此時(shí)的ap計(jì)算應(yīng)該改為:ap =  (char *)ap +sizeof(int) + __va_rounded_size(int);

改正后的代碼如下:

復(fù)制代碼
#include<stdio.h>#define __va_rounded_size(TYPE)  /  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))void var_args_func(const char * fmt, ...) {    char *ap;    ap = ((char*)&fmt) + sizeof(fmt);    printf("%d/n", *(int*)ap);              ap = (char *)ap + sizeof(int) + __va_rounded_size(int);    printf("%d/n", *(int*)ap);    ap = ap + sizeof(int) + __va_rounded_size(int);    printf("%s/n", *((char**)ap));}int main(){    var_args_func("%d %d %s/n", 4, 5, "hello world");     return 0;}復(fù)制代碼

var_args_func只是為了演示,并未根據(jù)fmt消息中的格式字符串來(lái)判斷變參的個(gè)數(shù)和類型,而是直接在實(shí)現(xiàn)中寫死了。

為了滿足代碼的可移植性,C標(biāo)準(zhǔn)庫(kù)在stdarg.h中提供了諸多便利以供實(shí)現(xiàn)變長(zhǎng)長(zhǎng)度參數(shù)時(shí)使用。這里也列出一個(gè)簡(jiǎn)單的例子,看看利用標(biāo)準(zhǔn)庫(kù)是如何支持變長(zhǎng)參數(shù)的:

復(fù)制代碼
 1 #include <stdarg.h>#include <stdio.h> 2  3 void std_vararg_func(const char *fmt, ...) { 4         va_list ap; 5         va_start(ap, fmt); 6  7         printf("%d/n", va_arg(ap, int)); 8         printf("%f/n", va_arg(ap, double)); 9         printf("%s/n", va_arg(ap, char*));10 11         va_end(ap);12 }13 14 int main() {15         std_vararg_func("%d %f %s/n", 4, 5.4, "hello world");        return 0;}復(fù)制代碼

對(duì)比一下 std_vararg_func和var_args_func的實(shí)現(xiàn),va_list似乎就是char*, va_start似乎就是 ((char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一個(gè)參數(shù)的首地址。沒(méi)錯(cuò),多數(shù)平臺(tái)下stdarg.h中va_list, va_start和var_arg的實(shí)現(xiàn)就是類似這樣的。一般stdarg.h會(huì)包含很多宏,看起來(lái)比較復(fù)雜。

下面我們來(lái)探討如何寫一個(gè)簡(jiǎn)單的可變參數(shù)的C 函數(shù).

使用可變參數(shù)應(yīng)該有以下步驟: 1)首先在函數(shù)里定義一個(gè)va_list型的變量,這里是arg_ptr,這個(gè)變量是指向參數(shù)的指針. 2)然后用va_start宏初始化變量arg_ptr,這個(gè)宏的第二個(gè)參數(shù)是第一個(gè)可變參數(shù)的前一個(gè)參數(shù),是一個(gè)固定的參數(shù). 3)然后用va_arg返回可變的參數(shù),并賦值給整數(shù)j. va_arg的第二個(gè)參數(shù)是你要返回的參數(shù)的類型,這里是int型. 4)最后用va_end宏結(jié)束可變參數(shù)的獲取.然后你就可以在函數(shù)里使用第二個(gè)參數(shù)了.如果函數(shù)有多個(gè)可變參數(shù)的,依次調(diào)用va_arg獲取各個(gè)參數(shù).

在《C程序設(shè)計(jì)語(yǔ)言》中,Ritchie提供了一個(gè)簡(jiǎn)易版printf函數(shù):

復(fù)制代碼
 1 #include<stdarg.h> 2  3 void minprintf(char *fmt, ...) 4 { 5     va_list ap; 6     char *p, *sval; 7     int ival; 8     double dval; 9 10     va_start(ap, fmt);11     for (p = fmt; *p; p++) {12         if(*p != '%') {13             putchar(*p);14             continue;15         }16         switch(*++p) {17         case 'd':18             ival = va_arg(ap, int);19             printf("%d", ival);20             break;21         case 'f':22             dval = va_arg(ap, double);23             printf("%f", dval);24             break;25         case 's':26             for (sval = va_arg(ap, char *); *sval; sval++)27                 putchar(*sval);28             break;29         default:30             putchar(*p);31             break;32         }33     }34     va_end(ap);35 }

我們看一段具有可變參數(shù)列表的函數(shù)的代碼:

  #include <stdio.h>#include <stdarg.h>void simple_va_fun(int i, ...){        va_list ap;        int j = 0;        int k = 0;        va_start(ap, i);        j = va_arg(ap, int);        k = va_arg(ap, int);        va_end(ap);        printf("%d %d %d/n",i,j,k);        return;}int main(void){        simple_va_fun(100);        simple_va_fun(100,200);        simple_va_fun(100,200,300);        return 0;}

 

輸出結(jié)果:100 730930504 730930520100 200 -965392000100 200 300

va_start的功能是要把,ap指針指向可變參數(shù)的第一個(gè)參數(shù)位置處,

    #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

    #define _ADDRESSOF(v) ( &reinterpret_cast<constchar &>(v) ):reinterpret_cast是C++新標(biāo)準(zhǔn)下的強(qiáng)制類型轉(zhuǎn)換,這里將v強(qiáng)制轉(zhuǎn)換為const char*型,然后取其地址。

   先取第一個(gè)參數(shù)的地址,在sum函數(shù)中就是取number的地址并且將其轉(zhuǎn)化為char *的(因?yàn)閏har *的指針進(jìn)行加減運(yùn)算后,偏移的字節(jié)數(shù)才與加的數(shù)字相同, 如果為int *p,那么p+1實(shí)際上將p移動(dòng)了4個(gè)字節(jié)),然后加上4(__INITSIZEOF(number)=(4+3)&~3),這樣就將ap指向了可變參數(shù)字符串的第一個(gè)參數(shù)。

 

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

 

以int所占的字節(jié)為標(biāo)準(zhǔn)進(jìn)行對(duì)其操作。如果int占四字節(jié),則以四字節(jié)對(duì)齊為標(biāo)準(zhǔn)讀取數(shù)據(jù)。

至于為什么會(huì)這樣計(jì)算是考慮到內(nèi)存對(duì)齊

兩年之前我寫過(guò)一篇可變參數(shù)學(xué)習(xí)筆記,里面曾經(jīng)簡(jiǎn)單的解釋過(guò)一句:代碼((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))的作用是在考慮字節(jié)對(duì)齊的因素下計(jì)算第一個(gè)可變參數(shù)的起始地址。當(dāng)時(shí)限于時(shí)間和水平,未能做更詳細(xì)的解釋。今天(2007-11-26)在csdn論壇上看到了一個(gè)帖子http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257問(wèn)題:CRT源碼分析中一個(gè)關(guān)于可變函數(shù)參數(shù)的問(wèn)題提問(wèn)者:Sun_Moon_Stars里面又問(wèn)到了這個(gè)宏,于是決定抽出半天時(shí)間,把這個(gè)問(wèn)題詳細(xì)的說(shuō)清楚。也算是把我的那篇文章做一個(gè)完美的結(jié)尾。

二、引子先看一個(gè)日常生活中的問(wèn)題,問(wèn)題1:假設(shè)有要把一批貨物放到集裝箱里,貨物有12件,一個(gè)箱子最多能裝6件貨物,求箱子的數(shù)目。解答:顯然我們需要12/6=2個(gè)箱子,并且每個(gè)箱子都是滿的。這個(gè)連小學(xué)生都會(huì)算:-)

問(wèn)題2:       把問(wèn)題1的條件改一下,假設(shè)一個(gè)箱子最多能裝5件貨物,那么現(xiàn)在的箱子數(shù)是多少?解答:       12/5=2.4個(gè),但是根據(jù)實(shí)際情況,箱子的個(gè)數(shù)必須為整數(shù),(有不知道這個(gè)常識(shí)的就不要再往下看了,回小學(xué)重讀吧,呵呵)自然我們就要取3,下面把問(wèn)題一般化

三、一般數(shù)學(xué)模型問(wèn)題3:設(shè)一個(gè)箱子最多可以裝M件貨物,且現(xiàn)有N件貨物,則至少需要多少個(gè)箱子,給出一般的計(jì)算公式。這里要注意兩點(diǎn)1、箱子的總數(shù)必須為整數(shù)2、N不一定大于M,很顯然,即使N<m,也得需要一只箱子 <="" p="" style="Word-wrap: break-word;">

四、通項(xiàng)公式1、預(yù)備知識(shí)在討論之問(wèn)題3的解答之前,我們先明確一下/運(yùn)算符的含義。定義/運(yùn)算為取整運(yùn)算,即對(duì)任意兩個(gè)整數(shù)N,M,必然有且只有唯一的整數(shù)X,滿足X*M   <=   N   <   (X+1)*M,那么記N/M=X。這個(gè)也正是c里/運(yùn)算的確切含義。x的存在性和唯一性的嚴(yán)格證明可以見(jiàn)數(shù)論教材。以后如無(wú)額外說(shuō)明,/運(yùn)算的含義均和本處一致。

/運(yùn)算有一個(gè)基本的性質(zhì)若N=MX+Y,則N/M=X+Y/M,證明略

注意:N不是可以隨便拆的,設(shè)N=A+B,那么一般情況下N/M   不一定等于   A/M+B/M,如果A和B至少有一個(gè)是M的倍數(shù),才能保證式子一定成立。

2、分步討論根據(jù)上面的/運(yùn)算符的定義,我們可以得到問(wèn)題三的解答,分情況討論一下已知N/M=X,那么當(dāng)(1)、當(dāng)N正好是M的倍數(shù)時(shí)即N=M*X時(shí),那么箱子數(shù)就是X=N/M(2)、如果N不是M的倍數(shù),即N=M*X+Y(1 <=Y <m)時(shí)那么顯然還要多一個(gè)箱子來(lái)裝余下的Y件貨物,則箱子總數(shù)為X+1   =   N/M+1

3、一般公式上面的解答雖然完整,但是用起來(lái)并不方便,因?yàn)槊看味家ヅ袛郚和M的倍數(shù)關(guān)系,我們自然就要想一個(gè)統(tǒng)一的公式,于是,下面的公式出現(xiàn)了箱子數(shù)目為     (N+M-1)/M

這個(gè)式子用具體數(shù)字去驗(yàn)證是很簡(jiǎn)單的,留給讀者去做。我這里給一個(gè)完整的數(shù)學(xué)推導(dǎo):現(xiàn)在已經(jīng)假定   /運(yùn)算的結(jié)果為取整(或者說(shuō)取模),即N/M=X,則XM   <=N   <(X+1)M那么,(1)、當(dāng)N=MX時(shí),(N+M-1)/M=   MX/M+(M-1)/M=X(2)、當(dāng)N=MX+Y(1 <=Y <m)時(shí),由1 <=Y   <   M,同時(shí)加上M-1,得到M   <=   Y-1+M   <=   2M-1   <2M根據(jù)   /運(yùn)算的定義   (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中的分步討論結(jié)果一致。可能有的讀者還會(huì)問(wèn),這個(gè)公式是怎么想出來(lái)的,怎么就想到了加上那個(gè)M-1?這個(gè)問(wèn)題可以先去看看數(shù)論中的余數(shù)理論。

五、對(duì)齊代碼的分析有了上面的數(shù)學(xué)基礎(chǔ),我們?cè)賮?lái)看看開(kāi)頭所說(shuō)的對(duì)齊代碼的含義((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))意義就很明顯了這里。機(jī)器字長(zhǎng)度sizeof(int)相當(dāng)于箱子的容量M,變量的真實(shí)字節(jié)大小相于貨物總數(shù)N,整個(gè)代碼就是求n所占的機(jī)器字?jǐn)?shù)目。

順便仔細(xì)的解釋一下~(sizeof(int)-1))

這里用到了一個(gè)位運(yùn)算的技巧,即若M是2的冪,M=power(2,Y);

則N/M   =  N>>Y  ,

另根據(jù)數(shù)論中的余數(shù)定理,

有N=M*X+Z(1 <   =Z <  M)而注意到這里的N,M,Z都是二進(jìn)制表示,所以把N的最右邊的Y位數(shù)字就是余數(shù)Z.剩下的左邊數(shù)字就是模X.

而內(nèi)存對(duì)齊要計(jì)算的是占用的總字節(jié)數(shù)(相當(dāng)于箱子的最大容量),所以

總字節(jié)數(shù) = ( N/M)*M =( N>>Y)<<y <="" p="" style="word-wrap: break-word;">

注意,這里的右移和左移運(yùn)算并未相互抵消,最后的結(jié)果實(shí)際上是把N中的余數(shù)Z去掉(被清0),

而左邊模X得以保持不變。

而當(dāng)M = power(2,Y) 時(shí)

(N >>Y) << Y = (N   &(~(M-1))也是一個(gè)恒等式(這個(gè)讀者也可以用數(shù)字驗(yàn)證),

所以,就得到我們前面看到的宏

((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

注意:(1)這里最關(guān)鍵的一點(diǎn)就是M必須是2的冪(有人常常理解成2的倍數(shù)也可以,那是不對(duì)的),否則上面的結(jié)論是不成立的(2)   ~(M-1)更專業(yè)的叫法就是掩碼(mask)。因?yàn)閿?shù)字和這個(gè)掩碼進(jìn)行與運(yùn)算后,數(shù)字的最右邊Y位的數(shù)字被置0("掩抹"掉了).即掩碼最右邊的0有多少位,數(shù)字最右邊就有多少位被清0。

小結(jié):1、字節(jié)對(duì)齊的數(shù)學(xué)本質(zhì)就是數(shù)論中的取模運(yùn)算。在計(jì)算機(jī)上的含義就是求出一個(gè)對(duì)象占用的機(jī)器字?jǐn)?shù)目。2、在數(shù)學(xué)上看內(nèi)存計(jì)算的過(guò)程就是先右移再左移相同的位數(shù),以得到箱子的最大容量。

3、在c中/運(yùn)算可以用位運(yùn)算和掩碼來(lái)實(shí)現(xiàn)以加快速度(省掉了求位數(shù)的過(guò)程),前提是機(jī)器字長(zhǎng)度必須為2的冪。

——————————————————

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

[此問(wèn)題的推薦答案]~是位取反的意思。_INTSIZEOF(n)整個(gè)做的事情就是將n的長(zhǎng)度化為int長(zhǎng)度的整數(shù)倍。比如n為5,二進(jìn)制就是101b,int長(zhǎng)度為4,二進(jìn)制為100b,那么n化為int長(zhǎng)度的整數(shù)倍就應(yīng)該為8。~(sizeof(int) – 1) )就應(yīng)該為~(4-1)=~(00000011b)=11111100b,這樣任何數(shù)& ~(sizeof(int) – 1) )后最后兩位肯定為0,就肯定是4的整數(shù)倍了。(sizeof(n) + sizeof(int) – 1)就是將大于4m但小于等于4(m+1)的數(shù)提高到大于等于4(m+1)但小于4(m+2),這樣再& ~(sizeof(int) – 1) )后就正好將原長(zhǎng)度補(bǔ)齊到4的倍數(shù)了。

    

va_arg是要從ap中取下一個(gè)參數(shù)。

 

       #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

 

對(duì)于這個(gè)宏,哥糾結(jié)了很久,最后終于搞清楚了,究其原因就是自己C語(yǔ)言功底不扎實(shí),具體表現(xiàn)在沒(méi)有搞清楚賦值表達(dá)式的值是怎么運(yùn)作的。
我們看這個(gè)宏,首先是ap = ap + __INTSIZEOF(t)。注意到,此時(shí)ap已經(jīng)被改變了,它已經(jīng)指向了下一個(gè)參數(shù),我們令x=ap + __INTSIZEOF(t);
那么括號(hào)內(nèi)就變成了(x – __INTSIZEOF(t)),但是這里沒(méi)有賦值運(yùn)算符,所以ap的值沒(méi)有發(fā)生變化,此時(shí)ap仍然指向的是當(dāng)前參數(shù)的下一個(gè)參數(shù)的位置,
也就是說(shuō)ap指向的位置比當(dāng)前正在處理的位置超前了一個(gè)位置。
其實(shí)寫成下面的形式就簡(jiǎn)單明了了:

    #define   va_arg(ap,t)   (*(t   *)((ap   +=   _INTSIZEOF(t)),   ap   -   _INTSIZEOF(t))   )

 
分析:為什么要將ap指向當(dāng)前處理參數(shù)的下一個(gè)參數(shù)了?
經(jīng)過(guò)上面的分析,我們知道va_start(ap,v)已經(jīng)將ap指向了可變參數(shù)列表的第一個(gè)參數(shù)了,以后我們每一步操作都需要將ap移動(dòng)到下一個(gè)
參數(shù)的位置,由于我們每次使用可變參數(shù)的順序是:va_start(ap,v)—>va_arg(ap,t);這樣我們?cè)诘谝淮稳?shù)的時(shí)候,其實(shí)ap已經(jīng)指向了
第二個(gè)參數(shù)開(kāi)始的位置,所以我們用表達(dá)式的方式獲得一個(gè)指向第一個(gè)參數(shù)的臨時(shí)指針,這樣我們就可以采用這種一致的方式來(lái)處理可變參數(shù)列表。
(感覺(jué)沒(méi)表達(dá)的十分清楚,希望各位朋友糾正~~~~~~)。
下圖是我的例子程序中去參數(shù)的情況(時(shí)間倉(cāng)促,畫得很丑,請(qǐng)?jiān)彛?pre style="margin-top:0px; margin-bottom:0px; padding:0px; word-wrap:break-word"> 
image 
 va_end(ap)  將聲明的ap指針置為空,因?yàn)橹羔樖褂煤笞詈笤O(shè)置為空。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 沾化县| 红桥区| 南丹县| 五莲县| 方城县| 彩票| 巴东县| 山西省| 郧西县| 久治县| 石景山区| 宜阳县| 三台县| 牟定县| 扶风县| 镇坪县| 崇文区| 孟村| 天祝| 榆中县| 赣州市| 辽宁省| 余庆县| 肇源县| 巴青县| 大同县| 嘉祥县| 西青区| 林口县| 丹棱县| 河北省| 崇仁县| 曲靖市| 榆林市| 怀安县| 岱山县| 阿尔山市| 开鲁县| 河津市| 榆林市| 阿鲁科尔沁旗|