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?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Andreas
  • 9,245
  • 9
  • 49
  • 97
  • 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 '23 at 12:27
  • 4
    Full code is here: https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gatomic.c – Andreas Jul 16 '23 at 12:29
  • 22
    One of my favorite things is when two apparent separate mysteries answer each other. – Daniel R. Collins Jul 16 '23 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... – ScottishTapWater Jul 18 '23 at 10:06

5 Answers5

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

Gerhardh
  • 11,688
  • 4
  • 17
  • 39
  • 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 '23 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. – val - disappointed in SE Jul 18 '23 at 17:14
  • 1
    It'll avoid the expansion of function like macros, but not the regular ones. – Aykhan Hagverdili Jul 19 '23 at 07: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).

chrslg
  • 9,023
  • 5
  • 17
  • 31
  • 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. – Daniel Schepler Jul 17 '23 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 '23 at 18:01
  • 8
    @DanielSchepler And then you get bitten by `sqr(++i)` – dobiwan Jul 18 '23 at 06:45
  • 1
    @dobiwan how do you avoid getting bitten by `sqr(++i)` ? – Zorgatone Jul 18 '23 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 '23 at 13:33
  • 1
    Why have both a macro and a function with the same name though? I get **what** this does, but not **why** it is useful. – Alex Mandelias Jul 18 '23 at 18:33
  • 1
    @AlexMandelias That is another question. I don't really know. That is a choice made by glib devs. One pure speculation (from me, trying to figure out in what kind of situation I may be tempted to do so myself): maybe they have some internal version of the same operations, and wanted to avoid duplicate code. Just one example, in the same spirit as my `sqr`. Imagine you are trying to provide in a library a function `sqrt`. The "official" `sqrt` function, the one you provide to the library user, does some checking: – chrslg Jul 18 '23 at 18:47
  • 1
    It checks that argument is positive. But your library also makes internal usage of `sqrt`. And since this is your own code, sometimes, you know you don't need to check (because you know it is already positive). So you want a `sqrt` without positivity checking. But, nevertheless, you don't want to code twice `sqrt`. So you put it in a macro. Use the macro directly for yourself. And wrap it in a nice function for the user. – chrslg Jul 18 '23 at 18:50
  • 1
    Obviously, it is not exactly what happens here (the functions doesn't even add any such checking). But I wouldn't be surprise that it is still this kind of reason. They expose real functions for the user. But the use macro internally, for some optimization reason. And didn't want to have two version of the code – chrslg Jul 18 '23 at 18:52
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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 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. – Thomas Weller Jul 17 '23 at 06: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. – Toby Speight Jul 18 '23 at 07: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.
plugwash
  • 9,724
  • 2
  • 38
  • 51
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
}
ryyker
  • 22,849
  • 3
  • 43
  • 87
zoldxk
  • 2,632
  • 1
  • 7
  • 29