《2022年从printf谈可变参数的实现 .pdf》由会员分享,可在线阅读,更多相关《2022年从printf谈可变参数的实现 .pdf(6页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、从 printf谈 可 变 参 数 函 数 的 实 现作 者 : 戎 亚 新摘 要 :一 直 以 来 都 觉 得 printf似 乎 是 c 语 言 库 中 功 能 最 强 大 的 函 数 之 一 ,不 仅 因为 它 能 格 式 化 输 出 , 更 在 于 它 的 参 数 个 数 没 有 限 制 , 要 几 个 就 给 几 个 , 来 者 不 拒 。printf这 种 对 参 数 个 数 和 参 数 类 型 的 强 大 适 应 性 ,让 人 产 生 了 对 它 进 行 探 索 的 浓厚 兴 趣 。关 键 字 : printf, 可 变 参 数1. 使 用 情 形int a =10; doubl
2、e b = 20.0; char *str = Hello world; printf(begin printn); printf(a=%d, b=%.3f, str=%sn, a, b, str); . 从 printf的 使 用 情 况 来 看 ,我 们 不 难 发 现 一 个 规 律 ,就 是 无 论 其 可 变 的 参 数有 多 少 个 , printf的 第 一 个 参 数 总 是 一 个 字 符 串 。 而 正 是 这 第 一 个 参 数 , 使 得 它可 以 确 认 后 面 还 有 有 多 少 个 参 数 尾 随 。 而 尾 随 的 每 个 参 数 占 用 的 栈 空 间 大 小
3、 又 是通 过 第 一 个 格 式 字 符 串 确 定 的 。然 而 printf到 底 是 怎 样 取 第 一 个 参 数 后 面 的 参 数值 的 呢 , 请 看 如 下 代 码2. printf 函 数 的 实 现/acenv.h typedef char *va_list; #define _AUPBND (sizeof (acpi_native_int) - 1) #define _ADNBND (sizeof (acpi_native_int) - 1) #define _bnd(X, bnd) (sizeof (X) + (bnd) & (bnd) #define va_arg(
4、ap, T) (*(T *)(ap) += (_bnd (T, _AUPBND) - (_bnd (T,_ADNBND) #define va_end(ap) (void) 0 #define va_start(ap, A) (void) (ap) = (char *) &(A) + (_bnd (A,_AUPBND) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 6 页 - - - - - - - - - /start.c static char sprint_buf
5、1024; int printf(char *fmt, .) va_list args; int n; va_start(args, fmt); n = vsprintf(sprint_buf, fmt, args); va_end(args); write(stdout, sprint_buf, n); return n; /unistd.h static inline long write(int fd, const char *buf, off_t count) return sys_write(fd, buf, count); 3. 分 析从 上 面 的 代 码 来 看 ,printf
6、似 乎 并 不 复 杂 ,它 通 过 一 个 宏 v a_start把 所 有 的可 变 参 数 放 到 了 由 args指 向 的 一 块 内 存 中 ,然 后 再 调 用 v sprintf. 真 正 的 参 数 个数 以 及 格 式 的 确 定 是 在 v sprintf搞 定 的 了 。由 于 v sprintf的 代 码 比 较 复 杂 ,也 不是 我 们 这 里 要 讨 论 的 重 点 , 所 以 下 面 就 不 再 列 出 了 。 我 们 这 里 要 讨 论 的 重 点 是v a_start(ap, A)宏 的 实 现 ,它 对 定 位 从 参 数 A 后 面 的 参 数 有 重
7、 大 的 制 导 意 义 。现在 把#define va_start(ap, A) (void) (ap) = (char *) &(A) + (_bnd (A,_AUPBND) 的 含 义 解 释 一 下 如 下 :va_start(ap, A) char *ap = (char *)(&A) + sizeof(A)并 int类型 大 小地 址对 齐 在 printf的 va_start(args, fmt)中 , fmt的 类 型 为 char *, 因 此 对 于 一 个 32为 系 统sizeof(char *) = 4, 如 果 int 大 小 也 是 32 , 则 v a_star
8、t(args, fmt);相 当于char *args = (char *)(&fmt) + 4; 此 时 args 的 值 正 好 为 fmt后 第 一 个 参 数的 地 址 。 对 于 如 下 的 可 变 参 数 函 数void fun(double d,.) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 6 页 - - - - - - - - - va_list args; int n; va_start(args, d); 则v a_start(args, d)
9、;相 当 于char *args = (char *)&d + sizeof(double); 此 时 args正 好 指 向 d 后 面 的 第 一 个 参 数 。可 变 参 数 函 数 的 实 现 与 函 数 调 用 的 栈 结 构 有 关 ,正 常 情 况 下 c/c+的 函 数 参数 入 栈 规 则 为 _stdcall, 它 是 从 右 到 左 的 , 即 函 数 中 的 最 右 边 的 参 数 最 先 入 栈 。对 于 函 数void fun(int a, int b, int c) int d; . 其 栈 结 构 为0 x1ffc-d 0 x2000-a 0 x2004-b 0
10、 x2008-c 对 于 任 何 编 译 器 ,每 个 栈 单 元 的 大 小 都 是 sizeof(int), 而 函 数 的 每 个 参 数 都至 少 要 占 一 个 栈 单 元 大 小 , 如 函 数void fun1(char a, int b, double c, short d) 对 一 个 32 的 系 统 其 栈 的 结 构 就 是0 x1ffc-a (4 字节) 0 x2000-b (4 字节) 0 x2004-c (8 字节) 0 x200c-d (4 字节) 对 于 函 数 v oid fun1(char a, int b, double c, short d) 如 果
11、知 道 了 参 数 a 的 地 址 ,则 要 取 后 续 参 数 的 值 则 可 以 通 过 a 的 地 址 计 算 a后 面 参 数 的 地 址 , 然 后 取 对 应 的 值 , 而 后 面 参 数 的 个 数 可 以 直 接 由 变 量 a 指 定 ,当 然 也 可 以 像 printf一 样 根 据 第 一 个 参 数 中 的 % 模 式 个 数 来 决 定 后 续 参 数 的 个 数和 类 型 。如 果 参 数 的 个 数 由 第 一 个 参 数 a 直 接 决 定 ,则 后 续 参 数 的 类 型 如 果 没 有变 化 并 且 是 已 知 的 , 则 我 们 可 以 这 样 来 取
12、 后 续 参 数 , 假 定 后 续 参 数 的 类 型 都 是double; void fun1(int num, .) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 6 页 - - - - - - - - - double *p = (double *)(&num)+1); double Param1 = *p; double Param2 = *(p+1); . double Paramn *(p+num); 如 果 后 续 参 数 的 类 型 是 变 化 而 且
13、 是 未 知 的 , 则 必 须 通 过 一 个 参 数 中 设 定 模 式来 匹 配 后 续 参 数 的 个 数 和 类 型 , 就 像 pr intf一 样 , 当 然 我 们 可 以 定 义 自 己 的 模 式 ,如 可 以 用 i 表 示 int 参 数 , d 表 示 double参 数 , 为 了 简 单 , 我 们 用 一 个 字 符 表 示一 个 参 数 , 并 由 该 字 符 的 名 称 决 定 参 数 的 类 型 而 字 符 的 出 现 的 顺 序 也 表 示 后 续 参数 的 顺 序 。我 们 可 以 这 样 定 义 字 符 和 参 数 类 型 的 映 射 表 ,i-in
14、t s-signed short l-long c-char ild 模 式 用 于 表 示 后 续 有 三 个 参 数 , 按 顺 序 分 别 为 int, long, double类 型 的 三 个参 数 那 么 这 样 我 们 可 以 定 义 自 己 版 本 的 printf 如 下void printf(char *fmt, .) char s80 = ; int paramCount = strlen(fmt); write(stdout, paramCount = , strlen(paramCount = ); itoa(paramCount,s,10); write(stdou
15、t, s, strlen(s); char *p = (char *)(&fmt) + sizeof(char *); int *pi = (int *)p; for (int i=0; iparamCount; i+) char line80 = ; strcpy(line, param); itoa(i+1, s, 10); strcat(line, s); strcat(line, =); switch(fmti) case i: case s: 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - -
16、 - - - 第 4 页,共 6 页 - - - - - - - - - itoa(*pi),s,10); strcat(line, s); pi+; break; case c: int len = strlen(line); linelen = (char)(*pi); linelen+1 = 0; break; case l: ltoa(*(long *)pi),s,10); strcat(line, s); pi+; break; default: break; 也 可 以 这 样 定 义 我 们 的 Max 函 数 , 它 返 回 多 个 输 入 整 型 参 数 的 最 大 值int
17、 Max(int n, .) int *p = &n + 1; int ret = *p; for (int i=0; in; i+) if (ret *(p + i) ret = *(p + i); return ret; 可 以 这 样 调 用 , 后 续 参 数 的 个 数 由 第 一 个 参 数 指 定int m = Max(3, 45, 12, 56); int m = Max(1, 3); int m = Max(2, 23, 45); 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - -
18、 - - 第 5 页,共 6 页 - - - - - - - - - int first = 34, second = 45, third=5; int m = Max(5, first, second, third, 100, 4); 结 论对 于 可 变 参 数 函 数 的 调 用 有 一 点 需 要 注 意 , 实 际 的 可 变 参 数 的 个 数 必 须 比 前面 模 式 指 定 的 个 数 要 多 ,或 者 不 小 于 , 也 即 后 续 参 数 多 一 点 不 要 紧 ,但 不 能 少 ,如 果 少 了 则 会 访 问 到 函 数 参 数 以 外 的 堆 栈 区 域 ,这 可 能
19、 会 把 程 序 搞 崩 掉 。前 面 模式 的 类 型 和 后 面 实 际 参 数 的 类 型 不 匹 配 也 有 可 能 造 成 把 程 序 搞 崩 溃 , 只 要 模 式 指定 的 数 据 长 度 大 于 后 续 参 数 长 度 , 则 这 种 情 况 就 会 发 生 。 如 :printf(%.3f, %.3f, %.6e, 1, 2, 3, 4); 参 数 1, 2, 3, 4 的 默 认 类 型 为 整 型 , 而 模 式 指 定 的 需 要 为 double型 , 其数 据 长 度 比 int 大 , 这 种 情 况 就 有 可 能 访 问 函 数 参 数 堆 栈 以 外 的 区 域 , 从 而 造 成危 险 。 但 是 printf(%d, %d, %d, 1.0, 20., 3.0);这 种 情 况 虽 然 结 果 可 能 不 正 确 ,但 是 确 不 会 造 成 灾 难 性 后 果 。因 为 实 际 指 定 的 参 数 长 度 比 要 求 的 参 数 长 度 长 ,堆栈 不 会 越 界 。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 6 页 - - - - - - - - -
限制150内