c语言中printf函数参数个数可变实现原理

我们在使用printf函数时会发现,printf函数与其他函数最大的区别就在于该函数可以接收不同数量的参数,如下:

printf("%d",1);
printf("%d,%d",1,2);

那么这是怎么实现的呢?

原因在于printf函数使用了<stdarg.h>库,这个库是c语言内置的标准库,专门用来处理参数数量可变的情况

而且处理参数数量可变情况一般写法是这样的:

int fun(int args,...)

这里args指后面跟着的参数数量,后面的… 表示可变参数(variadic arguments)。它允许一个函数接受数量可变的参数,即可以接受不定数量的参数。

假如你去stdio.h查找printf函数,你会发现printf函数声明是这样的:

extern int printf (const char *__restrict __format, ...);

第一个参数是一个字符串,后面的省略号就是可变参数。

接下来我们自己实现一个带可变参数的函数,主要使用的就是stdarg.h库里的va_list,va_start(), va_arg(), va_end()。示例:

#include <stdio.h>
//一定要包含这个库
#include <stdarg.h>
//一定要带省略号,代表可变参数
void fun(int args,...){
    va_list va;
    //只有带省略号的函数才能调用va_start函数,否则会报错
    va_start(va,args);
    for(int i=0;i<args;i++){
        printf("%d\n",va_arg(va,int));
    }
    va_end(va);
}

int main(){
    //第一个2指后面跟2个参数
    fun(2,1,2);
    fun(3,1,2,3);
    return 0;
}

输出:
1
2
1
2
3

逐行分析:

  • void fun(int args,…) 定义一个带可变参数的函数,省略号代表可变参数,注意省略号前一定要至少有一个参数,因为 va_start 宏需要一个确定的参数来定位变长参数列表。
  • va_list va; 声明一个变长参数列表va,va_list 来自stdarg.h 库,是用来存储省略号…代表的参数列表的。
  • va_start(va,args); 这个函数意思是让va指向args后面的可变参数列表,也就是省略号代表的起始地址,假如函数是void fun(int args,int a,…),省略号…前是a, 那么这里就应该改成va_start(va,a)。
  • 后面的for循环就是根据第一个参数的多少循环打印后面跟着的参数,va_arg(va,int)就是指从va里以int类型取出一个参数,其参数指针会自动递增。
  • va_end(args) 用于结束对可变参数列表的访问。

可以看到,写法基本是比较固定的,就是定义va_list, 然后初始化va_start,va_arg取出参数,va_end结束访问。

那么问题来了,假如你可变参数只有3个,但你却调用了四次va_arg取出参数,那么可能会访问越界内存,虽然不会报错,但会得到一个未知的数据。

va_list 本身并不直接包含参数个数的信息,因为可变参数列表在函数内部并没有一个固定的结构或长度信息。这也就是为什么我们第一个参数一般都是后面可变参数的个数,就是为了确保不会越界

可是printf第一个参数是字符串,并不知道后面可变参数的个数,它是怎么确保va_list不越界的呢?

原因是通过你字符串里的格式化输出个数来确保不越界的,比如printf(“%d,%s”,a,b),检测到%d就调用一次va_arg取出参数,

检测到%s就调用一次va_arg取出参数,这样就不会越界了。

当然,如果你格式化输出个数和后面的参数个数不匹配,比如printf(“%d,%c\n”,1),这样虽然编译器会警告,但实际也能编译通过,

但结果就是会调用两次va_arg取出两个参数,但实际后面只跟了一个1,结果就是访问越界输出一个奇怪的结果。

相关推荐

最近更新

  1. Robot Operating System——借用内存型消息

    2024-07-09 17:42:02       0 阅读
  2. B树(B-Tree)详解

    2024-07-09 17:42:02       0 阅读
  3. IPython与Pandas:数据分析的动态组

    2024-07-09 17:42:02       0 阅读
  4. SSR和SPA渲染模式

    2024-07-09 17:42:02       0 阅读
  5. 《流程引擎原理与实践》开源电子书

    2024-07-09 17:42:02       0 阅读
  6. 2742. 给墙壁刷油漆

    2024-07-09 17:42:02       0 阅读
  7. longjmp和多线程:读写线程实例

    2024-07-09 17:42:02       0 阅读
  8. 【CF】1216F-WiFi 题解

    2024-07-09 17:42:02       0 阅读
  9. 牛客周赛 Round 52VP(附D的详细证明)

    2024-07-09 17:42:02       0 阅读
  10. Android13 应用代码中修改热点默认密码

    2024-07-09 17:42:02       0 阅读

热门阅读

  1. FFmpeg——视频拼接总结

    2024-07-09 17:42:02       7 阅读
  2. php框架详解-symfony框架

    2024-07-09 17:42:02       9 阅读
  3. 红黑树,B+树,B树的结构原理及对比

    2024-07-09 17:42:02       5 阅读
  4. 代码随想录算法训练营:21/60

    2024-07-09 17:42:02       8 阅读
  5. Vue3 对于内嵌Iframe组件进行缓存

    2024-07-09 17:42:02       4 阅读
  6. Kafka 面试题指南

    2024-07-09 17:42:02       9 阅读
  7. vue3 插件

    2024-07-09 17:42:02       6 阅读