I recently helped someone troubleshoot a bug and saw an interesting case. The following program will have Segmentation Fault, can you see the problem?
|
|
va_list Introduction
Variadic functions in C are implemented as va_list
, va_start
, va_arg
and va_end
defined by <stdarg.h>
. Here is a simple example.
|
|
Among them, va_list
is an “implementation-defined” data structure. Its function is equivalent to an iterator of function parameters. We must first initialize the start of the iterator with va_start
, then read each parameter in turn with va_arg
, and finally release all the resources required by va_list
with va_end
.
va_list
itself can also be passed as an argument to other functions. Commonly used for printf
functions starting with v
, such as vprintf
, vfprintf
, vsnprintf
, etc.
Problems
Go back to the code at the beginning of this article. The code wants to first call vsnprintf
to calculate the amount of memory it needs, then allocate enough memory, and then call vsnprintf
again to convert format
and args
into strings.
The root of the problem is that the args
iterator comes to the end of the vsnprintf
function when it is first called. the C language standard has the following notation for the behavior of the vsnprintf
function.
- As the functions
vfprintf
,vfscanf
,vprintf
,vscanf
,vsnprintf
,vsprintf
, andvsscanf
invoke theva_arg
macro, the value ofarg
after the return is indeterminate. – C 11 (N1570), p. 327
In other words, if you read the function argument from the same va_list
with va_arg
after the vsnprintf
return, the value of va_arg
after the return is indeterminate. In my test environment, va_arg(args, const char *)
returns the wrong address, which leads to a Segmenetation Fault.
How to fix it
Because we have to access the arguments twice, we should make a copy of va_list
with va_copy
before accessing the arguments for the first time. Example.
|
|
Alternatively, if you can use the GNU extension function (compiled with the definition _GNU_SOURCE
), we can call the vasprintf
function directly. vasprintf
will directly calculate the required memory space, configure the memory, and output the string.
However, the actual code I encountered was not configured with malloc
memory, so I did not use this modification.