printf的聲明
int _cdecl printf(const char* format, …);
呼叫慣例:_cdecl
_cdecl呼叫慣例的特點:
1). 參數從右向左依次入棧
2). 調用者負責清理堆棧
3). 參數的數量類型不會導致編譯階段的錯誤
對x86, 棧的生長方向向下(高地址向低地址),_cdecl呼叫慣例函數參數從右向左入棧,因此從第一個固定參數(format)的堆棧地址向前(向上,向高地址)移動就可得到其他變參的地址。
1: va_list、va_start、va_arg、va_end對可變參數的支援
va_list、va_start、va_arg、va_end相關宏可以在stdarg.h中找到。
// _INTSIZEOF(n)宏:將sizeof(n)按sizeof(int)對齊。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
// 取format參數之後的第一個變參地址,4位元組對齊
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
// 對type類型資料,先取到其四位元組對齊地址,再取其值
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
// 將指標置為無效
#define va_end(ap) ( ap = (va_list)0 )
2: va_list的用法
1). 首先在函數裡定義一具va_list型的變數,這個變數是指向參數的指標
2). 然後用va_start宏初始設定變數剛定義的va_list變數,這個宏的第二個參數是第一個可變參數的前一個參數,也就是最後一個可變參數。
3). 然後用va_arg返回可變的參數,va_arg的第二個參數是你要返回的參數的類型。
4). 最後用va_end宏結束可變參數的擷取。然後你就可以在函數裡使用第二個參數了。如果函數有多個可變參數的,依次調用va_arg擷取各個參數。
3: 函數調用棧結構
_cdecl呼叫慣例下,當執行一個函數的時候,將參數列表(由右向左)入棧,壓入堆棧的高地址部分,然後入棧函數的返回地址,接著入棧函數的執行代碼。
函數在堆棧中的分布情況是:地址從高到低, 依次是:函數參數列表,函數返回地址,函數執行程式碼片段.
在堆棧中,分布情況如下:
高地址 –> 最後一個參數
| 倒數第二個參數
| ...
| 第一個參數
| 函數返回地址
低地址 –> 函數程式碼片段
比如Z函數調用A函數,棧的擴充次序如下所示:
1). 自右向左(_cdecl約定)壓入參數
2). Z的返回地址,也就是A執行完後,返回Z中的下一條指令地址
3). 調用者的EBP。由編譯器插入指令實現:
"pushl %ebp"
"movl %esp, %ebp" //esp為棧指標
因而形成一個鏈表。依此可得到調用者的棧頂位置(對於A的EBP,得到Z的EBP地址為0x000f)。
4). 局部變數。
隨著棧的擴充,棧頂指標由高地址向低地址移動。
4: 可變參數支援例子
1: #include "stdafx.h"
2: #include <iostream>
3: #include <stdarg.h>
4: using namespace std;
5:
6: void arg_test(int i, ...);
7: void arg_cnt(int cnt, ...);
8:
9: int main(int argc,char *argv[]) {
10:
11: int int_size = _INTSIZEOF(int);
12: printf(" int_size = %d \n", int_size);
13: arg_test(0, 4);
14: arg_cnt(4,1,2,3,4);
15: return 0;
16: }
17: void arg_test(int i, ...) {
18: int j=0;
19:
20: // 1: 首先在函數裡定義一具va_list型的變數,這個變數是指向參數的指標
21: va_list arg_ptr;
22:
23: // 2: 然後用va_start宏初始設定變數剛定義的va_list變數,
24: // 這個宏的第二個參數是第一個可變參數的前一個參數,也就是最後一個固定的參數。
25: // va_start返回的是一個可變參數地址
26: va_start(arg_ptr, i);
27:
28: //列印參數i在堆棧中的地址
29: printf(" &i = %p \n", &i);
30: // 調用va_start之後arg_ptr指向第一個可變參數,比參數i的地址高sizeof(int)個位元組
31: printf(" arg_ptr = %p \n", arg_ptr);
32:
33: j=*((int *)arg_ptr);
34: printf(" %d %d \n", i, j);
35:
36: // 3. 然後用va_arg返回下一個可變的參數地址,va_arg的第二個參數是你要返回的參數的類型。
37: j = va_arg(arg_ptr, int);
38:
39: // 調用va_arg指向,arg_ptr指向下一個可變參數地址
40: printf(" arg_ptr = %p \n", arg_ptr);
41:
42: // va_end宏結束可變參數的擷取
43: va_end(arg_ptr);
44:
45: printf(" %d %d\n", i, j);
46: }//arg_test
47:
48: // 利用第一個參數指定可變參數數目
49: void arg_cnt(int cnt, ...) {
50: int value=0;
51: int i=0;
52: int arg_cnt = cnt;
53: va_list arg_ptr;
54: va_start(arg_ptr, cnt);
55:
56: for(i = 0; i < cnt; i++) {
57: value = va_arg(arg_ptr,int);
58: printf(" value %d = %d \n", i+1, value);
59: }//for
60: }//arg_cnt
5: printf的相關問題
1: int i = 0;
2: printf(" %d , %d \n",i,i++); // 輸出1,0
3: i = 0;
4: printf(" %d , %d \n",i,++i); // 輸出1,1
5:
6: int a[]={0,1,2,3,4};
7: int *int_ptr = a;
8: (*int_ptr++) += 111; // 等價於:*int_ptr= *int_ptr + 111; int_ptr += 1;
9: printf(" %d , %d \n",*int_ptr,*(int_ptr--)); // 輸出111,1
對於printf,首先入棧的是*(int_ptr—),入棧的是1,之後int_ptr指向位置0,之後*int_ptr入棧,入棧的是111.所以printf輸出的是 : 111,1.