可变参数函数的实现解析
也许很多人懂得这个概念,也许很多不知道。之前我只知道有这么一种函数,但不知道它的具体实现原理,今天又对它进行了重新的学习。
在C语言中如果你分别定义了这样的两个函数
int function1(){
.............
}
int function2(void){
.............
}
那么请问你知道它们的不同吗?刚开始的时候我感觉是一样的,直到有一天在看C++方面书籍的时候才看到了其实它们是不一样的,在C中function1是一个可以拥有任意参数的函数,而function2很明显是没有参数的函数。而再C++中这两个是 一样的。这验证了那一句话C++编译器为我们做了太多我们不知道的事情,是这样的。似乎脱离正题了,圆规正传!
也许你写的第一个C程序就是printf("hello world!");,但在以后的日子里这个printf函数你用了无数次,但是你想过没有这个函数的实现方式。它是多么奇怪的函数啊(和我们自己定义的函数相比较),它可以拥有任意个数不同类型的变量。其实我们也可以做到,但前提是我们需要了解它的内部工作机制原理。
让我们首先回想一下函数的调用过程,我们都知道函数参数是从右向左进行依次入栈(为什么从右向左?自己想)。有一点编程经验的人对内存空间的概念一定不会陌生,我们要想使用每一个变量首先要得到它的地址,对,函数参数已经进入栈内存(并且是可变个数的),我们接下来要使用它们,就需要首先得到它的存储地址,首地址(栈顶)存储的就是第一个参数,接下来要使用第二个参数怎么办?要知道第一个参数的类型(计算offset),为的是找到下一个参数的地址。接着读,接着计算offset.....就是这样的一个过程。
那么系统是怎么实现的这么一个抽象过程呢?在这么一个头文件中<stdarg.h>中定义了这么一些宏:


也许你刚开始看会一脸迷茫,不急,等我慢慢道来。
va_list 其实就是一个char指针,它的作用是用来存储函数参数列表的地址的。
_INTSIZEOF(n) 它的实现多少有一点麻烦,它的功能其实很单纯,就是为了考虑那些内存地址需要对齐的系统,如果sizeof(n)在1--4之间,那么_INTSIZEOF(n) = 4,如果sizeof(n)在5--8之间,那么_INTSIZEOF(n) = 8。 你懂得(内存对齐)!
va_start(ap,v) 这里的使用的v变量时在函数定义中你定义的最后一个固定参数变量,在运行完该宏之后ap就指向了剩下的可变参数的内存地址了。
va_arg(ap,t) 这个宏一共实现了两个作用。首先它用来取ap指向的类型为t的变量,接着它改变ap的地址指向下一个可变参数。
va_end(ap)的作用很明显,在参数读取完之后改变ap的为0,是为了从安全角度考虑的。
光说不练,等于白说。接下来让我们看看该怎么把这么东西运用到我们自己的程序当中。
#include <stdio.h>
#include <stdarg.h>
int sum(int data,...){
int i=data, s=0;
va_list var;
va_start(var,data);//data为最后一个固定变量 切看上述中va_start的用法。
while(i!=-1){
s+=i;
i=va_arg(var,int);//取变量,然后改变var指向下一个
}
va_end(var);//结束
return s;
}
int main(){
int s=sum(1,2,3,4,5,-1);
printf("sum = %d",s);
return 0;
}
我定义了一个sum的可变参数,变量var用来存储参数列表中可变部分的地址va_start(var,data)。接着用循环的方式读取函数参数,然后叠加它们。
我之前的学习方法一直存在问题,以后我会严格按照以下的思路来学习技术问题。(希望能跟大家共享之)
理解技术点的时候问自己四个问题:
为什么是这样的?
用来解决什么问题?
原理是什么?
缺点是什么?
只有找到了这些问题的答案,才能真正掌握这个技术,也才能从操作技术上升到玩技术。
(注:来自陈皓老师的建议)
浙公网安备 33010602011771号