C语言可变参数函数的实参求值顺序及va_arg相关疑问
1. 实参求值顺序:普通函数与可变参数函数规则一致
C标准明确规定:所有函数调用中,实参的求值顺序都是未指定的——不管是普通函数还是可变参数函数,都遵循这一规则。
比如调用g2(f1(), f2(), f3())(g2是void g2(int a, ...);),f1()、f2()、f3()的执行顺序完全由编译器决定,既可能先执行f3()再执行f1(),也可能按其他任意顺序,你不能做任何假设。甚至固定参数和可变参数的求值顺序也没有规定,比如g2(f1(), f2())中,f1()(对应固定参数a)和f2()(对应第一个可变参数)谁先执行也是未指定的。
2. va_arg的“下一个”:按参数声明顺序(左到右)获取
虽然va_arg的描述里没直接写“左到右”,但它的“下一个”是严格对应函数调用时传递的可变参数的声明顺序。
举个例子,调用printf("%d %s", 123, "test")时,可变参数的顺序是123(第一个可变参数)、"test"(第二个可变参数)。va_arg会先获取123,再获取"test"——这是C标准对可变参数机制的定义所保证的,这个顺序是确定的,和实参的求值顺序无关。
3. 为什么printf这类函数能正常工作?
printf的实现不依赖实参的求值顺序,而是依赖格式字符串的占位符顺序与可变参数的位置顺序匹配。
比如printf("%d %d", f1(), f2()),不管f1()和f2()谁先执行,它们的返回值都会被放到参数列表的对应位置(第一个可变参数位置、第二个可变参数位置)。va_arg会按照格式字符串的要求,依次从这些固定位置取参数,最终输出的结果只会和两个函数的返回值有关,和它们的执行顺序无关。
换句话说,实参求值顺序影响的是“哪个函数先执行”,但不会改变“每个参数值在参数列表中的位置”——而va_arg是按位置顺序取参数的,所以这类函数可以稳定实现。
内容的提问来源于stack exchange,提问作者fearless_fool

