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

8.3k Views Asked by At

In gatomic.c of glib there are several function declarations that look like this:

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

Can someone explain what this code exactly does? I'm confused by several things here:

  1. The function name g_atomic_int_compare_and_exchange_full is in parentheses. What's the significance of this?

  2. The function's body apparently consists of nothing but a call to the function itself so this will run forever and result in stack overflow (pun intended).

I can't make any sense of this function declaration at all. What's really going on here?

5

There are 5 best solutions below

3
Gerhardh On BEST ANSWER
  1. The function name g_atomic_int_compare_and_exchange_full is in parentheses. What's the significance of this?

Putting a function name in brackets avoids any macro expansion in case there is a function like macro with the same name.

That means, g_atomic_int_compare_and_exchange_full(...) will use the macro while (g_atomic_int_compare_and_exchange_full)(...) will use the function.

Why is this used? You cannot assign a macro to a function pointer. In that case you can provide the definition as you saw it and then you can use

ptr = g_atomic_int_compare_and_exchange_full;

to use the function instead of the macro.

  1. The function's body apparently consists of nothing but a call to the function itself so this will run forever and result in stack overflow (pun intended).

If you look into the associated header gatomic.h you will see that such a macro is indeed defined. And in the function body, no brackets are used around the function name. That means, the macro is used and it is not an infinite recursion.

9
chrslg On

Answer is given in the comments of the code you've linked.

The following is a collection of compiler macros to provide atomic access to integer and pointer-sized values.

And related to @Gerhardh comment, btw.

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

Defines a function fn whose action is whatever macro fn expands too.

The parenthesis around the first occurrence of fn are there to avoid expansion of this one, which, obviously, would lead to inconsistent code.

Example

sqr.c

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

main.c

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

Compile with gcc -o main main.c sqr.c

Running ./main prints 144. Of course.

But more interestingly, main.c, after preprocessing looks like (gcc -E main.c)

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

(So, sqr is a function here. If it were a macro, it would have been expanded by now)

And sqr.c preprocessing gives

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

And that the main point: sqr is a function, whose code is the macro expansion of sqr(x).

2
Vlad from Moscow On

There is a macro with the name g_atomic_int_compare_and_exchange_full defined in 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;                               \
  }))

and there is a function with the same name.

When the name is enclosed in parentheses as here,

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

then the compiler can not consider it as a macro.

That is, you have a function definition within which there is used a macro with the same name as the name of the function.

Here is a demonstration program:

#include <stdio.h>

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

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

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

Pay attention to that a declarator (including a function declarator) may be enclosed in parentheses.

From the C grammar that defines declarators:

direct-declarator:
    identifier
    ( declarator )

Here is a declaration of a two-dimensional array with enclosing declarators in parentheses:

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

Though the parentheses evidently are redundant.

0
plugwash On

As others have said, this allows something to be defined as both a function-like macro, and as a real function.

Why would you want to do that? I can see several reasons.

  1. Backwards compatibility. A library author may switch from real functions to function-like macros for performance or flexibility reasons, but still want to keep compatibility with existing binaries that call the real functions.
  2. Compatibility with other programming languages. A macro written in C can effectively only be used from C and languages like C++ that are more or less extensions of C. A real function can be called from any language that supports C calling conventions.
  3. A real function can be used with function pointers, a function-like macro cannot.
0
zoldxk On

The code you provided is not a complete function definition but rather a "wrapper" or a "function alias" for the actual function g_atomic_int_compare_and_exchange_full. Let's break down what's happening here:

  1. Function Alias: The code you provided creates an alias for the function g_atomic_int_compare_and_exchange_full. The alias is created using parentheses around the function name and is followed by the function's parameter list and body. This technique is commonly used in macros to define function-like macros. In this case, it seems like the macro is aliased to the actual function.
  2. Recursive Call: You are right that the code, as it is, seems to lead to a recursive call, which would result in a stack overflow. However, this code snippet is likely part of a larger context, and the real definition of the function g_atomic_int_compare_and_exchange_full should be somewhere else. The real function would have a proper implementation and not be a recursive call to itself.

To make this more clear, the actual implementation of g_atomic_int_compare_and_exchange_full should be defined elsewhere, and it might look something like this:

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
}