久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

C變長(zhǎng)參數(shù)

 oskycar 2012-06-18
在這里開(kāi)始書寫日記,、心情 …很多技術(shù)人員都有在"技術(shù)細(xì)節(jié)"上"鉆牛角尖"的"癖好",對(duì)此很多人褒貶不一,;無(wú)論怎樣,,我也是屬于這類人。C語(yǔ)言的變長(zhǎng)參數(shù)在平時(shí)做開(kāi)發(fā)時(shí)很少會(huì)在自 己設(shè)計(jì)的接口中用到,,但我們最常用的接口printf就是使用的變長(zhǎng)參數(shù)接口,,在感受到printf強(qiáng)大的魅力的同時(shí),是否想挖據(jù)一下到底printf是 如何實(shí)現(xiàn)的呢,?這里我們一起來(lái)挖掘一下C語(yǔ)言變長(zhǎng)參數(shù)的奧秘,。

先考慮這樣一個(gè)問(wèn)題:如果我們不使用C標(biāo)準(zhǔn)庫(kù)(libc)中提供的Facilities,我們自己是否可以實(shí)現(xiàn)擁有變長(zhǎng)參數(shù)的函數(shù)呢,?我們不妨試試,。

一步一步進(jìn)入正題,我們先看看固定參數(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類型的; 通過(guò)&b我們可以得到b的地址,,并通過(guò)函數(shù)原型聲明了解到b是double類型的; 通過(guò)&c我們可以得到c的地址,并通過(guò)函數(shù)原型聲明了解到c是char*類型的,。

但是對(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ù)都是什么類型的,自然也就無(wú)法確定其位置了,。那么如何可 以做到呢,?在大腦中回想一下函數(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è)思路,我們 繼續(xù)往下走,,通過(guò)一個(gè)例子來(lái)詮釋一下:(這里要說(shuō)明的是:函數(shù)參數(shù)進(jìn)棧以及參數(shù)空間地址分配都是"實(shí)現(xiàn)相關(guān)"的,,不同平臺(tái)、不同編譯器都可能不同,,所以下 面的例子僅在IA-32,,Windows XP, MinGW gcc v3.4.2下成立)

我們先用上面的那個(gè)fixed_args_func函數(shù)確定一下這個(gè)平臺(tái)下的入棧順序,。

int main() {
    fixed_args_func(17, 5.40, "hello world");
    return 0;
}
a = 0x0022FF50
b = 0x0022FF54
c = 0x0022FF5C

從這個(gè)結(jié)果來(lái)看,,顯然參數(shù)是從右到左,逐一壓入棧中的(棧的延伸方向是從高地址到低地址,,棧底的占領(lǐng)著最高內(nèi)存地址,,先入棧的參數(shù),其地理位置也就最高了),。我們基本可以得出這樣一個(gè)結(jié)論:
 c.addr = b.addr + x_sizeof(b);  /*注意:  x_sizeof != sizeof,,后話再說(shuō) */
 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ù):

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");
}

輸出結(jié)果:
4
5
hello world

var_args_func 只是為了演示,并未根據(jù)fmt消息中的格式字符串來(lái)判斷變參的個(gè)數(shù)和類型,,而是直接在實(shí)現(xiàn)中寫死了,,如果你把這個(gè)程序拿到solaris 9下,運(yùn)行后,,一定得不到正確的結(jié)果,,為什么呢,后續(xù)再說(shuō),。先來(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);

前面說(shuō)過(guò),如果將var_args_func放到solaris上,,一定是得不到正確結(jié)果的?為什么呢,?由于內(nèi)存對(duì)齊,。編譯器在棧上壓入?yún)?shù)時(shí),不是一個(gè)緊挨著另一個(gè)的,,編譯器會(huì)根據(jù)變參的類型將其放到滿足類型對(duì)齊的地址上的,,這樣棧上參數(shù)之間實(shí)際上可能會(huì)是有空隙的。上述例子中,,我是根據(jù)反編譯后的匯編碼得到的參數(shù)間隔,,還好都是4,然后在代碼中寫死了,。

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

void std_vararg_func(const char *fmt, ... ) {
        va_list ap;
        va_start(ap, fmt);

        printf("%d\n", va_arg(ap, int));
        printf("%f\n", va_arg(ap, double));
        printf("%s\n", va_arg(ap, char*));

        va_end(ap);
}

int main() {
        std_vararg_func("%d %f %s\n", 4, 5.4, "hello world");
}
輸出:
4
5.400000
hello world

對(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ù)雜,。在有的系統(tǒng)中stdarg.h的實(shí)現(xiàn)依賴 some special functions built into the the compilation system to handle variable argument lists and stack allocations,多數(shù)其他系統(tǒng)的實(shí)現(xiàn)與下面很相似:(Visual C++ 6.0的實(shí)現(xiàn)較為清晰,,因?yàn)閣indows上的應(yīng)用程序只需要在windows平臺(tái)間做移植即可,,沒(méi)有必要考慮太多的平臺(tái)情況)。

Microsoft Visual Studio\VC98\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 )

這里有兩個(gè)地方需要深入挖掘一下:
1,、#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
我們這里簡(jiǎn)化一下這個(gè)宏:
#define _INTSIZEOF(n)  ((sizeof(n) + x) & ~(x))
x = sizeof(int) - 1 = 3 = 0000 0000 0000 0011(b)
~x = 1111 1111 1111 1100(b)

當(dāng)一個(gè)數(shù) & (-x)時(shí),得到的值始終是sizeof(int)的倍數(shù),,也就是說(shuō)_INTSIZEOF(n)的功能是將n圓整到sizeof(int)的倍數(shù)上去,。sizeof(n) >= 1, sizeof(n)+sizeof(int)-1經(jīng)過(guò)圓整后,一定會(huì)是>=4的整數(shù),;在其他系統(tǒng)平臺(tái)上,,圓整的目標(biāo)值有的是4,有的則是8,,視具體系統(tǒng)而定,。

2、#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
其 實(shí)有了var_args_func的實(shí)現(xiàn),,這里也就不難理解了,。不過(guò)這里有一個(gè)trick,很多人一開(kāi)始肯定對(duì)先加上_INTSIZEOF(t),,又減去 _INTSIZEOF(t)很不理解,,其實(shí)這里是一點(diǎn)就透的:整個(gè)表達(dá)式((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) 返回的值其實(shí)和最初的ap所指向的地址是一致的,關(guān)鍵就是在整個(gè)表達(dá)式被evaluated后,,ap卻指向了下一個(gè)參數(shù)的地址了,,就這么簡(jiǎn)單

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多