89

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?

4
  • 14
    Without having the full code, it is hard to tell. Putting a function name in brackets avoids any macro expansion in case there is a function like macro with the same name. That said, this could be a wrapper function for said macro. But that is just guessing.
    – Gerhardh
    Jul 16, 2023 at 12:27
  • 4
    Full code is here: gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gatomic.c
    – Andreas
    Jul 16, 2023 at 12:29
  • 22
    One of my favorite things is when two apparent separate mysteries answer each other. Jul 16, 2023 at 23:49
  • 5
    I'm not sure saying something that will cause a stack overflow is a pun just because you posted it on Stack Overflow... Jul 18, 2023 at 10:06

5 Answers 5

121
  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.

3
  • What are the possible reasons they did it this way instead of defining a static inline function in the header file or using a normal function and enabling LTO?
    – Pkkm
    Jul 18, 2023 at 15:55
  • 2
    @Pkkm LTO is not ubiquitous even today and this also wouldn't work for non-glib users of this function when using it from shared glib (which would be the case on most Linux systems). Why not static inline - that is something I'm less sure about; possibly to avoid low optimization levels slowing down hot paths too much. Jul 18, 2023 at 17:14
  • 1
    It'll avoid the expansion of function like macros, but not the regular ones. Jul 19, 2023 at 7:14
49

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).

9
  • 11
    Of course, any C programmer would probably reflexively define the macro as #define sqr(x) ((x)*(x)) so for example 5 / sqr(2) and sqr(2+3) will work as expected. Jul 17, 2023 at 16:34
  • 6
    Indeed. I hesitated to do it. But I didn't want to add another thing to explain, since the goal wasn't to tell "how to code a macro", but "why it behaves like that". So I've just, cautiously, and a bit cowardly, chosen an example for which it doesn't matter :D. Also, note that, precisely for this question, it is not really a problem. As a macro, it matters. sqr(2+3) would end up as 2+3*2+3=11 indeed. But for this question (and this example), argument is not 2+3, if the macro is only used to create the function. Argument of macro is symbol x
    – chrslg
    Jul 17, 2023 at 18:01
  • 8
    @DanielSchepler And then you get bitten by sqr(++i)
    – dobiwan
    Jul 18, 2023 at 6:45
  • 1
    @dobiwan how do you avoid getting bitten by sqr(++i) ?
    – Zorgatone
    Jul 18, 2023 at 12:30
  • 6
    @Zorgatone: roughly, I would say, with macro, you don't. That is what you asked for if you use a macro: to repeat the expression, not the value. Or you do exactly what is done in this answer (and in the code shown by OP): you define a function that uses a macro. Here when you call function sqr(++i), ++i is evaluated, passed to the function, sqr, in which it is called x. And then x*x is computed. So at the end, i=5; sqr(++i) returns 36, not 42 :D. Because here it is just a function. And the macro is just used with x as argument, not ++i.
    – chrslg
    Jul 18, 2023 at 13:33
19

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.

2
  • 7
    "compiler can not consider it as a macro" - The compiler will never consider stuff as a macro, IMHO. At compilation time, all macros have already been eliminated by the preprocessor. Jul 17, 2023 at 6:09
  • 5
    @Thomas: That depends whether you consider preprocessing to be part of the compiler or not. According to ISO 9899, preprocessing is one of the (conceptual) phases of translation (and I don't think the word "compiler" is mentioned) so you could argue it either way. Jul 18, 2023 at 7:44
4

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.
2

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
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.