標籤:c語言 可變 長度參數
C語言中可變長度參數極大地方便了我們編程的同時,也非常容易由於使用不慎導致及其隱形錯誤。以下面的這段函數為例,運行一段時間後會隨機出現段錯誤,而且出錯的位置一直穩定在vsprintf()函數裡面。
……………...
a_listap;
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)
returnNULL;
return__redisBlockForReply(c);
}
它主要調用了redisvAppendCommand:
intredisvAppendCommand(redisContext *c, const char *format, va_list ap){
char*cmd;
intlen;
len= redisvFormatCommand(&cmd,format,ap);
if(len == -1) {
__redisSetError(c,REDIS_ERR_OOM,"Outof memory");
returnREDIS_ERR;
}else if (len == -2) {
__redisSetError(c,REDIS_ERR_OTHER,"Invalidformat string");
returnREDIS_ERR;
}
if(__redisAppendCommand(c,cmd,len) != REDIS_OK) {
free(cmd);
returnREDIS_ERR;
}
free(cmd);
returnREDIS_OK;
}
而redisvAppendCommand()函數中使用了va_arg,比如下面的部分代碼:
256 /* Set newarg so it can be checked even if it is nottouched. */
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()
Theva_arg() macro expands to an expression that has the type and valueof 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 sothat the next call returns the next argument. The argument typeis a
typename specified so that the type of a pointer to an object that hasthe specified type can be obtained simply by adding a * to type.
Thefirst use of the va_arg() macro after that of the va_start() macroreturns the argument after last. Successive invocations return the
valuesof the remaining arguments.
而ap又是作為指標的指標傳遞進來的,因此上層調用函數裡的可變長度參數ap也會改變,著就導致後面對可變長度參數的使用出現段錯誤。因為前面已經遍曆一遍了,ap到末尾了。
理解了這一點,針對一個函數中要調用多個可變長度的參數的用法,安全的用法就是為每一個被調用的函數單獨分配一個可變長度參數va_list。據此,上面的代碼就應該改寫成這樣:
a_listap;
va_listaq;
va_start(ap,cmd);
va_copy(aq,ap);
………
rep= (redisReply *)redisvCommand(conn, cmd, ap);
vsprintf(str,cmd, aq);
va_end(ap);
va_end(aq);
………
本文出自 “儲存之廚” 部落格,請務必保留此出處http://xiamachao.blog.51cto.com/10580956/1951621
c語言中可變長度參數使用的注意事項