程序debug过程中经常会打印调试信息,如果我们要在程序运行过程当中看到调试信心,就得把运行的状态信息输出到文件。于是,我写了下面两个函数来实现可变参数的打印调试信息到文件。
#include "stdio.h"
#include "stdarg.h"
int print_string_to_file(char file_name[64], ...)
{
va_list ap;
char *p;
FILE* fp;
fp = fopen(file_name, "ab");
if (fp == NULL)
{
return -1;
}
va_start(ap, file_name);
p = va_arg(ap, char *);
while (p != NULL)
{
fprintf(fp, "%s/n", p);
p = va_arg(ap, char*);
}
va_end(ap);
fclose(fp);
return 0;
}
static char szBuffer[8*1024];
int print_data_to_file (char file_name[64], const char * szFormat, ...)
{
int iReturn ;
va_list pArgs ;
FILE* fp;
fp = fopen(file_name, "ab");
if (fp == NULL)
{
return -1;
}
va_start (pArgs, szFormat) ;
iReturn = vsprintf (szBuffer, szFormat, pArgs) ;
fprintf(fp, "%s",szBuffer);
va_end (pArgs) ;
fclose(fp);
return iReturn ;
}
其中,第一个函数只能打印字符串,并且调用的时候最后一个参数必须传入一个NULL指针,从里面的代码也可以看到可变参数的实现技巧。
而第二个函数可以按照格式化的信息打印,类似于printf的format。简单的调用代码如下:
int main(int argc, char* argv[])
{
float f = 7.0f;
print_string_to_file("info.txt", "123","string", NULL);
print_data_to_file("info2.txt","str = %s, float num = %f/n", "string", f);
return 0;
}
可变参数的函数实现实际上与函数参数传递的堆栈结构有关,在x86平台,一般的c编译器的参数传递默认按照从右到左的,即函数中的最右边的参数最先入栈的顺序。这个实际上与调用约定有关。对于函数
void fun1(char a, int b, double c, short d) ;
如果知道了参数a的地址,则要取后续参数的值则可以通过a的地址计算a后面参数的地址,然后取对应的值,而后面参数的个数可以直接由变量a指定,当然也可以像printf一样根据第一个参数中的%模式个数来决定后续参数的个数和类型。如果参数的个数由第一个参数a直接决定,则后续参数的类型如果没有变化并且是已知的,则我们可以这样来取后续参数, 假定后续参数的类型都是double;
void fun1(int num, ...)
{
double *p = (double *)((&num)+1);
double Param1 = *p;
double Param2 = *(p+1);
...
double Paramn *(p+num);
}
如果后续参数的类型是变化而且是未知的,则必须通过一个参数中设定模式来匹配后续参数的个数和类型,例如printf就是这样.
综上,我实现了一个求解n个整数的最大值的可变参数函数。
int Max(int n, ...)
{
int *p = &n + 1;
int ret = *p;
for (int i=0; i<n; i++)
{
if (ret < *(p + i))
ret = *(p + i);
}
return ret;
}
调用方式如下:
int main(int argc, char* argv[])
{
int m3 = Max_num(3, 45, 12, 56);
int m1 = Max_num(1, 3);
int m2 = Max_num(2, 23, 45);
int first = 34, second = 45, third=5;
int m5 = Max_num(5, first, second, third, 100, 4);
}
结论
对于可变参数函数的调用有一点需要注意,实际的可变参数的个数必须比前面模式指定的个数要多,或者不小于, 也即后续参数多一点不要紧,但不能少, 如果少了则会访问到函数参数以外的堆栈区域,这可能会把程序搞崩掉。前面模式的类型和后面实际参数的类型不匹配也有可能造成把程序搞崩溃,只要模式指定的数据长度大于后续参数长度,则这种情况就会发生。