带va_list参数的函数需在开头调用va_start()吗?含max函数场景疑问
Hey,这两个问题刚好是C语言可变参数里很容易踩坑的点,我来给你掰扯清楚:
问题1:调用带有va_list参数的函数时,是否需要在函数开头执行va_start()?
绝对不需要!甚至这么做会直接搞砸你的参数读取。
va_start()的核心作用是初始化一个全新的va_list变量,让它指向当前函数...可变参数列表的第一个参数。只有当你的函数是直接接收...(比如void func(int n, ...)这种)的时候,才需要用va_start()来初始化你自己声明的va_list变量。
如果你的函数参数是别人传过来的现成va_list,那这个变量已经被上游的调用者用va_start()初始化过了。你再在当前函数里调用va_start(),会直接重置这个va_list的状态,后续用va_arg()读取参数时,要么读错值,要么直接程序崩溃,属于标准的未定义行为。
问题2:对于函数int max(int n, va_list vals),内部调用int max_first(int n, ...),是否需要在max函数体的开头调用va_start(vals, n)?
同样不需要,你测试时不加能运行纯粹是运气好,绝对不是正确的做法。
首先,vals是调用者传递过来的已经初始化好的va_list,你在这里调用va_start(vals, n)会彻底破坏它的状态——不仅max函数自己没法正确读取参数,甚至如果调用者在调用max之后还要继续使用这个va_list,也会跟着出错。
另外还要提醒你一个关键点:你不能直接把va_list变量传递给接收...的函数(比如这里的max_first)。...对应的是可变参数列表,而va_list是一个已经封装好的参数迭代器,直接传的话会导致max_first把va_list本身当成一个普通参数来读,完全不是你想要的效果。
正确的做法是:如果max_first有对应的“v版本”函数(比如int vmax_first(int n, va_list vals),就像标准库的vprintf对应printf那样),你可以直接把vals传进去;如果没有,你需要先用va_copy()复制一份va_list再操作,但本质上还是建议尽量用v版本函数来处理这种传递场景。
最后再强调一遍:你的示例代码里的va_start(vals, n)是错误的,必须删掉。
内容的提问来源于stack exchange,提问作者Cătălina Sîrbu

