C語言可變參數函數執行原理以應用

來源:互聯網
上載者:User

參數可變函數又稱VA函數,例如printf,scanf,exec。

1.舉例:

//fun:列印n後面參數的值

void fun(int n, ...);

int main()

{

     int part1 = 128;

     int part2 = 256;

     int part3 = 512;

     fun(part1, part2, part3);

     return 0;

}

void fun(int part1, ...)

{

     int *p = &part1; //擷取part1的地址

     printf("%d\n", *++p);//列印part2的值

     printf("%d\n", *++p);//列印part3的值

}

C預設的函數調用規範是_cdecl,即所有參數從右至左依次入棧,嚴格的fun聲明應該是:

void _cdecl fun(int n, ...);

在main中,調用fun函數前先將參數入棧,入棧的順序是:

push part3

push part2

push part1

然後調用fun,執行函數體代碼。

先壓棧的參數會放在高地址,因為棧是由高地址往低地址生長的,所以part1,part2,part3在記憶體中的順序將會是:

0xFE6C part3

0xFE70 part2

0xFE74 part1

這樣可以通過取得第一個參數的地址&part1和++操作分別訪問到後面的參數,但這必須是_cdecl函數調用規範,例如printf系列的庫函數(sprintf,fprintf)都是可以接受可變參數的函數。假設有下面一條語句:

printf("%d %d %d\n", m, n, k);

可以看出,我們可以通過第一個參數得到參數的個數(格式符的數目)和類型(例如%d),這就是為什麼printf("%d %d\n", m, n, k)可以成功執行,而printf("%d %d %d\n", m, n)會失敗的原因了。傳遞的參數如果多于格式符的個數可以忽略掉(在Linux下能正常執行),但是少於就會出現訪問越界(Linux下會警示告,但是仍然正常執行,越界的參數會是個隨機值)。(執行自己程式的時候記得加./,例如./a.out)

2.採用varargs宏來編寫支援可變參數列表的函數,在ASCI C標準裡,這些宏包含在stdarg.h。

例如以下代碼:

#include <stdio.h>

#include <stdarg.h>

void _cdecl fun(int n, ...);//可以不加_cdecl

int main(int argc, char *argv[])

{

     int part1 = 128;

     int part2 = 256;

     int part3 = 512;

     fun(part1, part2, part3);

     return 0;

}

void fun(int n, ...)

{

     va_list ap;

     va_start(ap, n);

     printf("%d\n", va_arg(ap, int));

     printf("%d\n", va_arg(ap, int));

     va_end(ap);

}

在Microsoft為VC提供的實現中,可以看到這樣的定義:

#define _ADDRESSOF(v) (&(v))

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

typedef char* va_list;

#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )//ap指向v下一個參數的地址

#define va_arg(ap,t) (*(t*)( (ap += _INTSIZEOF(t)) -  _INTSIZEOF(t)) )   //ap指向下一個參數的地址,但是返回的還    //是這個參數的值

#define va_end(ap) ( ap = (va_list)0 )

va_list 一個char型指標,每次單個位元組定址。

va_start 通過_INTSIZEOF計算參數類型大小,並讓ap獲得v後面參數對象的地址

va_arg ap指向參數列表中ap下一個參數對象,並返回ap之前指向的t型別參數對象

其中_INTSIZEOF(n)就是將n的長度化為int長度的整數倍。如果n是char類型,那麼sizeof(n)=1,化為int長度的整數倍就應該是4,假設sizeof(n)=4m+k(m>=0,k=0,1,2,3),sizeof(n)+4-1=4m+(k+3),~(sizeof(int)-1)=~(4-1)=~(00000011b)=11111100b,這樣任何數&~(sizeof(int)-1)後,後兩位肯定是0,那就是4的倍數了,(4m+(k+3))&~(sizeof(int)-1)可以保證能存放4m+k。

將這些宏還原重寫fun函數:

void fun(int part1, ...)

{

     int part2, part3;

     char *ap;//va_list ap;

     ap = (char *)&part1+4;//va_start(ap, part1), ap指向part2

     part2 = *(int *)(ap+=4 - 4);//返回part2,ap指向part3,part2=va_arg(ap,int)

     part3 = *(int *)(ap+=4 - 4);//返回part2,ap指向part3,part3=va_arg(ap,int)

     ap=(char *)0;//ap指向空,var_end(ap)

}

若part2為char,shor則會自動轉化成int型,float則會自動轉化成double型。

#include <stdio.h>
#include <stdarg.h>
void fun(int n, ...);

int main(int argc, char *argv[])
{
     int part1 = 128;
     float part2 = 256.0;//float
     float part3 = 512.0;
     fun(part1, part2, part3);
     return 0;
}
void fun(int n, ...)
{
     va_list ap;
     va_start(ap, n);
     printf("%f\n", va_arg(ap, double));//double正確,float錯誤,系統自動儲存為double型
     printf("%f\n", va_arg(ap, double));
     va_end(ap);
}

#include <stdio.h>
#include <stdarg.h>
void fun(int n, ...);

int main(int argc, char *argv[])
{
     int part1 = 128;
     char part2 = 'c';//char
     short part3 = 8;//short
     fun(part1, part2, part3);
     return 0;
}
void fun(int n, ...)
{
     va_list ap;
     va_start(ap, n);
     printf("%c\n", va_arg(ap, int));//char自動儲存為int,不能用char,即使這樣,仍然可用%c輸出字元
     printf("%d\n", va_arg(ap, int));//short自動儲存為int,不能用short
     va_end(ap);
}

以上這些問題都是記憶體對齊造成的。

3.vprintf,vfprintf,vsprintf,vsnprintf,vasprintf格式化輸出,有一個va_list參數

#include <stdio.h>

#include <stdarg.h>

int vprintf(const char *format, va_list ap);//格式化輸出到標準輸出,對應到printf

int vfprintf(FILE *stream, const char *format, va_list ap);//格式化輸出到檔案流,對應到fprintf

int vsprintf(char *s, const char *format, va_list ap);//格式化輸出到字串,對應到sprintf

int vsnprintf(char *s, size_t n, const char *format, va_list ap);//格式化輸出固定長度到字串,對應到snprintf

int vasprintf(char **ret, const char *format, va_list ap);//對應到asprintf

舉例,用vprintf實現error:

#include <stdio.h>

#include <stdarg.h>

void error(char *function_name, char *format, ...)

{

     va_list ap;

     va_start(ap, format);

     /*print out name of function causing error*/

     (void)fprintf(stderr, "ERR in %s:", function_name);

     /*print out remainder of message*/

     (void)vfprintf(stderr, format, ap);

     va_end(ap);

     (void)abort();

}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.