c语言可变参数函数的原理与实现详解

c 语言可变参数函数的原理与实现详解

上代码,注意此代码运行在 32 位系统上,所以下文的数据类型大小按32位计算。
#include <stdio.h>
#include <stdarg.h>

// 模仿 printf 函数可变参数的样式
void f(const char *fmt, ...) {
	// 模仿获取可变参数的获取过程
    char *ap;
    ap = (char *)&fmt; 		// ap 为第一个参数的地址
   
    int v1 = *(int *)(ap + 4);	// 因为上一个参数 char * 占4个字节,所以第二个参数(即第一个可变参数)的地址是 ap + 4
    int v2 = *(int *)(ap + 8);
    char *v3 = *(char **)(ap + 12);
    long long v4 = *(long long *)(ap + 16);  
    int v5 = *(char *)(ap + 24); 	//上一个参数 long long 占 8 个字节
    printf("%d %d %s %lld %c\n", v1, v2, v3, v4, v5);

	// c 语言中获取可变参数
    va_list a;
    va_start(a, fmt);	// 这里 a 实际上是第二个参数(第一个可变参数)的地址
    
    int z1 = va_arg(a, int);
    int z2 = va_arg(a, int);
    char *z3 = va_arg(a, char *);
    long long z4 = va_arg(a, long long);
    char z5 = va_arg(a, int);
    printf("%d %d %s %lld %c\n", z1, z2, z3, z4, v5);
    va_end(a);
}

int main() {
    long long x = 293738475;
    char y = 'r';
    f("call %d %d %s %lld", 20, 301, "hello", x, y);
    return 0;
}

运行,指定 32 位系统
gcc -m32 -g -O0 f.c
./a.out
运行结果
p1: 20 301 hello 293738475 r
p2: 20 301 hello 293738475 r
为什么可以获取到可变参数

1、函数传递参数时,参数从右到左依次压栈。
2、栈是向下增长的,栈顶内存地址比栈底小。
所以只要知道第一个参数的内存地址(最后压栈参数),和参数类型(所占的内存大小),就可以算出各参数在栈中的位置,从而获取到各参数。

反汇编查看汇编代码实现
objdump -S a.out

在这里插入图片描述
此时各参数在内存栈中的分布
在这里插入图片描述

赋上获取动态参数的宏定义
#ifndef _STDARG_H
#define _STDARG_H

typedef char *va_list;

/* Amount of space required in an argument list for an arg of type TYPE.
   TYPE may alternatively be an expression whose type is used.  */

#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

#ifndef __sparc__
#define va_start(AP, LASTARG) 						\
 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) 						\
 (__builtin_saveregs (),						\
  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif

void va_end (va_list);		/* Defined in gnulib */
#define va_end(AP)

#define va_arg(AP, TYPE)						\
 (AP += __va_rounded_size (TYPE),					\
  *((TYPE *) (AP - __va_rounded_size (TYPE))))

#endif /* _STDARG_H */

__va_rounded_size(TYPE) : 是为了字节对齐,32 位系统内存栈4个字节对齐,比如 TYPE为3时计算得4,TYPE为7时计算得8。

va_start(AP, LASTARG):AP 指向第二个参数(第一个可变参数)的内存地址。

va_arg(AP, TYPE):每次获取后AP值增加TYPE所占大小,指向下一个可变参数地址,为获取下一个参数准备。

va_end(AP):最后调用,将AP指向无效。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值