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.