下面的 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)))

Link to stdarg.h file

存在带逗号运算符的表达式。

第一个子表达式

(((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 的这个定义中还有一些其他的神奇之处——这不足为奇,因为它是一个非常神奇的宏! -- 但这至少应该解释逗号运算符在那里做什么。