c语言中的void printlog(char *format,...)这是什么意思?

我看别人源代码的时候,看到一个这种函数声明,参数列表中的...是什么意思呢?这是什么语法?
我记得printf也是这么弄的:
printf, (const char *, ...)

是可变参数,是c的一个语法现象,我在电脑上保存的一些资料,希望对你有用。
一、什么是可变参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点"…"做参数占位符),实际调用时可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文就这个问题进行一些探讨,希望能对大家有些帮助.

二、可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98\include\stdarg.h)
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 )

下面我们解释这些代码的含义:

1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的

2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。

3、va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。

这里要知道两个事情:
⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
(2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|--------------------------|
| 最后一个可变参数 | ->高内存地址处
|--------------------------|
|--------------------------|
| 第N个可变参数 | ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|--------------- |
|--------------------------|
| 第一个可变参数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|--------------- |
|------------------------ --|
| |
| 最后一个固定参数 | -> start的起始地址
|-------------- -| .................
|-------------------------- |
| |
|--------------- | -> 低内存地址处

(4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
因此,现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。

(5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

三、可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 例如,在C的经典教材《the c programming language》的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。

四、小结:
1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志-- printf函数就是用这个办法。后面也有例子。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.
无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。
3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。
温馨提示:内容为网友见解,仅供参考
第1个回答  推荐于2018-03-26

  表示printlog这个函数,接受任意多个参数,第一个参数必须为char *类型,后面有多少个参数都可以,没有也可以。

  C语言对可变参数的支持通过stdarg.h来实现,原理很简单。

  首先让编译器识别...,允许函数调用传入任意数量的参数而不产生警告。然后在函数体里通过stdarg.h提供的几个宏来获取参数。

  这是stdarg.h提供的用于实现变长参数的几个宏:

#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 )

   va_start用来初始化,就是将指针指向栈中变长参数的起始位置。va_arg用来取参数,即将指针转换为指定类型的指针,取值。再将指针进行偏移。va_end用来完成清理工作,也就是把指针置为NULL。

  例子:

int sum(unsigned int n,...)  
{  
   int sum=0;  
   va_list args;  
   va_start(args,n);  
   while(n>0)  
   {  
    //通过va_arg(args,int)依次获取参数的值   
     sum+=va_arg(args,int);  
     n--;  
   }  
   va_end(args);  
   return sum;  
}

    这段代码实现了对任意数量的整数的求和,整数的数目由n指定。所以说,变长参数的函数一定有一个已知类型的参数,即第一个参数,用来告知函数,一共传入了多少个参数,每个参数该取多长。

本回答被网友采纳

c语言中的void printlog(char *format,...)这是什么意思?
(5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类...

c语言中的void printlog这是什么意思
这个没有意义, void 是 无类型,虽然表示一种类型,但如果用在定义一个变量上没有实际意义,估计编译都过不了,你试下看看。 可以用来定义函数的类型, 比如void func( void ) ; 前面的void表示此函数无需返回参数, 刮号里的void表示此函数没有参数 ...

如何编写守护进程
void syslog(int priority,char*format,……) void closelog(); 调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslog守护进程通信的描述符。调用openlog 使我们可以指定一个ident,以后, 此ident 将被加至每则记录消息中。ident...

LINUX下C语言编程怎么打印日志
syslog函数及参数syslog函数用于把日志消息发给系统程序syslogd去记录,此函数原型是:void syslog(int priority, const char *format, ...);第一个参数是消息的紧急级别,第二个参数是消息的格式,之后是格式对应的参数。就是printf函数一样使用。如果我们的程序要使用系统日志功能,只需要在程序启动时使...

C语言程序设计 题目:学生信息管理系统
void ElePrint(char str[]) \/\/输出单个元素 { if(str==NULL) exit(0); printf("%s",str); for(unsigned int i=0;i<12-strlen(str);i++) printf(" ");\/\/为了对齐输出,需插入一些空格 return; } int LinePrint(student *ptr) \/\/输出一行 { if(ptr==NULL) \/\/检查传进来的指针 return 0; ...

C语言这个错误时什么意思expected primary-expression before...
int print(struct student) 改为 int print(struct student stu),然后 student.num 改为 stu.num,其余类似修改。. 前面应该是结构变量名,而不是结构类型名。

C语言中的标准函数有哪些
int getchar(void)getchar主要是从标准输入流读取一个字符.默认的标准输入流即stdio.h中定义的stdin.但是从输入流中读取字符时又涉及到缓冲的问题,所以并不是在屏幕中敲上一个字符程序就会运行,一般是通过在屏幕上敲上回车键,然后将回车前的字符串放在缓冲区中,getchar就是在缓冲区中一个一个的读...

多项式算法?
定义:如果NP类中所有问题都可以多项式时间归约到NP类中某个问题x,则称x是NP-完全问题。定义:如果某优化问题x的判定问题是NP-完全的,则称问题x是NP-难的;如果x的判定问题是强NP-完全的,则储x是强NP-难的。多项式计算 C语言编程 这个其实很简单,需要3个数组(暂时考虑int数组),长度都是10...

【求助啊】分解因式 c语言
void primes() { bool get[MAXP+1]; int i; for(i = 0;i <= PCOUNT;i++) prim[i] = 0; for(i = 2;i <= MAXP;i++) get[i] = true; for(i = 2;i <= MAXP;i++) if (get[i]) { int j = i+i; while(j <= MAXP){get[j] = false;j += i;} } ...

C中的float和double的有效数字和精度各是多少?
float有效数字位为6 – 7位,字节数为4,指数长度为8位,小数长度为23位。取值范围为 3.4E-38~3.4E+38。double有效数字位为15 – 16位,字节数为8,指数长度为11位,小数长度为52位。取值范围为1.7E-308~1.7E+308。

相似回答