括号中的 C 函数声明显然永远称呼自己有什么意义?

What's the significance of a C function declaration in parentheses apparently forever calling itself?

提问人:Andreas 提问时间:7/16/2023 最后编辑:genpfaultAndreas 更新时间:7/25/2023 访问量:8334

问:

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

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);
}

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

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

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

我根本无法理解这个函数声明。这到底是怎么回事?

C glib 函数声明

评论

14赞 Gerhardh 7/16/2023
如果没有完整的代码,就很难分辨。将函数名称放在括号中可避免任何宏扩展,以防存在同名的宏等函数。也就是说,这可能是所述宏的包装函数。但这只是猜测。
4赞 Andreas 7/16/2023
完整的代码在这里:gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gatomic.c
22赞 Daniel R. Collins 7/17/2023
我最喜欢的事情之一是当两个看似独立的谜团相互回答时。
5赞 ScottishTapWater 7/18/2023
我不确定仅仅因为您在 Stack Overflow 上发布它而说会导致堆栈溢出的东西是双关语......

答:

120赞 Gerhardh 7/16/2023 #1
  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;

使用函数而不是宏。

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

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

评论

0赞 Pkkm 7/18/2023
他们这样做而不是在头文件中定义函数或使用普通函数并启用 LTO 的可能原因是什么?static inline
2赞 val - disappointed in SE 7/19/2023
即使在今天,@Pkkm LTO 也不是无处不在,当从共享 glib 使用它时,这也不适用于此功能的非 glib 用户(大多数 Linux 系统都是这种情况)。为什么不呢——这是我不太确定的事情;可能是为了避免低优化级别过多地减慢热路径。static inline
1赞 Aykhan Hagverdili 7/19/2023
它将避免像宏这样的功能扩展,但不会像常规宏一样扩展。
49赞 chrslg 7/16/2023 #2

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

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

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

int (fn)(int param){
    fn(param);
}

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

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

sqr.c的

#define sqr(x) x*x
int (sqr)(int x){
   return sqr(x);
}

主.c

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

编译方式gcc -o main main.c sqr.c

正在运行打印。答案是肯定的。./main144

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

extern int sqr(int);
int main(){
    printf("%d\n", sqr(12));
}

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

预处理给出sqr.c

int (sqr)(int x){
   return x*x;
}

而要点:是一个函数,其代码是 的宏扩展。sqrsqr(x)

评论

11赞 Daniel Schepler 7/18/2023
当然,任何 C 程序员都可能会反射性地将宏定义为这样,并且会按预期工作。#define sqr(x) ((x)*(x))5 / sqr(2)sqr(2+3)
6赞 chrslg 7/18/2023
事实上。我犹豫要不要这样做。但我不想再添加其他东西来解释,因为目标不是告诉“如何编码宏”,而是“为什么它会这样”。所以我只是小心翼翼地,有点怯懦地,选择了一个无关紧要的例子:D。另外,请注意,正是对于这个问题,这并不是真正的问题。作为一个宏观,它很重要。 最终会变成 = 确实。但是对于这个问题(和这个例子),如果宏仅用于创建函数,则 argument 不是 。宏的参数是符号sqr(2+3)2+3*2+3112+3x
8赞 dobiwan 7/18/2023
@DanielSchepler 然后你被咬了sqr(++i)
1赞 Zorgatone 7/18/2023
@dobiwan如何避免被咬?sqr(++i)
6赞 chrslg 7/18/2023
@Zorgatone:粗略地说,我想说,对于宏,你没有。如果你使用宏,这就是你所要求的:重复表达式,而不是值。或者,您完全按照此答案(以及 OP 所示的代码)中所做的操作:定义一个使用宏的函数。在这里,当您调用函数 时,++i 被计算,传递给函数 ,在该函数中调用它。然后计算。因此,最后返回 36,而不是 42 :D。因为这里它只是一个函数。宏只是用作参数,而不是 .sqr(++i)sqrxx*xi=5; sqr(++i)x++i
19赞 Vlad from Moscow 7/16/2023 #3

有一个在标头中定义名称的宏:g_atomic_int_compare_and_exchange_fullgatomic.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;                               \
  }))

并且有一个同名的函数。

当名称括在括号中时,如下所示,

gboolean
(g_atomic_int_compare_and_exchange_full) (gint *atomic,
                                          gint  oldval,
                                          gint  newval,
                                          gint *preval)

那么编译器就不能把它当成一个宏了。

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

下面是一个演示程序:

#include <stdio.h>

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

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

int main( void )
{
    printf( "%d\n", (from)( 10 ) );
}

请注意,声明符(包括函数声明符)可以括在括号中。

从定义声明符的 C 语法中:

direct-declarator:
    identifier
    ( declarator )

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

int ( ( ( a )[10] )[10] );

尽管括号显然是多余的。

评论

7赞 Thomas Weller 7/17/2023
“编译器不能将其视为宏” - 恕我直言,编译器永远不会将东西视为宏。在编译时,预处理器已经消除了所有宏。
5赞 Toby Speight 7/18/2023
@Thomas:这取决于你是否认为预处理是编译器的一部分。根据 ISO 9899,预处理是翻译的(概念)阶段之一(我认为没有提到“编译器”这个词),所以你可以以任何一种方式争论它。
4赞 plugwash 7/20/2023 #4

正如其他人所说,这允许将某物定义为类似函数的宏和实函数。

你为什么要这样做?我可以看到几个原因。

  1. 向后兼容性。出于性能或灵活性的原因,库作者可能会从实际函数切换到类似函数的宏,但仍希望保持与调用实际函数的现有二进制文件的兼容性。
  2. 与其他编程语言的兼容性。用 C 编写的宏只能有效地从 C 和 C++ 等语言中使用,这些语言或多或少是 C 的扩展。可以从任何支持 C 调用约定的语言调用实函数。
  3. 实函数可以与函数指针一起使用,而类似函数的宏则不能。
2赞 zoldxk 7/20/2023 #5

您提供的代码不是完整的函数定义,而是实际函数的“包装器”或“函数别名”。让我们来分析一下这里发生的事情:g_atomic_int_compare_and_exchange_full

  1. 函数别名:您提供的代码为函数创建别名。别名是使用函数名称两边的括号创建的,后跟函数的参数列表和正文。此技术通常在宏中用于定义类似函数的宏。在这种情况下,宏似乎是实际函数的别名。g_atomic_int_compare_and_exchange_full
  2. 递归调用:你说得对,代码似乎会导致递归调用,这会导致堆栈溢出。但是,此代码片段可能是更大上下文的一部分,函数的真正定义应该在其他地方。实际函数将具有适当的实现,而不是对自身的递归调用。g_atomic_int_compare_and_exchange_full

为了更清楚地说明这一点,实际实现应该在其他地方定义,它可能看起来像这样:g_atomic_int_compare_and_exchange_full

gboolean g_atomic_int_compare_and_exchange_full(gint *atomic,
                                                gint oldval,
                                                gint newval,
                                                gint *preval)
{
  // Actual implementation of the atomic compare and exchange operation
  // This function performs an atomic compare-and-exchange on the given integer value.
  // It compares the value at the memory location "atomic" with "oldval", and if they are equal,
  // it updates the value at "atomic" to "newval". The current value at "atomic" is returned in "preval".

  // ... Implementation details ...
  // (Actual atomic compare-and-exchange code)
  // ...

  return TRUE; // or FALSE, depending on whether the exchange was successful
}