括号中的 C 函数声明显然永远调用自身,有什么意义?

And*_*eas 89 c macros function glib function-declaration

gatomic.cglib 中,有几个函数声明如下所示:

gboolean
(g_atomic_int_compare_and_exchange_full) (gint *atomic,
                                          gint  oldval,
                                          gint  newval,
                                          gint *preval)
{
  return g_atomic_int_compare_and_exchange_full (atomic, oldval, newval, preval);
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下这段代码到底是做什么的吗?我对这里的几件事感到困惑:

  1. 函数名称g_atomic_int_compare_and_exchange_full位于括号内。这有什么意义呢?

  2. 该函数的主体显然只包含对函数本身的调用,因此这将永远运行并导致堆栈溢出(双关语)。

我根本无法理解这个函数声明。这里究竟发生了什么?

Ger*_*rdh 121

  1. 函数名称 g_atomic_int_compare_and_exchange_full 在括号中。这有什么意义呢?

将函数名称放在括号中可以避免任何宏扩展,以防存在同名的类似宏的函数。

这意味着,g_atomic_int_compare_and_exchange_full(...)将使用宏,同时(g_atomic_int_compare_and_exchange_full)(...)将使用函数。

为什么要使用这个?您不能将宏分配给函数指针。在这种情况下,您可以提供您所看到的定义,然后您可以使用

ptr = g_atomic_int_compare_and_exchange_full;
Run Code Online (Sandbox Code Playgroud)

使用函数而不是宏。

  1. 该函数的主体显然只包含对函数本身的调用,因此这将永远运行并导致堆栈溢出(双关语)。

如果您查看关联的标头,gatomic.h您会发现确实定义了这样的宏。并且在函数体中,函数名称周围没有使用括号。这意味着,使用了宏并且它不是无限递归。

  • @Pkkm LTO 即使在今天也不是普遍存在的,并且当从共享 glib 使用该函数时,这对于非 glib 用户也不起作用(大多数 Linux 系统上都是这种情况)。为什么不是“静态内联”——这是我不太确定的事情;可能是为了避免低优化级别过多地减慢热路径的速度。 (2认同)

chr*_*slg 49

答案在您链接的代码的注释中给出。

以下是编译器宏的集合,用于提供对整数和指针大小的值的原子访问。

顺便说一句,与@Gerhardh 评论相关。

int (fn)(int param){
    fn(param);
}
Run Code Online (Sandbox Code Playgroud)

定义一个函数fn,其操作也是宏fn扩展的任何内容。

第一次出现的括号fn是为了避免扩展这个,这显然会导致代码不一致。

例子

sqr.c

#define sqr(x) x*x
int (sqr)(int x){
   return sqr(x);
}
Run Code Online (Sandbox Code Playgroud)

主程序

#include <stdio.h>
extern int sqr(int);
int main(){
    printf("%d\n", sqr(12));
}
Run Code Online (Sandbox Code Playgroud)

编译用gcc -o main main.c sqr.c

运行./main打印144。当然。

但更有趣的是,经过预处理后的 main.c 看起来像 ( gcc -E main.c)

extern int sqr(int);
int main(){
    printf("%d\n", sqr(12));
}
Run Code Online (Sandbox Code Playgroud)

(所以,sqr这里是一个函数。如果它是一个宏,那么它现在已经被扩展了)

sqr.c预处理给出

int (sqr)(int x){
   return x*x;
}
Run Code Online (Sandbox Code Playgroud)

要点是:sqr是一个函数,其代码是 的宏展开sqr(x)

  • 当然,任何 C 程序员可能都会条件反射地将宏定义为“#define sqr(x) ((x)*(x))”,例如“5 / sqr(2)”和“sqr(2+3)”将按预期工作。 (11认同)
  • @DanielSchepler 然后你就会被 `sqr(++i)` 咬住 (8认同)
  • 的确。我犹豫着要不要这么做。但我不想添加另一件事来解释,因为目标不是告诉“如何编写宏”,而是“为什么它会这样”。所以我只是谨慎地、有点胆怯地选择了一个无关紧要的例子:D。另外,请注意,正是对于这个问题,这并不是一个真正的问题。作为一个宏观,它很重要。`sqr(2+3)` 最终会变成 `2+3*2+3`=`11`。但对于这个问题(以及这个例子),如果宏仅用于创建函数,则参数不是“2+3”。宏的参数是符号“x” (6认同)
  • @Zorgatone:粗略地说,我想说,对于宏,你不需要。如果您使用宏,这就是您所要求的:重复表达式,而不是值。或者你完全按照这个答案(以及OP所示的代码)中所做的操作:定义一个使用宏的函数。这里,当你调用函数 `sqr(++i)` 时,++i 被求值,传递给函数 `sqr`,在函数中它被称为 `x`。然后计算“x*x”。所以最后`i=5; sqr(++i)` 返回 36,而不是 42 :D。因为这里它只是一个函数。并且该宏仅与“x”作为参数一起使用,而不是“++i”。 (6认同)

Vla*_*cow 19

有一个宏,其名称g_atomic_int_compare_and_exchange_full在 header 中定义gatomic.h

#define g_atomic_int_compare_and_exchange_full(atomic, oldval, newval, preval) \
  (G_GNUC_EXTENSION ({                                                         \
    G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint));                       \
    G_STATIC_ASSERT (sizeof *(preval) == sizeof (gint));                       \
    (void) (0 ? *(atomic) ^ (newval) ^ (oldval) ^ *(preval) : 1);              \
    *(preval) = (oldval);                                                      \
    __atomic_compare_exchange_n ((atomic), (preval), (newval), FALSE,          \
                                 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)           \
                                 ? TRUE : FALSE;                               \
  }))
Run Code Online (Sandbox Code Playgroud)

并且有一个同名的函数。

当名称像这里一样用括号括起来时,

gboolean
(g_atomic_int_compare_and_exchange_full) (gint *atomic,
                                          gint  oldval,
                                          gint  newval,
                                          gint *preval)
Run Code Online (Sandbox Code Playgroud)

那么编译器就不能将其视为宏。

也就是说,您有一个函数定义,其中使用了与函数名称相同的宏。

这是一个演示程序:

#include <stdio.h>

#define from( x )( x ) * ( x )

int (from)( int x ) { return from( x ); }

int main( void )
{
    printf( "%d\n", (from)( 10 ) );
}
Run Code Online (Sandbox Code Playgroud)

请注意,声明符(包括函数声明符)可以用括号括起来。

从定义声明符的 C 语法来看:

direct-declarator:
    identifier
    ( declarator )
Run Code Online (Sandbox Code Playgroud)

下面是一个二维数组的声明,其中声明符括在括号中:

int ( ( ( a )[10] )[10] );
Run Code Online (Sandbox Code Playgroud)

尽管括号显然是多余的。

  • “编译器不能将其视为宏” - 恕我直言,编译器永远不会将东西视为宏。在编译时,所有宏都已被预处理器消除。 (7认同)
  • @Thomas:这取决于您是否认为预处理是编译器的一部分。根据 ISO 9899,预处理是_翻译_的(概念)_阶段_之一(我认为没有提到“编译器”这个词),所以你可以以任何一种方式争论它。 (5认同)