[c&cpp] printf原理与变参支持

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调用约定下,当执行一个函数的时候,将参数列表(由右向左)入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码。
    函数在堆栈中的分布情况是:地址从高到低,  依次是:函数参数列表,函数返回地址,函数执行代码段.
    在堆栈中,分布情况如下:
        高地址 –>  最后一个参数  
            |          倒数第二个参数
            |          ...
            |          第一个参数
            |          函数返回地址
       低地址 –>   函数代码段

   image

  比如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.

posted @ 2011-03-24 14:00  zsounder  阅读(1079)  评论(0编辑  收藏  举报