[英]How does va_arg actually work in variadic functions?
我阅读了可变参数函数以及数据类型和宏如何工作,我以为我理解它是如何工作的,但我发现有些不同,这是我一直在试验的内容,我对不同宏和数据类型如何工作的理解:
typedef char * va_list;
#define va_start( ap, v ) ap = (char *)&v + sizeof( v )
#define va_arg( ap, t ) ( (t *) ( ap += sizeof( t ) )[-1]
#define va_end( ap ) ap = NULL
所以这只是一个指针,它一次移动 1 个字节并移动t
数据类型使用的字节数,所以我试图在不使用这个宏或<stdarg.h>
情况下复制行为,只是为了更好地理解如何它有效,使用标头的函数的代码是这样的:
int _print_ints(int n, ...)
{
va_list listArgs;
va_start( listArgs, n );
for(int i = 0; i < n; i++)
{
printf("%3d ", va_arg( listArgs, int ) );
}
va_end( listArgs );
}
它按预期工作,我制作的代码是这样的:
int print_ints(int n, ...)
{
char *arg = (char *)&n;
for(int i = 0; i < n; i++)
{
int val = *( (int *) (arg += sizeof( int ) * 2 ) );
printf("%3d ", val);
}
arg = NULL;
}
这个也按预期工作,但是,您看到我必须移动两倍于我必须首先移动的假设大小,而不是每个变量移动4 bytes
(在我的系统中为 int 大小),我必须移动8 bytes
这是一个长长的大小,代码中的常量变量是int
所以我真的不明白为什么在堆栈中为了从一个变量移动到另一个我必须移动两倍的大小,这是完整的代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int _print_ints(int n, ...)
{
va_list listArgs;
va_start( listArgs, n );
for(int i = 0; i < n; i++)
{
printf("%3d ", va_arg( listArgs, int ) );
}
va_end( listArgs );
}
int print_ints(int n, ...)
{
char *arg = (char *)&n;
for(int i = 0; i < n; i++)
{
int val = *( (int *) (arg += sizeof( int ) * 2 ) );
printf("%3d ", val);
}
arg = NULL;
}
int main( void )
{
_print_ints(5, 1, 2, 3, 4, 5);
printf("\n");
print_ints(5, 6, 7, 8, 9, 10);
return EXIT_SUCCESS;
}
它输出:
1 2 3 4 5
6 7 8 9 10
总结一下,为什么我必须移动sizeof( int ) * 2
而不是sizeof( int )
?
可能是我得到信息的那本书已经过时了
首先,我不确定您是否从系统的stdarg.h
头文件中获得了这些宏,或者您只是认为编译器正在使用这些宏,因为它很可能不是。 使用-E -P
编译您的源代码以仅应用预处理器并查看预处理后_print_ints()
样子。 在现代编译器上, va_*
宏只是编译器内置的(你会在输出中看到类似__builtin_va_arg(...)
东西),因此没有 C 等价物,只有编译器知道它们是如何实现的以及那些需要的代码.
现在,如果您展示的那些确实是您系统上正在使用的宏,那么您对va_start()
和va_arg()
重新实现就是错误的:
char *arg = (char *)&n; // wrong
char *arg = (char *)&n + sizeof(n); // correct
int val = *( (int *) (arg += sizeof( int ) * 2 ) ); // wrong
int val = *((int *)(arg += sizeof(int)) - 1); // correct
但是,由于您正在执行+= sizeof(int) * 2
并且奇怪地获得了正确的结果,这可能意味着您的编译器确实以与您想象的不同的方式实现了va_start()
和va_arg()
,很可能会对齐您的堆栈上的 4 字节整数到 8 字节边界(如果您在 64 位系统上可能有意义)。
在任何情况下,您最有可能获得正确的结果,因为您正在使用野指针,因此如果您不小心,您所做的事情很容易最终成为未定义的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.