可变参数函数

本文介绍了C语言中可变参数函数的使用,包括`stdarg.h`中的宏`va_list`、`va_start`、`va_arg`和`va_end`。详细解释了可变参数的压栈原理,并通过示例展示了如何获取和使用这些参数。同时提醒在使用可变参数时需要注意参数长度的获取和潜在的错误情况。

可变参数相关的宏

首先先了解几个宏:

typedef __gnuc_va_list va_list;
#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)

这几个宏可以在stdarg.h中找到。va是variable-argument的缩写,方便记忆。
va_list继续追踪为:

typedef __builtin_va_list __gnuc_va_list;

这是由编译器内置的变量,可以和int、char等一样去理解。
va_start(v,l)用于初始化va_list变量(对应参数v),参数l为可变参数前一个参数;
va_arg(v,l)用于获取可变的参数,l对应获取的类型,如int等;
va_end(v)用于结束参数获取。

可变参数获取原理

在函数的调用时,参数压栈是从右到左进行的,即最后一个参数先入栈,第一个参数最后入栈。栈的生长方向一般都是向下生长的,即先入栈的地址高,后入栈的地址低。

地址低			|———————————————|
				|    全局量      |
		 堆起始->|———————————————|
				|  堆向高地址增长 |
				|               |
				|               |
				|    自由空间    |
				|               |
				|               |
				|  栈向低地址增长 |
地址高 	 栈起始->|———————————————|

当我们知道第一个参数(即最后一个入栈的参数)的地址,同时知道各个参数的类型时,即可通过地址的移动获取到对一个的变量值。

地址低						|---------------......---------------|
	已知--->1个参数->---------------|
			第2个参数->---------------......---------------|
			第n-1个参数->---------------|
			第n个参数->---------------......---------------|
地址高						|---------------

如上图示,我们知道如果已知第一个参数的地址(假设是add_base),并知道它的类型(假设是char*型),则第二个参数的地址为add_base+4,由这个地址,再根据第二个参数的类型,即可得到第二个参数的值以及下一个参数(如果有)的地址,以此类推得到所有参数。

可变参数函数示例

编写以下代码进行测试:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

/**
* va_test测试函数,打印指定长度的int实参。观察打印结果和输入的可变参数是否一致。
*/
void va_test(int length, ...) {
    int* temp = (int*)malloc(sizeof(int)*length);
    int i = 0;
    va_list list;
    va_start(list, length);
    for (i = 0; i < length; i++) {
        temp[i] = va_arg(list, int);
    }
    va_end(list);
    for (i = 0; i < length; i++) {
        printf("%d\t", temp[i]);
    }
    printf("\n");
    free(temp);
}

int main(){
    va_test(3, 1, 2);
    va_test(5, 1, 2, 3, 4, 5);
    va_test(2, 1, 2);
    return 0;
}

输出结果如下:

1	2	-2078377552	
1	2	3	4	5	
1	2	

对上述结果进行分析:
首先是第一条结果:1 2 -2078377552 ,我在可变参数中输入了两个参数,但在读取过程中读了三个参数,程序没有报错,但第三个结果为非预期的结果。
第二第三条结果为可变参数个数和读取个数匹配的情况,结果参数正确读取。
通过以上例子,我们完成了可变参数函数的编写。

注意事项

  1. 可变参数的参数长度需要我们用其他方法获得,编译器是不知道你传递参数长度的。
  2. 编译器只通过地址和类型获取对应的值,而不能保证这个值是否为你传递的参数。上面例子的第一条打印结果就显示了这个问题:传递了两个参数,读取三个参数,但第三个参数并不是你要传递的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值