欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
從printf談可變參數函數的實(shí)現

從printf談可變參數函數的實(shí)現

作者:戎亞新

摘要:一直以來(lái)都覺(jué)得printf似乎是c語(yǔ)言庫中功能最強大的函數之一,不僅因為它能格式化輸出,更在于它的參數個(gè)數沒(méi)有限制,要幾個(gè)就給幾個(gè),來(lái)者不拒。printf這種對參數個(gè)數和參數類(lèi)型的強大適應性,讓人產(chǎn)生了對它進(jìn)行探索的濃厚興趣。

關(guān)鍵字:printf, 可變參數

1. 使用情形

int a =10;double b = 20.0;char *str = "Hello world";printf("begin print\n");printf("a=%d, b=%.3f, str=%s\n", a, b, str);...
  從printf的使用情況來(lái)看,我們不難發(fā)現一個(gè)規律,就是無(wú)論其可變的參數有多少個(gè),printf的第一個(gè)參數總是一個(gè)字符串。而正是這第一個(gè)參數,使得它可以確認后面還有有多少個(gè)參數尾隨。而尾隨的每個(gè)參數占用的??臻g大小又是通過(guò)第一個(gè)格式字符串確定的。然而printf到底是怎樣取第一個(gè)參數后面的參數值的呢,請看如下代碼

2. printf 函數的實(shí)現

//acenv.htypedef char *va_list;#define  _AUPBND        (sizeof (acpi_native_int) - 1)#define  _ADNBND        (sizeof (acpi_native_int) - 1)                        #define _bnd(X, bnd)    (((sizeof (X)) + (bnd)) & (~(bnd)))#define va_arg(ap, T)   (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))#define va_end(ap)      (void) 0#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))//start.cstatic char sprint_buf[1024];int printf(char *fmt, ...){	va_list args;	int n;	va_start(args, fmt);	n = vsprintf(sprint_buf, fmt, args);	va_end(args);	write(stdout, sprint_buf, n);	return n;}//unistd.hstatic inline long write(int fd, const char *buf, off_t count){	return sys_write(fd, buf, count);}
3. 分析

  從上面的代碼來(lái)看,printf似乎并不復雜,它通過(guò)一個(gè)宏va_start把所有的可變參數放到了由args指向的一塊內存中,然后再調用vsprintf. 真正的參數個(gè)數以及格式的確定是在vsprintf搞定的了。由于vsprintf的代碼比較復雜,也不是我們這里要討論的重點(diǎn),所以下面就不再列出了。我們這里要討論的重點(diǎn)是va_start(ap, A)宏的實(shí)現,它對定位從參數A后面的參數有重大的制導意義?,F在把 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) 的含義解釋一下如下:

    va_start(ap, A)    {         char *ap =  ((char *)(&A)) + sizeof(A)并int類(lèi)型大小地址對齊    }
   在printf的va_start(args, fmt)中,fmt的類(lèi)型為char *, 因此對于一個(gè)32為系統 sizeof(char *) = 4, 如果int大小也是32,則va_start(args, fmt);相當于 char *args = (char *)(&fmt) + 4; 此時(shí)args的值正好為fmt后第一個(gè)參數的地址。對于如下的可變參數函數
    void fun(double d,...)    {        va_list args;    	int n;    	va_start(args, d);    }
則 va_start(args, d);相當于
    char *args = (char *)&d + sizeof(double);
   此時(shí)args正好指向d后面的第一個(gè)參數。

  可變參數函數的實(shí)現與函數調用的棧結構有關(guān),正常情況下c/c++的函數參數入棧規則為_(kāi)_stdcall, 它是從右到左的,即函數中的最右邊的參數最先入棧。對于函數

    void fun(int a, int b, int c)    {        int d;        ...    }
其棧結構為
    0x1ffc-->d    0x2000-->a    0x2004-->b    0x2008-->c
   對于任何編譯器,每個(gè)棧單元的大小都是sizeof(int), 而函數的每個(gè)參數都至少要占一個(gè)棧單元大小,如函數 void fun1(char a, int b, double c, short d) 對一個(gè)32的系統其棧的結構就是
    0x1ffc-->a  (4字節)    0x2000-->b  (4字節)    0x2004-->c  (8字節)    0x200c-->d  (4字節)
  對于函數void fun1(char a, int b, double c, short d)

  如果知道了參數a的地址,則要取后續參數的值則可以通過(guò)a的地址計算a后面參數的地址,然后取對應的值,而后面參數的個(gè)數可以直接由變量a指定,當然也可以像printf一樣根據第一個(gè)參數中的%模式個(gè)數來(lái)決定后續參數的個(gè)數和類(lèi)型。如果參數的個(gè)數由第一個(gè)參數a直接決定,則后續參數的類(lèi)型如果沒(méi)有變化并且是已知的,則我們可以這樣來(lái)取后續參數, 假定后續參數的類(lèi)型都是double;

void fun1(int num, ...){    double *p = (double *)((&num)+1);    double Param1 = *p;    double Param2 = *(p+1);    ...    double Paramn  *(p+num);}
   如果后續參數的類(lèi)型是變化而且是未知的,則必須通過(guò)一個(gè)參數中設定模式來(lái)匹配后續參數的個(gè)數和類(lèi)型,就像printf一樣,當然我們可以定義自己的模式,如可以用i表示int參數,d表示double參數,為了簡(jiǎn)單,我們用一個(gè)字符表示一個(gè)參數,并由該字符的名稱(chēng)決定參數的類(lèi)型而字符的出現的順序也表示后續參數的順序。 我們可以這樣定義字符和參數類(lèi)型的映射表,
i---ints---signed shortl---longc---char 
"ild"模式用于表示后續有三個(gè)參數,按順序分別為int, long, double類(lèi)型的三個(gè)參數那么這樣我們可以定義自己版本的printf 如下
void printf(char *fmt, ...){    char s[80] = "";    int paramCount = strlen(fmt);    write(stdout, "paramCount = " , strlen(paramCount = ));    itoa(paramCount,s,10);    write(stdout, s, strlen(s));    char *p = (char *)(&fmt) + sizeof(char *);    int *pi = (int *)p;    for (int i=0; i<paramCount; i++)    {        char line[80] = "";        strcpy(line, "param");        itoa(i+1, s, 10);        strcat(line, s);        strcat(line, "=");        switch(fmt[i])        {            case 'i':            case 's':                itoa((*pi),s,10);                strcat(line, s);                pi++;                break;            case 'c':                {                    int len = strlen(line);                    line[len] = (char)(*pi);                    line[len+1] = '\0';                }                break;            case 'l':                ltoa((*(long *)pi),s,10);                strcat(line, s);                pi++;                break;            default:                break;        }    }}
也可以這樣定義我們的Max函數,它返回多個(gè)輸入整型參數的最大值
int Max(int n, ...){    int *p = &n + 1;    int ret = *p;    for (int i=0; i<n; i++)    {        if (ret < *(p + i))            ret = *(p + i);    }    return ret;}
可以這樣調用, 后續參數的個(gè)數由第一個(gè)參數指定
int m = Max(3, 45, 12, 56);int m = Max(1, 3);int m = Max(2, 23, 45);int first = 34, second = 45, third=5;int m = Max(5, first, second, third, 100, 4);
結論

   對于可變參數函數的調用有一點(diǎn)需要注意,實(shí)際的可變參數的個(gè)數必須比前面模式指定的個(gè)數要多,或者不小于, 也即后續參數多一點(diǎn)不要緊,但不能少, 如果少了則會(huì )訪(fǎng)問(wèn)到函數參數以外的堆棧區域,這可能會(huì )把程序搞崩掉。前面模式的類(lèi)型和后面實(shí)際參數的類(lèi)型不匹配也有可能造成把程序搞崩潰,只要模式指定的數據長(cháng)度大于后續參數長(cháng)度,則這種情況就會(huì )發(fā)生。 如:

printf("%.3f, %.3f, %.6e", 1, 2, 3, 4);
   參數1,2,3,4的默認類(lèi)型為整型,而模式指定的需要為double型,其數據長(cháng)度比int大,這種情況就有可能訪(fǎng)問(wèn)函數參數堆棧以外的區域,從而造成危險。但是printf("%d, %d, %d", 1.0, 20., 3.0);這種情況雖然結果可能不正確,但是確不會(huì )造成災難性后果。因為實(shí)際指定的參數長(cháng)度比要求的參數長(cháng)度長(cháng),堆棧不會(huì )越界。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
高效可靠的日志記錄模塊的構建 | 艾瑞楓∑°
可變參數及可變參數宏的使用
C變長(cháng)參數
【轉】Linux內核中的printf實(shí)現
C語(yǔ)言可變參數函數的使用及相關(guān)函數介紹
變參函數的實(shí)現
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久