一步一步写嵌入式操作系统5—变参函数的原理和实现
我们写操作系统,当然很多的库就用不到了,可能很多人都不习惯,不过,有一些东西我们是可以自己来实现的,其实包括经典的printf这个函数,可是,这个函数是变参函数,
也就是说,你输入的形参的个数是不固定的,于是就产生了一个问题,怎么才能实现这种函数呢,是不是所有的情况下都可以实现这种变参函数呢?
首先我们想一下经常用到的调用形式,不外乎这几种,__stdcall,__pascal,__fastcall,__classcall,__cdecl,这其除了最后一个是调用者自己管理堆栈外,其它都是被调用者自
己管自己,而这恰恰就是是否可以形成变参的一个重要的因素,也就是说,只有__cdecl这种调用者管理堆栈的方式才可能会出现变参。
变参的实现我们以一个小例子来实现:
typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
void test_num(int num){
*(char *)0xd0000020=num+'0';
}
void test_vparameter(int i,...){
int mm;
va_list argv;
va_start(argv,i);
while(i--){
mm=va_arg(argv,int);
test_num(mm);
}
va_end(argv);
}
程序很简单,关键在宏的使用上,_INTSIZEOF宏提供了内存中字节对齐的具体的数据的长度,比如int 是4,double是 8,这个宏就是生成一个比要计算的数大并能整除4的最小的那
个数,其实就是为了字节对齐。va_start用于计算第一个固定的参数的内存中的位置。va_arg宏用于根据va_start和_INTSIZEOF来不断的计算变参的位置以得出其值(即返回当前
值并将指针移到下一个参数的位置),va_end宏用于清理整个过程。
于是这就产生了一个规矩,即变参的函数必须有一个固定的参数,两行不行捏,这还用说,当然行。
书上和网上都有类似于printf的小例子:
#include <stdio.h>
#include <stdlib.h>
void myprintf(char* fmt, ...) //一个简单的类似于p rintf 的实现,参数必须都是int 类型
{
char* pArg = NULL; //等价于原来的 va_list
char c;
pArg = (char*) &fmt; //注意不要写成 p = fmt !!因为这里要对//参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start
do {
c =*fmt;
if (c != '%') {
putchar(c);//照原样输出字符
} else { //按格式字符输出数据
switch(*++fmt)
{
case 'd':
printf("%d",*((int*)pArg));
break;
case 'x':
printf("%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
} while (*fmt != '\0');
pArg = NULL; //等价于va_end
return;
}
int main(int argc, char* argv[])
{
int i = 1234;
int j = 5678;
myprintf("the first test: i = %d\n", i, j);
myprintf("the secend test: i = %d; %x; j=%d;\n", i, 0xabcd, j);
system("pause");
return 0;
}
固定的类型和可计算出的类型,都好说,现在说说如果实现以下的函数怎么办:
void mytest(int c,...);//变参为:double,int,short
即类型在变,书上和网上目前尚未找到现成的例子,其实你看上面的类似printf这个例子就明白了,你可以自己实现一个数组或者其它的什么,把数据类型保存起,比如1代表
int,2代表short,3代表double等等,那么你也可以实现一个case,每当判断时,自动跳转,然后在case 语句中,实现mm=va_arg(argv,int);mm=va_arg(argv,short);,当然,肯定
有更好的方法,但只要懂得了原理,那么其它的具体的实现的好坏,大家就仁者见仁,智者见智。
时间过得很快,进度要一点点儿把握住。
每天前进一点点,日积月累,以汇成江海。
用魏武帝的诗来结尾:山不厌高,水不厌深。以自勉。