C 可變引數函式分析(va_start,va_end,va_list...)

Iflyinsky2013發表於2017-03-14

#PS:要轉載請註明出處,本人版權所有

#PS:這個只是 《 我自己 》理解,如果和你的

#原則相沖突,請諒解,勿噴
系統:Linux 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

在幾年前,由於興趣需要,去研究了c的不定引數問題,當時由於太懶,沒有記錄任何資料,導致現在實際需要的時候,又要重新來過。嗚呼哀哉,太煩了~~~~~~~~~~~~~~

首先,c不定引數所定義的巨集的標頭檔案在 stdarg.h中。在我的系統中在這裡:/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h,原始碼我就不貼了,就簡要分析幾個重要巨集的原理。

    va_list  //此巨集依賴與不同的平臺和作業系統。
        //常用定義:(具體和編譯器有關,其實瞭解透後,都差不多)
        typedef char *  __builtin_va_list;//(注意可能是這樣定義的)
        (or)
        typedef void *  __builtin_va_list;//(注意可能是這樣定義的)
        typedef __builtin_va_list __gnuc_va_list;  
        typedef __gnuc_va_list va_list;


    _INTSIZEOF(n) //此巨集是windows上定義的,目的是為了考慮相容一些記憶體地址對齊的系統,n應該佔多少位元組,如:arm指令集是4位元組對齊訪問,Thumb指令集是24位元組對齊訪問.在linux上,gcc編譯器會內部定義一個類似的巨集,來代表對齊的位元組數。其用法是為了根據一個確定的引數,來依次確定不定引數中的每一個引數。
        #define  _INTSIZEOF(n) \
          ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
          //一般在一個作業系統中,int型別所佔的位元組數即為當前對齊的位元組數,一般為4或者8。關於此巨集的詳細分析,以下的分析來至於網上,帶入sizeof(n) = 4,8即可明白:
          #define  _INTSIZEOF(n)  ((sizeof(n) + x) & ~(x))
            x = sizeof(int) - 1 = 3 = 0000 0000 0000 0011(b)
            ~x = 1111 1111 1111 1100(b)
    va_start(v,l) //此巨集獲取的是可變引數中的第一個引數的地址
        #define __builtin_va_start(v,l) \
             ( v = (va_list)&l + _INTSIZEOF(l) )//(注意可能是這樣定義的),l的地址加上l地址所佔的空間
        #define va_start(v,l)   __builtin_va_start(v,l)

    va_arg(v,l)//此巨集是使下一個可變引數的地址賦值給v,同時返回開始時v地址所指向的值,注意:此時v已經指向下一個引數地址,但是表示式返回的值是初始v的值,《《這裡只需要搞清楚括號優先順序就能夠明白》》。
        #define __builtin_va_arg(v,l) \
             ( *(l *)((v += _INTSIZEOF(l)) - _INTSIZEOF(l)) )//(注意可能是這樣定義的)
        #define va_arg(v,l) __builtin_va_arg(v,l)

    va_end(v)//此巨集處理va_list宣告的一個指標,防止出現野指標。有始有終。
        #define __builtin_va_end(v)\
            ( v = (va_list)0 )
        #define va_end(v)   __builtin_va_end(v)


來個例子,(t.c)

#include <stdarg.h>
#include <stdio.h>

void ttt(char * fuk,...);

int main(int argc, char * argv[]){
ttt("fuk",2333,"this is 23333333333333333");
return 0;
}
void ttt(char * fuk,...){

    va_list arg;
    int a;
    char *b;
    va_start(arg,fuk);
    a = va_arg(arg,int);
    b = va_arg(arg,char *);
    va_end(arg);

    printf("%d,%s\n",a,b);
}
>>gcc t.c
>>./a.out

很厲害的

最後的注意:

1 變參函式必須有一個以上的指定引數

2 變參的《《型別》》必須通過一定方式已知(如格式化字串,”%d,%s”等等),否則就是定死的

3 變參的《《個數》》也必須通過一定方式已知(如格式化字串,”%d,%s”等等),否則就是定死的

4 c的函式引數入棧方式是從右到左,棧的地址一般是從高到底。

#PS:請尊重原創,不喜勿噴

#PS:要轉載請註明出處,本人版權所有.

有問題請留言,看到後我會第一時間回覆

相關文章