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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介:

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

………



















本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/1951621 ,如需转载请自行联系原作者

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项
本文深入解析C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项,并通过实际案例分析,展示预处理器指令在代码编写与处理中的重要性和灵活性。
113 2
C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项
本文深入探讨了C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项,并通过案例分析展示了实际应用,旨在帮助读者提高编程效率和代码质量。
212 4
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
268 3
|
10月前
|
C语言函数参数的声明及调用
C语言函数参数的声明及调用
80 1
C语言之函数与参数
C语言之函数与参数
72 0
C语言多维数组名作函数参数的研究
C语言多维数组名作函数参数的研究
63 0
|
10月前
|
在C语言中数组作为函数参数的应用与示例
在C语言中数组作为函数参数的应用与示例
130 0
|
10月前
|
在C语言中多维数组名作为函数参数的应用与示例
在C语言中多维数组名作为函数参数的应用与示例
96 0
C语言命令行参数
C语言命令行参数
51 0
C语言宏定义(#define定义常量​、#define定义宏​、 带有副作用的宏参数、 宏替换的规则、 宏函数的对比)
C语言宏定义(#define定义常量​、#define定义宏​、 带有副作用的宏参数、 宏替换的规则、 宏函数的对比)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等