C语言中的可变参数-printf的实现原理

在C/C++中,对函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了,下面给出printf("%d,%d",a,b);(其中a、b都是int型的)的汇编代码.

.section
.data
string out = "%d,%d"
push b  //最后的先压入栈中
push a //最先的后压入栈中
push $out//参数控制的那个字符串常量是最后被压入的
call printf
 
你会看到,参数是最后的先压入栈中,最先的后压入栈中,参数控制的那个字符串常量是最后被压入的,所以这个常量总是能被找到的。
 
通常情况下函数可变参数表的长度是已知的,通过num参数传入,这种函数比较容易实现。
而printf函数的实现非常复杂因为
1)可变参数的个数不能轻易的得到
2)而可变参数的类型也不是固定的,需由格式字符串进行识别(由%f、%d、%s等确定)
在这个函数中,需通过对传入的格式字符串(首地址为lpStr)进行识别来获知可变参数个数及各个可变参数的类型,具体实现体现在for循环中。譬如,在识别为%d后,做的是va_arg ( vap, int ),而获知为%l和%lf后则进行的是va_arg ( vap, long )、va_arg ( vap, double )。格式字符串识别完成后,可变参数也就处理完了。
 
 
printf的简单实现:
#include
#include void myitoa(int n, char str[], int radix)
{
int i , j , remain;
char tmp;
i = ;
do
{
remain = n % radix;
if(remain > )
str[i] = remain - + 'A';
else
str[i] = remain + '';
i++;
}while(n /= radix);
str[i] = '\0'; for(i-- , j = ; j <= i ; j++ , i--)
{
tmp = str[j];
str[j] = str[i];
str[i] = tmp;
} } void myprintf(const char *format, ...)
{
char c, ch, str[];
va_list ap; va_start(ap, format);
while((c = *format))
{
switch(c)
{
  case '%':
  ch = *++format;
  switch(ch)
  {
    case 'd':
    {
    int n = va_arg(ap, int);
    myitoa(n, str, );
    fputs(str, stdout);
    break;
    }
    case 'x':
    {
      int n = va_arg(ap, int);
      myitoa(n, str, );
      fputs(str, stdout);
      break;
    }
    case 'f':
    {
      double f = va_arg(ap, double);
      int n;
      n = f;
      myitoa(n, str, );
      fputs(str, stdout);
      putchar('.');
      n = (f - n) * ;
      myitoa(n, str, );
      fputs(str, stdout);
      break;
    }
    case 'c':
    {
      putchar(va_arg(ap, int));
      break;
    }
    case 's':
    {
      char *p = va_arg(ap, char *);
      fputs(p, stdout);
      break;
    }
    case '%':
    {
      putchar('%');
      break;
    }
    default:
    {
      fputs("format invalid!", stdout);
      break;
    }
  }
  break;
  default:
    putchar(c);
    break;
  }
  format++;
}
va_end(ap);
} int main(void)
{
myprintf("%d, %x, %f, %c, %s, %%,%a\n", , , 3.14, 'B', "hello");
return ;
}
from:http://blog.csdn.net/hackbuteer1/article/details/7558979

C语言中的可变参数-printf的实现原理的更多相关文章

  1. C语言中的可变参数函数的浅析(以Arm 程序中的printf()函数实现为例) .

    我们在C语言编程中会遇到一些参数个数可变的函数,一般人对它的实现不理解.例如Printf(): Printf()函数是C语言中非常常用的一个典型的变参数函数,它 的原型为: int printf( c ...

  2. C语言中函数可变参数解析

    大多数时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数.但在某些情况下希望函数的参数个数可以根据需要确定.典型的例子有 大家熟悉的函数printf().scanf ...

  3. C语言中的可变参数函数

    C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...

  4. c语言中的# ## 可变参数宏 ...和_ _VA_ARGS_ _

    1.#假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串.例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字 ...

  5. 各种语言中的可变参数(java、python、c++、javascript)

    索引: java python c++ js 1.Java public class Animal { // 接受可变参数的方法 void eat(String... Objects) { for ( ...

  6. C函数和宏中的可变参数

    一:调用惯例 函数的调用方和被调用方对函数如何调用应该有统一的理解,否则函数就无法正确调用.比如foo(int n, int m),调用方如果认为压栈顺序是m,n,而foo认为压栈顺序是n, m,那么 ...

  7. Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数

    Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数 7.4.4  为外部参数设置默认值 开发者也可以对外部参数设置默认值.这时,调用的时候,也可以省略参数传递本文选自Swift1 ...

  8. C# 中的可变参数方法(VarArgs)

    首先需要明确一点:这里提到的可变参数方法,指的是具有 CallingConventions.VarArgs 调用约定的方法,而不是包含 params 参数的方法.可以通过MethodBase.Call ...

  9. 【转】C,C++中使用可变参数

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是 int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处 ...

随机推荐

  1. Kotlin 中的伴生对象和静态成员

    用了一段时间kotlin,越用越觉得好用,爱不释手啊,留点笔记. Kotlin 中,在类中定义的对象(object)声明,可使用 companion 修饰,这样此对象(object)就是伴生对象了.类 ...

  2. 使用ipython %matplotlib inline

    首先讲讲这句话的作用,matplotlib是最著名的Python图表绘制扩展库,它支持输出多种格式的图形图像,并且可以使用多种GUI界面库交互式地显示图表.使用%matplotlib命令可以将matp ...

  3. Jmeter插件图表分析

    1.jp@gc - Actiive Threads Over Time:不同时间的活动用户数量展示(图表) 当前的时间间隔是1毫秒,在setting中可以设置时间间隔以及其他的参数,右击可以导出exc ...

  4. luoguP4779 【模板】单源最短路径

    题目描述 单源最短路径模板. 使用 SPFA 肯定是不行的啦,网格图hack. 所以我们使用 Dijkstra 算法. 这里有一篇写的很好的 blog,无必要赘述.最后贴上代码. #include&l ...

  5. 高通电源管理函数的power_supply的调用关系

    以msm8909为例,高通的主要文件有几个: qpnp-linear-charger.c(线性充电器) qpnp-vm-bms.c(BMS管理) power_supply_core.c(power_s ...

  6. nginx::基于Nginx+nginx-rtmp-module+ffmpeg搭建rtmp、hls流媒体服务器

    待续 ffmpeg -re -i "/home/bk/hello.mp4" -vcodec libx264 -vprofile baseline -acodec aac -ar 4 ...

  7. php和apache的关系是什么?

    例如我们在自己的电脑里练习如何做网站的时候,我们需要搭建一个本地环境,这个本地环境就是PHP+APACHE + MYSQL的环境,这样就可以让我们的电脑拥有PHP要求的运行环境了.那么php和apac ...

  8. PHP代码审计基础-高级篇

    高级篇主要讲 1. 熟知各个开源框架历史版本漏洞. 2. 业务逻辑漏洞 3. 多线程引发的漏洞 4. 事务锁引发的漏洞 在高级篇审计中有很多漏洞正常情况下是不存在的只有在特殊情况下才有 PHP常用框架 ...

  9. .Net Core 3.0 IdentityServer4 快速入门02

    .Net Core 3.0 IdentityServer4 快速入门 —— resource owner password credentials(密码模式) 一.前言 OAuth2.0默认有四种授权 ...

  10. git jenkins SonarQube手动代码质检

    SonarQube代码质检:1.提交代码-->gitlab-->jenkins抓取-->sonarqube质量检测-->maven编译-->shell-->web集 ...