添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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;

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

13【C语言 & 趣味算法】分糖果 问题。(数组名作为函数形参,亦即:形参数组名作 指针变量)
13【C语言 & 趣味算法】分糖果 问题。(数组名作为函数形参,亦即:形参数组名作 指针变量)
在C语言中,SetConsoleTextAttribute(参数1,参数2)是设置控制台窗口字体颜色和背景颜色的函数。GetStdHandle(参数)函数用于获得句柄
在C语言中,SetConsoleTextAttribute(参数1,参数2)是设置控制台窗口字体颜色和背景颜色的函数。 参数1:句柄 参数2:颜色
C语言完成通讯录(内含解析和操作注意事项) 要求将函数分为:函数声明部分,函数主要部分,各个函数实现部分
C语言完成通讯录(内含解析和操作注意事项) 要求将函数分为:函数声明部分,函数主要部分,各个函数实现部分