下面的 va_arg 宏是如何工作的?
How does the following va_arg macro work?
我正在阅读一个 stdarg.h(下面的 link)头文件,它定义了 va_arg 宏,如下所示
/*
* Increment ap to the next argument in the list while returing a
* pointer to what ap pointed to first, which is of type t.
*
* We cast to void* and then to t* because this avoids a warning about
* increasing the alignment requirement.
*/
#define va_arg(ap, t) \
(((ap) = (ap) + __va_argsiz(t)), \
*((t*) (void*) ((ap) - __va_argsiz(t))))
行
((ap) = (ap) + __va_argsiz(t))
重新分配 ap 的值,但我不明白逗号或行
的用途
*((t*) (void*) ((ap) - __va_argsiz(t)))
存在带逗号运算符的表达式。
第一个子表达式
(((ap) = (ap) + __va_argsiz(t))
增加指针ap
.
第二个子表达式
*((t*) (void*) ((ap) - __va_argsiz(t))))
returns 指针指向的值 ap
在其递增之前。
如果您对逗号运算符有疑问,那么它允许将多个完整表达式组合成一个表达式。
例如考虑以下程序
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2 };
size_t i = 0;
printf( "%d\n", ( i++, a[i] ) );
return 0;
}
它的输出是
2
此处 printf
调用的参数是带有逗号运算符的表达式。
或者更有趣的例子
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2 };
size_t i = 0;
printf( "%d\n", a[i] ), i++, printf( "%d\n", a[i] );
return 0;
}
程序输出为
1
2
这里是表达式语句
printf( "%d\n", a[i] ), i++, printf( "%d\n", a[i] );
包含一个使用两个逗号运算符的表达式。
我们需要return向调用者ap
指向什么,和提前ap
。等价物是
old_ap = ap
ap = ap + argsiz
return *old_ap
但是,这将需要一个额外的变量,这很难(如果可能的话)以可移植的方式在宏中处理。相反,宏依赖于逗号表达式。它前进 ap
,然后计算它的旧值,它成为逗号表达式的值,即整个宏的值。
假设我有一个 char *
指针 p
指向一些字符。假设我想 return p
指向的字符,同时递增 p
指向下一个字符。这很简单,这只是 *p++
-- C 中的一个完全基本的操作。
并且由于指针算法的工作方式,如果我有一个指向某些整数的 int *
指针 ip
,我可以做完全相同的事情。 *ip++
return 是 ip
指向的整数,它递增 ip
以指向下一个整数。至关重要的是,如果我们在这个操作之前和之后查看指针 ip
的实际值,我们会发现它已经增加了 sizeof(int)
,而不仅仅是增加了 1.
现在,va_arg(ap, t)
有点像 *ip++
。它 return 参数 "pointed to" 为 ap
,并且递增 ap
以指向列表中的下一个事物。 但是,而且这是一个非常大的"but",我们不一定要增加sizeof(int)
。我们希望递增 sizeof(t)
,其中 t
是调用者告诉我们的列表中下一个预期参数类型。 (我们可以假设指针ap
的实际类型是char *
,这样它就逐个字节递增,这样给它加sizeof(t)
才有意义。)
所以我们想要的是 *(ap += sizeof(t))
的效果。但是 +=
给你前增量,而我们想要后增量。虽然 C 为您提供了前递增 ++
和后递增 ++
之间的便利区别,但 +=
运算符始终是预递增的。没有后增量形式。
因此,您为 va_arg
显示的代码必须使用逗号运算符分隔的两个单独的表达式,以困难的方式模拟后增量 +=
。首先,它使用子表达式 (ap) = (ap) + __va_argsiz(t)
将 sizeof(int)
添加到 ap
,然后它 return 是 ap
用来指向的内容,方法是减去刚刚添加的大小: *((t*) (void*) ((ap) - __va_argsiz(t)))
.
逗号运算符总是按顺序做两件事:它做第一件事并丢弃结果,然后它做第二件事并且 return 是第二件事的值。 v_arg
的这个定义被安排来利用那个定义。我们不关心第一个子表达式的值——第一个表达式的要点是它的副作用,向 ap
添加一些东西。我们需要的值(即va_arg
的值)是第二个子表达式。
(换句话说,根据逗号运算符的工作方式,不可能编写这样的表达式:首先 returns ap
指向的内容,然后向 [=24 添加内容=]。所以这个va_arg
宏的作者只好把加法做得太早了一点,意思是后半部分要用减法来抵消。)
在 va_arg
的这个定义中还有一些其他的神奇之处——这不足为奇,因为它是一个非常神奇的宏! -- 但这至少应该解释逗号运算符在那里做什么。
我正在阅读一个 stdarg.h(下面的 link)头文件,它定义了 va_arg 宏,如下所示
/*
* Increment ap to the next argument in the list while returing a
* pointer to what ap pointed to first, which is of type t.
*
* We cast to void* and then to t* because this avoids a warning about
* increasing the alignment requirement.
*/
#define va_arg(ap, t) \
(((ap) = (ap) + __va_argsiz(t)), \
*((t*) (void*) ((ap) - __va_argsiz(t))))
行
((ap) = (ap) + __va_argsiz(t))
重新分配 ap 的值,但我不明白逗号或行
的用途*((t*) (void*) ((ap) - __va_argsiz(t)))
存在带逗号运算符的表达式。
第一个子表达式
(((ap) = (ap) + __va_argsiz(t))
增加指针ap
.
第二个子表达式
*((t*) (void*) ((ap) - __va_argsiz(t))))
returns 指针指向的值 ap
在其递增之前。
如果您对逗号运算符有疑问,那么它允许将多个完整表达式组合成一个表达式。
例如考虑以下程序
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2 };
size_t i = 0;
printf( "%d\n", ( i++, a[i] ) );
return 0;
}
它的输出是
2
此处 printf
调用的参数是带有逗号运算符的表达式。
或者更有趣的例子
#include <stdio.h>
int main(void)
{
int a[] = { 1, 2 };
size_t i = 0;
printf( "%d\n", a[i] ), i++, printf( "%d\n", a[i] );
return 0;
}
程序输出为
1
2
这里是表达式语句
printf( "%d\n", a[i] ), i++, printf( "%d\n", a[i] );
包含一个使用两个逗号运算符的表达式。
我们需要return向调用者ap
指向什么,和提前ap
。等价物是
old_ap = ap
ap = ap + argsiz
return *old_ap
但是,这将需要一个额外的变量,这很难(如果可能的话)以可移植的方式在宏中处理。相反,宏依赖于逗号表达式。它前进 ap
,然后计算它的旧值,它成为逗号表达式的值,即整个宏的值。
假设我有一个 char *
指针 p
指向一些字符。假设我想 return p
指向的字符,同时递增 p
指向下一个字符。这很简单,这只是 *p++
-- C 中的一个完全基本的操作。
并且由于指针算法的工作方式,如果我有一个指向某些整数的 int *
指针 ip
,我可以做完全相同的事情。 *ip++
return 是 ip
指向的整数,它递增 ip
以指向下一个整数。至关重要的是,如果我们在这个操作之前和之后查看指针 ip
的实际值,我们会发现它已经增加了 sizeof(int)
,而不仅仅是增加了 1.
现在,va_arg(ap, t)
有点像 *ip++
。它 return 参数 "pointed to" 为 ap
,并且递增 ap
以指向列表中的下一个事物。 但是,而且这是一个非常大的"but",我们不一定要增加sizeof(int)
。我们希望递增 sizeof(t)
,其中 t
是调用者告诉我们的列表中下一个预期参数类型。 (我们可以假设指针ap
的实际类型是char *
,这样它就逐个字节递增,这样给它加sizeof(t)
才有意义。)
所以我们想要的是 *(ap += sizeof(t))
的效果。但是 +=
给你前增量,而我们想要后增量。虽然 C 为您提供了前递增 ++
和后递增 ++
之间的便利区别,但 +=
运算符始终是预递增的。没有后增量形式。
因此,您为 va_arg
显示的代码必须使用逗号运算符分隔的两个单独的表达式,以困难的方式模拟后增量 +=
。首先,它使用子表达式 (ap) = (ap) + __va_argsiz(t)
将 sizeof(int)
添加到 ap
,然后它 return 是 ap
用来指向的内容,方法是减去刚刚添加的大小: *((t*) (void*) ((ap) - __va_argsiz(t)))
.
逗号运算符总是按顺序做两件事:它做第一件事并丢弃结果,然后它做第二件事并且 return 是第二件事的值。 v_arg
的这个定义被安排来利用那个定义。我们不关心第一个子表达式的值——第一个表达式的要点是它的副作用,向 ap
添加一些东西。我们需要的值(即va_arg
的值)是第二个子表达式。
(换句话说,根据逗号运算符的工作方式,不可能编写这样的表达式:首先 returns ap
指向的内容,然后向 [=24 添加内容=]。所以这个va_arg
宏的作者只好把加法做得太早了一点,意思是后半部分要用减法来抵消。)
在 va_arg
的这个定义中还有一些其他的神奇之处——这不足为奇,因为它是一个非常神奇的宏! -- 但这至少应该解释逗号运算符在那里做什么。