当前位置: 首页 > news >正文

Variadic function

要深入理解 C 语言中的可变参数机制,关键是了解 <stdarg.h> 头文件中定义的宏(如 va_listva_startva_argva_end)以及这些宏背后的原理。虽然它们在不同的系统上可能有不同的实现,但它们的工作原理是基于栈的内存布局和指针运算。

可变参数的基本原理

在C语言中,当一个函数被调用时,参数通常会依次压入栈中(堆栈的增长方向因体系结构而异)。对于可变参数函数(如 `printf`),编译器无法在编译时确定函数被调用时会传递多少个参数,因此需要一种机制在运行时遍历这些参数。

栈帧的布局(假设栈从高地址向低地址增长):向下增长

+-----------------------+   <- 高地址
|        参数 1          |
+-----------------------+
|        参数 2          |
+-----------------------+
|        参数 3          |
+-----------------------+
|   返回地址 (ret addr)  |   <- 栈顶
+-----------------------+   <- 低地址

在这种布局下,第一个参数(假设是 int a)位于栈的最低位置,最后一个参数位于最高位置。
第一个参数为低地址

可变参数的宏定义

va_list

typedef char* va_list;

在某些实现中,va_list 实际上只是一个指向栈中某个位置的指针,也可能是更复杂的结构,用于记录当前参数的位置和相关信息。

va_start

#define va_start(ap, last) \(ap = (va_list)&last + sizeof(last))
  • va_start 的目的是初始化 va_list 变量 ap。它将 ap 设置为指向最后一个已知参数(last)之后的位置,即第一个可变参数的起始位置。
  • &last 返回 last 在栈中的地址,加上 sizeof(last) 就是下一个参数的地址。

va_arg

#define va_arg(ap, type) \(*(type *)((ap += sizeof(type)) - sizeof(type)))
  • va_arg 用于从 va_list 中获取当前参数,并将 ap 移动到下一个参数的位置。
  • (ap += sizeof(type)) 先将 ap 移动到下一个参数的位置。
  • sizeof(type) 再将指针向回移动一个 type 大小的步长,指向当前参数。
  • *(type *) 将指针解引用,返回该类型的参数值。

示例

int arg = va_arg(ap, int);

  • 这段代码会返回当前 ap 指向的 int 值,并将 ap 移动到下一个参数的位置。

va_end

#define va_end(ap) \ (ap = (va_list)0)

  • va_end 用于清理 va_list 变量。通常只是将 ap 置为 NULL(或 0),在某些复杂的实现中可能会执行其他清理工作。

可变参数的内存操作

  • va_start:通过 va_startap 被设置为指向第一个可变参数的位置,ap 的初始值基于最后一个已知参数 last 的地址。
  • va_arg:每次调用 va_argap 都会向上移动,指向下一个参数。ap 实际上就是一个指针,它在栈中遍历每个参数的位置。
  • va_end:清理 va_list,通常只是将 ap 置为 NULL

不同体系结构上的实现差异

在不同的 CPU 体系结构上,函数参数可能会通过不同的方式传递,比如通过寄存器或栈。在这些情况下,<stdarg.h> 中的宏实现可能会有所不同,以适应具体的调用约定(calling conventions)。

  • 栈传递:在大多数体系结构中,参数是通过栈传递的,因此上述的宏定义可以直接使用。
  • 寄存器传递:在某些体系结构中,前几个参数可能会通过寄存器传递。这种情况下,va_list 可能不仅仅是一个指针,还需要保存寄存器的状态。va_start 可能会将 va_list 初始化为指向寄存器中的参数,然后在寄存器用完时切换到栈参数。

可变参数的使用示例

考虑一个可变参数函数 printf,它的实现会通过 va_list 和相关宏遍历所有传入的参数,并根据格式字符串进行处理。

#include <stdio.h>
#include <stdarg.h>void my_printf(const char *fmt, ...) {va_list args;va_start(args, fmt);const char *p;for (p = fmt; *p != '\0'; p++) {if (*p == '%') {p++;switch (*p) {case 'd': {int i = va_arg(args, int);printf("%d", i);break;}case 'c': {char c = va_arg(args, int); // char 提升为 intputchar(c);break;}case 's': {char *s = va_arg(args, char*);printf("%s", s);break;}default:putchar(*p);break;}} else {putchar(*p);}}va_end(args);
}int main() {my_printf("Integer: %d, Char: %c, String: %s\n", 42, 'A', "Hello");return 0;
}

http://www.mrgr.cn/news/50128.html

相关文章:

  • Russ Cox谈Go项目技术负责人的交接
  • ubuntu使用overlay
  • 基于LSTM-Transformer混合模型实现股票价格多变量时序预测(PyTorch版)
  • spark:数据的关联与合并、缓存和checkpoint
  • 二叉树的遍历
  • npm-run-all 使用实践
  • Ubuntu系统下的用户管理
  • 【C++】二叉搜索树的概念与实现
  • 面试十分钟不到就被赶出来了,问的实在是太变态了...
  • 周易解读:两仪01
  • 高德地图怎么定位自己的店铺位置?
  • VHDL基本结构和逻辑示例
  • libevent_structure
  • 图像及视频的基本操作
  • 西门子S7-200 SMART选型指南之电源需求
  • SQL Server LocalDB 表数据中文乱码问题
  • 代码训练营 day32|LeetCode 122,LeetCode 55,LeetCode 45,LeetCode 1005
  • 金融信用评分卡建模项目:AI辅助
  • 【深圳大学/大学物理实验2】超声探伤实验 实验前预习题答案参考
  • 数据结构期中考试复习(二叉树及之前)1-12