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

4

90

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?

Confiscate answered 16/7, 2023 at 12:22 Comment(4)
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.Nakada
Full code is here: gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gatomic.cConfiscate
One of my favorite things is when two apparent separate mysteries answer each other.Pinero
I'm not sure saying something that will cause a stack overflow is a pun just because you posted it on Stack Overflow...Repellent
N
122
  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.

Nakada answered 16/7, 2023 at 12:34 Comment(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?Pruter
@Pruter 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.Ellary
It'll avoid the expansion of function like macros, but not the regular ones.Saprophyte
S
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).

Saarinen answered 16/7, 2023 at 12:35 Comment(9)
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.Apoplectic
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 xSaarinen
@DanielSchepler And then you get bitten by sqr(++i)Carp
@Carp how do you avoid getting bitten by sqr(++i) ?Please
@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.Saarinen
Why have both a macro and a function with the same name though? I get what this does, but not why it is useful.Jeroldjeroma
@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:Saarinen
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.Saarinen
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 codeSaarinen
D
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.

Daile answered 16/7, 2023 at 12:55 Comment(2)
"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.Quinze
@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.Mycobacterium
V
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.
Verisimilar answered 19/7, 2023 at 18:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.