c语言中可变长度参数使用的注意事项

C语言中可变长度参数极大地方便了我们编程的同时,也非常容易由于使用不慎导致及其隐蔽的错误。以下面的这段函数为例,运行一段时间后会随机出现段错误,而且出错的位置一直稳定在vsprintf()函数里面。

……………...

a_list
ap;

va_start(ap,
cmd);

……………...

rep
= (redisReply *)redisvCommand(rc, cmd, ap);

vsprintf(str,
cmd, ap);

……………...

va_end(ap);

为了深入探究原因,我们下载了redis源代码和glibc源代码。看redis源代码中 redisvCommand的实现:

void
*redisvCommand(redisContext *c, const char *format, va_list ap) {

if
(redisvAppendCommand(c,format,ap) != REDIS_OK)

return
NULL;

return
__redisBlockForReply(c);

}

它主要调用了redisvAppendCommand:

int
redisvAppendCommand(redisContext *c, const char *format, va_list ap)
{

char
*cmd;

int
len;

len
= redisvFormatCommand(&cmd,format,ap);

if
(len == -1) {

__redisSetError(c,REDIS_ERR_OOM,"Out
of memory");

return
REDIS_ERR;

}
else if (len == -2) {

__redisSetError(c,REDIS_ERR_OTHER,"Invalid
format string");

return
REDIS_ERR;

}

if
(__redisAppendCommand(c,cmd,len) != REDIS_OK) {

free(cmd);

return
REDIS_ERR;

}

free(cmd);

return
REDIS_OK;

}

而redisvAppendCommand()函数中使用了va_arg,比如下面的部分代码:

256
           /* Set newarg so it can be checked even if it is not
touched. */

257
           newarg = curarg;

258

259
           switch(c[1]) {

260
           case ‘s‘:

261
               arg = va_arg(ap,char*);

262
               size = strlen(arg);

263
               if (size > 0)

264
                   newarg = sdscatlen(curarg,arg,size);

265
               break;

266
           case ‘b‘:

267
               arg = va_arg(ap,char*);

268
               size = va_arg(ap,size_t);

269
               if (size > 0)

270
                   newarg = sdscatlen(curarg,arg,size);

271
               break;

272
           case ‘%‘:

273
               newarg = sdscat(curarg,"%");

274
               break;

275
           default:

乍一看,ap传进去都是形式参数,不会改变,但仔细看va_arg的帮助文档可以看到,其实每次调用va_arg()都会改变ap的值:

va_arg()

The
va_arg() macro expands to an expression that has the type and value
of the next argument in the call.  The argument ap is the  va_list ap
initialized by va_start().  Each call to va_arg() modifies ap so
that the next call returns the next argument.
 The argument type
is a

type
name specified so that the type of a pointer to an object that has
the specified type can be obtained simply by adding a * to type.

The
first use of the va_arg() macro after that of the va_start() macro
returns the argument after last.  Successive invocations return the

values
of the remaining arguments.

而ap又是作为指针的指针传递进来的,因此上层调用函数里的可变长度参数ap也会改变,着就导致后面对可变长度参数的使用出现段错误。因为前面已经遍历一遍了,ap到末尾了。

理解了这一点,针对一个函数中要调用多个可变长度的参数的用法,安全的用法就是为每一个被调用的函数单独分配一个可变长度参数va_list。据此,上面的代码就应该改写成这样:

a_list
ap;

va_list
aq;

va_start(ap,
cmd);

va_copy(aq,
ap);

………

rep
= (redisReply *)redisvCommand(conn, cmd, ap);

vsprintf(str,
cmd, aq);

va_end(ap);

va_end(aq);

………

时间: 2024-10-08 09:48:12

c语言中可变长度参数使用的注意事项的相关文章

C语言中可变参数的函数(三个点,“...”)

C语言中可变参数的函数(三个点,"...") 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...); void foo(parm_list,...); 这种方式和我们以前认识的不大

C语言中函数参数为什么是由右往左入栈的?

先通过一个小程序来看一看: #include void foo(int x, int y, int z) { printf("x = %d at [%X]n", x, &x); printf("y = %d at [%X]n", y, &y); printf("z = %d at [%X]n", z, &z); } int main(int argc, char *argv[]) { foo(100, 200, 300);

C语言中函数参数入栈的顺序 - Fangzhen - 博客园

.wiz-todo, .wiz-todo-img {width: 16px; height: 16px; cursor: default; padding: 0 10px 0 2px; vertical-align: -10%;-webkit-user-select: none;} .wiz-todo-label { display: inline-block; padding-top: 7px; padding-bottom: 6px; line-height: 1.5;} .wiz-todo

C语言中可变参数的用法

前言 在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式化字符串之外还可以输入多个可变参量,如:    printf("%d",i);     printf("%s",s);    printf("the number is %d ,string is:%s", i, s); 格式化字符串的判断本章

C语言中可变参数的使用方法

[温馨提示: 以下内容均来自网友的无私奉献或书本的摘抄,在此表示感谢!] 我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,其定义为: int printf( const char* format, ...); 它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法: printf("%d",i); printf("%s",s); printf("the number is %d

C语言中可变参数函数实现原理

C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fun(int a, int b, int c) { int d; ... } 其栈结构为 0x1ffc-->d 0x2000-->a 0x2004-->b 0x2008-->c 对于在32位系统的多数编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单

C语言中带参数的宏

带参数的宏定义有如下的格式: [#define 指令----带参数的宏] #define 标识符(x1,x2,……,xn) 其中  x1,x2,……xn是标志符(宏的参数) 注意:在宏的名字和括号之间必修没有空格. 如果有空格,预处理会认为是在定义一个简单的宏,其中(x1,x2,……,xn)是替换列表的一部分 当预处理器遇到一个带参数的宏,会将定义存储起来以便以后使用.在后面的程序中,如果任何地方出现了标识符(y1,y2……,yn)格式的宏调用(其中y1,y2, ……yn是一些列标记),预处理器

c语言中如何实现可变参数

c语言函数库中有不少类似于printf,scanf的函数,这些函数接收的参数个数是不确定的.这些函数实际是通过<stdarg.h>文件中的va_list,va_start,va_arg,va_end宏定义是实现的.另外,我们知道程序运行时的内存结构后,也可以通过直接访问这些内存空间获得实际参数值(事实上stdarg.h文件中的相关宏定义就是这么操作的). #include <stdio.h> #include <stdlib.h> #include <stdarg

C语言中容易被忽略的细节(第三篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头.第三篇了,fight~... 第一篇链接:C语言中容易被忽略的细节(第一篇) 第二篇链接:C语言中容易被忽略的细节(第二篇) 1.__attribute__((noreturn)) __attribute__可设置函数属性.变量属性和类型属性.__attribute__((noreturn))设置了函数属性,n