深度探索va_start、va_arg、va_end
?采用C語言編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的所有實際參數。但在某些情況下希望函數的參數個數可以根據需要確定。典型的例子有大家熟悉的函數printf()、scanf()和系統調用execl()等。那么它們是怎樣實現的呢?
? ? C編譯器通常提供了一系列處理這種情況的宏,以屏蔽不同的硬件平臺造成的差異,增加程序的可移植性。這些宏包括va_start、va_arg和va_end等。在講解以上宏之前我們先了解一下調用函數時傳入參數的處理過程。
一、函數傳入參數過程
? ? 一個函數包括函數名、傳入參數、返回參數以及函數體,函數體編譯后的二進制代碼儲存在程序代碼區。當用戶調用某個函數時,系統會通過函數名(C++中會涉及到mangled命名處理)查找函數體入口指針并壓入棧中,之后將傳入參數以從右至左的順序壓入棧中(棧空間是往低地址方向增長的,也即棧底對應高地址,棧頂對應低地址),示例如下:
#include <iostream>
using?namespace?std;
void?fun(int?a, ...)
{
? ?int?*temp = &a;
? ? temp++;
? ?for?(int?i =?0; i < a; ++i) { cout<< *temp <<?endl;
? ? ? temp++;
? ? }
}
int?main()
{
? ?int?a =?1;
? ?int?b =?2;
? ?int?c =?3;
? ?int?d =?4;
? ? fun(4, a, b, c, d);
? ? system("pause");
? ?return?0;
}
//?Output:
//?1
//?2
//?3
//?4
二、va_start、va_arg和va_end宏定義
? 接著我們再來看看va_start、va_arg和va_end等宏的具體定義。
#define?_INTSIZEOF(n)? ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )?//?此句宏的作用是將類型n的大小向上取成4的倍數,如n為char型的話結果即為4
#ifdef? __cplusplus
#define?_ADDRESSOF(v)? ( &reinterpret_cast<const char &>(v) )?//?vs2015中此句高亮,作用是將v的地址重新解釋成char*型
#else
#define?_ADDRESSOF(v)? ( &(v) )
#endif
#define?_crt_va_start(ap,v)? ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define?_crt_va_arg(ap,t)? ? ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define?_crt_va_end(ap)? ? ? ( ap = (va_list)0 )
#ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedefchar?*? va_list;? ? ??//?vs2015中此句高亮
#endif?/* _M_CEE_PURE */
#define?_VA_LIST_DEFINED
#endif
//?以上宏定義出現在vadef.h,通過stdio.h即可使用
三、va_start、va_arg和va_end使用示例
? ? 容易看出,以上宏定義主要涉及地址操作,va_start獲取第二個傳入參數的地址給va_list類型變量(假設為arg_ptr),va_arg用于獲取當前參數值并將指針arg_ptr往后移,va_end則是將arg_ptr置為空。va_start、va_arg、va_end具體用法示例如下:
#include <stdio.h>
#include<stdarg.h>
int?fun(int?x,?int?y) {
? ?return?x -?y;
}
int?fun(int?count, ...) {
? ? va_list arg_ptr;? ? ??//?等同于 char *arg_ptr;
? ??int?nArgValue =?count;
? ?int?nArgCout =?0;?
? ? va_start(arg_ptr, count);?//?使arg_ptr指向第二個參數的地址
? ? printf("The 1 th arg: %d\n", nArgValue);? ??//?輸出第一個參數的值
? ??int?sum =?0;
? ?for?(int?i =?0; i < count; i++)
? ? {
? ? ? ?++nArgCout;
? ? ? ? nArgValue= va_arg(arg_ptr,?int);//?將arg_ptr所指參數返回成int并移動arg_ptr使其指向后一個參數,這里假設傳入參數均是int型
? ? ? ? printf("The %d th arg: %d\n", i+2, nArgValue);??//?輸出各參數的值
? ? ? ? sum +=?nArgValue;
? ? }
? ? va_end(arg_ptr);? ?//?將arg_ptr置為空
? ??return?sum;
}
int?main() {
? ? cout<< fun(0) <<?endl;
? ? cout<< fun(1,?1) << endl;??//?優先匹配到函數int fun(int, int), 輸出0
? ? cout << fun(2,?3,?4) <<?endl;
? ?system("pause");
? ? return?0;
}
? ? 注意:以上宏操作并不提供參數個數獲取操作,這需要用戶在函數中獲取,如第二個fun函數使用count指明個數,printf通過解析第一個傳入參數來確定參數個數與類型等。
總結
以上是生活随笔為你收集整理的深度探索va_start、va_arg、va_end的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql启动startpost_(转)
- 下一篇: Linux下远程连接断开后如何让程序继续