提问人:Andreas 提问时间:7/16/2023 最后编辑:genpfaultAndreas 更新时间:7/25/2023 访问量:8334
括号中的 C 函数声明显然永远称呼自己有什么意义?
What's the significance of a C function declaration in parentheses apparently forever calling itself?
问:
在 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);
}
有人可以解释一下这段代码到底是做什么的吗?我对这里的几件事感到困惑:
函数名称在 括弧。这有什么意义?
g_atomic_int_compare_and_exchange_full
函数的主体显然只包含对函数本身的调用,因此它将永远运行并导致堆栈 溢出(双关语)。
我根本无法理解这个函数声明。这到底是怎么回事?
答:
- 函数名称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;
使用函数而不是宏。
- 函数的主体显然只包含对函数本身的调用,因此这将永远运行并导致堆栈溢出(双关语)。
如果您查看关联的标头 gatomic.h
,您将看到确实定义了这样的宏。在函数体中,函数名称周围没有使用括号。这意味着,使用了宏,它不是无限递归。
评论
static inline
static inline
答案在您链接的代码的注释中给出。
下面是编译器宏的集合,用于提供对整数和指针大小值的原子访问。
顺便说一句,与@Gerhardh评论有关。
int (fn)(int param){
fn(param);
}
定义一个函数,其操作也是宏扩展的任何函数。fn
fn
第一次出现的括号是为了避免扩展这个括号,这显然会导致代码不一致。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
正在运行打印。答案是肯定的。./main
144
但更有趣的是,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;
}
而要点:是一个函数,其代码是 的宏扩展。sqr
sqr(x)
评论
#define sqr(x) ((x)*(x))
5 / sqr(2)
sqr(2+3)
sqr(2+3)
2+3*2+3
11
2+3
x
sqr(++i)
sqr(++i)
sqr(++i)
sqr
x
x*x
i=5; sqr(++i)
x
++i
有一个在标头中定义名称的宏:g_atomic_int_compare_and_exchange_full
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; \
}))
并且有一个同名的函数。
当名称括在括号中时,如下所示,
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] );
尽管括号显然是多余的。
评论
正如其他人所说,这允许将某物定义为类似函数的宏和实函数。
你为什么要这样做?我可以看到几个原因。
- 向后兼容性。出于性能或灵活性的原因,库作者可能会从实际函数切换到类似函数的宏,但仍希望保持与调用实际函数的现有二进制文件的兼容性。
- 与其他编程语言的兼容性。用 C 编写的宏只能有效地从 C 和 C++ 等语言中使用,这些语言或多或少是 C 的扩展。可以从任何支持 C 调用约定的语言调用实函数。
- 实函数可以与函数指针一起使用,而类似函数的宏则不能。
您提供的代码不是完整的函数定义,而是实际函数的“包装器”或“函数别名”。让我们来分析一下这里发生的事情:g_atomic_int_compare_and_exchange_full
- 函数别名:您提供的代码为函数创建别名。别名是使用函数名称两边的括号创建的,后跟函数的参数列表和正文。此技术通常在宏中用于定义类似函数的宏。在这种情况下,宏似乎是实际函数的别名。
g_atomic_int_compare_and_exchange_full
- 递归调用:你说得对,代码似乎会导致递归调用,这会导致堆栈溢出。但是,此代码片段可能是更大上下文的一部分,函数的真正定义应该在其他地方。实际函数将具有适当的实现,而不是对自身的递归调用。
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
}
评论