在此代码中,第一个参数值为1,表示传递的参数总数。
在被调用的函数中,我能够访问所有参数。
然后第一个参数有什么用?
不应该限制我访问多个参数。
int main(){
fun1(1, "Apple", "Boys", "Cats", "Dogs");
return 0;
}
void fun1(int num, ...){
char *str;
va_list ptr;
va_start(ptr, num);
str = va_arg(ptr, char *);
str = va_arg(ptr, char *);
printf("%s ", str);
}
输出:Boys
答案 0 :(得分:6)
第一个参数的作用通常是确定可变参数的数量,有时也是类型(例如:printf()
)。
在x86架构上,如果使用 cdecl调用约定,则第一个参数是堆栈中最顶层的参数(具有最低地址的参数,即:在堆栈上推送的最后一个参数) 。所以你总是知道它的地址:
-------------------------------
| 1st Arg |
-------------------------------
| return address | 4 bytes
-------------------------------
| saved old stack frame (EBP) | 4 bytes
------------------------------- <-- EBP
EBP
+ 8包含第一个参数的地址。
答案 1 :(得分:6)
对这个问题的答案有两种观点。
va_start()
和va_arg()
必须知道在哪里找到参数。通常,编译器将计算所有参数的固定内存位置。在编译时,这是不可能的,具有可变参数功能。 stdarg
函数由运行时提供,并且知道如何在堆栈上找到参数,但它们需要一些引用。因此,您始终必须在编译期间知道一个位置:最后一个非可变参数的位置。然后va_start()
和va_args()
可以计算下一个参数的位置。
这很可能是由于John Bollinger引用标准中至少需要一个非变量参数的实际原因,感谢这句话。
如果您尝试读取的参数多于传递的参数,则会导致未定义的行为,因为您从没有找到参数的内存位置读取。所以你需要知道何时停止。实现此目的的一种简单方法是只传递以下参数的数量,就像在您的代码段中一样。一个众所周知的可变参数函数是printf()
,它知道解析格式字符串时的参数数量。
因此,要解决您的编辑问题:没有自动限制,您可以自行决定如何让您的函数知道要阅读的参数数量,并且您只需阅读有效参数即可。
答案 2 :(得分:3)
fun1
是variadic。这是三个点...
的含义。需要有一些方法来告诉函数它的参数列表有多长。理想情况下,还有一种方法可以告诉函数其参数的类型是什么。格式字符串printf
是执行此操作的方案的示例。
不要使用可变参数函数,传递一个数组。使用可变参数函数,您几乎没有编译器支持来检测何时传递了错误的数字或类型的参数。
答案 3 :(得分:2)
如果不是第一个参数,你就不知道何时停止解析其他参数。
答案 4 :(得分:2)
要回答您的直接问题,在某些平台上需要最后一个显式参数(在您的情况下是第一个也是唯一的参数)来确定参数列表在内存中的起始位置。没有它va_start
将无法始终确定指向何处。至少还有一个显式参数也可用于确定有多少可变参数。
答案 5 :(得分:2)
fun1
无法知道函数调用中实际传递了多少个参数;堆栈(或寄存器)中没有任何元数据表示,&#34;是的,这块内存对应于从调用者传递的参数&#34;。
尝试将fun1
称为fun1(1);
(或任何其他整数值),看看会发生什么。
Variadic函数必须信任其调用者,以便在固定参数列表中为他们提供所需的信息,以正确读取任何剩余的变量参数。如果您将fun1
称为fun1(0)
,它仍会尝试在堆栈或寄存器中找到两个char *
对象,并尝试打印第二个,最有可能导致运行时错误。
fun1
应该查看num
参数,而不是尝试再读取任何参数。您必须信任fun1
的调用者才能传递与num
对应的正确数字和类型。
答案 6 :(得分:1)
num
中的 void fun1(int num, ...){
有两个目的:
...
之前的参数,在这种情况下为int num
,用于指示参数的变量数...
- 开始并初始化{{1}的位置开始访问。 C指定 。
va_list
通常 #include <stdarg.h>
void fun1(int num, ...){
va_list ap;
va_start(ap, num);
char *s = va_arg(ap, char *);
之前的参数,以某种方式表示以下参数的数量和类型。这是未指定由C.一些典型的实现包括:
...
之前的参数是以下参数的计数。这似乎与OP的预期方法很接近。
格式传递包含有关参数类型和数量的各种信息。 ...
sentinel :printf("%s %d\n", "Age", 25);
之前的参数不表示增量计数,但最后传递的是特殊:{{1 }}
使用全局变量来指示...
之后的参数数量。这种编码很少是好的做法。
我希望OP的代码是使用第一个参数来表示以下参数的计数。
sum(1,2,3,INT_MIN);