Home > Enterprise >  GCC recommends a strange implementation for max macro
GCC recommends a strange implementation for max macro

Time:08-26

so , I was reading GCC documentation until I reached Statements and Declarations in Expressions section 6.1 , follow this link to reach the section I am talking about.

I came to these strange lines where GCC states that

This feature is especially useful in making macro definitions “safe” (so that they evaluate each operand exactly once). For example, the “maximum” function is commonly defined as a macro in standard C as follows:

#define max(a,b) ((a) > (b) ? (a) : (b))

But this definition computes either a or b twice, with bad results if the operand has side effects. In GNU C, if you know the type of the operands (here taken as int), you can avoid this problem by defining the macro as follows:

#define maxint(a,b) ({int _a = (a), _b = (b); _a > _b ? _a : _b; })

from my understand , GCC recommends knowing the type of the arguments, now I have many questions in my head.

  1. why do I have to know the type of the arguments ? I mean C macros are just like text replacements , so I think max(a,b) will work just fine with any primitive data type whether it's integer, float, double, etc..

  2. I don't know how will maxint(a,b) work , I mean it's a strange signature of ternary operator , I mean for example in the next lines of code:

    #include <stdio.h>
    
    #define maxint(a,b) \
      ({int _a = (a), _b = (b); _a > _b ? _a : _b; })
    
    int main()
    {
        int x = 5;
        int y = 10;
        int z = maxint(x , y);
        printf("z = %d",z);
    }
    

z value will be 10 , which is a correct answer, and the next lines are the lines generated after preprocessing done by the compiler of the above code:

int main()
{
    int x = 5;
    int y = 10;
    int z = ({int _a = (x), _b = (y); _a > _b ? _a : _b; });
    printf("z = %d",z);
    return 0;
}

my problem is in this line int z = ({int _a = (x), _b = (y); _a > _b ? _a : _b; }); , how does this line evaluate z to be 10 , I mean It's just a block of lines , there is no return value. I only see this line when evaluating at runtime to be

int z = ({int _a = (5), _b = (10); 10; }); as whole ternary operation will be replaced by the value 10,as (5 > 10 ? 5 : 10) will be replaced by 10, which doesn't make sense to me to assign a block of code to a primitive data type (which is z in my case)

  1. they said in the quoted lines above

But this definition computes either a or b twice

talking about this macro max(a,b) , I don't understand how does this macro computes either a or b twice and how did macro maxint(a,b) solve this problem ?

CodePudding user response:

why do I have to know the type of the arguments ?

To evaluate the arguments once, in assignment.

( And you don't, you can use another gcc extension __auto_type or __typeof__. )

I mean it's a strange signature of ternary operator

It's a gcc extension called statement expression https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html .

It's just a block of lines , there is no return value

And from that documentation: The last thing in the compound statement should be an expression followed by a semicolon; the value of this subexpression serves as the value of the entire construct. The value of _a > _b ? _a : _b; is the last, and it serves as the "return value" of ({...}).

how does this macro computes either a or b twice and how did macro maxint(a,b) solve this problem ?

Is explained in macro pitfalls in gcc documentation https://gcc.gnu.org/onlinedocs/cpp/Duplication-of-Side-Effects.html#Duplication-of-Side-Effects .

Many C programs define a macro min, for “minimum”, like this:

#define min(X, Y) ((X) < (Y) ? (X) : (Y))

When you use this macro with an argument containing a side effect, as shown here,

next = min (x y, foo (z));

it expands as follows:

next = ((x y) < (foo (z)) ? (x y) : (foo (z)));

CodePudding user response:

The gcc doc is correct about one thing:

For example, the “maximum” function is commonly defined as a macro in standard C as follows:

 #define max(a,b) ((a) > (b) ? (a) : (b))

But this definition computes either a or b twice, with bad results if the operand has side effects.

Yet the proposed solution is ill advised:

#define maxint(a,b)  ({int _a = (a), _b = (b); _a > _b ? _a : _b; })

This macro illustrates the compiler's statement expression extension and will indeed evaluate the macro arguments exactly once, which is the goal, but the macro will fail on this simple example:

#include <stdio.h>

#define maxint(a,b)  ({int _a = (a), _b = (b); _a > _b ? _a : _b; })

int main() {
    int a = 1, _a = 2;
    printf("max = %d\n", maxint(a, _a));
    printf("max = %d\n", maxint(_a, a));
    return 0;
}

The above code outputs max = 1 then max = 2 (!)

Writing macros is tricky and error prone, making them safe is extremely difficult. The only sound advice for this matter is: use a function, possibly making it static inline to request inline expansion, albeit modern compilers may inline it even without these qualifiers:

int maxint(int a, int b) { return a > b ? a : b; }

Note that it is possible in C99 to use a macro to achieve some level of type genericity:

#define DEFINE_MAX(name, T) T name(T a, T b) { return a > b ? a : b; }
DEFINE_MAX(max_bool, _Bool)
DEFINE_MAX(max_char, char)
DEFINE_MAX(max_schar, signed char)
DEFINE_MAX(max_uchar, unsigned char)
DEFINE_MAX(max_short, short)
DEFINE_MAX(max_ushort, unsigned short)
DEFINE_MAX(max_int, int)
DEFINE_MAX(max_uint, unsigned)
DEFINE_MAX(max_long, long)
DEFINE_MAX(max_ulong, unsigned long)
DEFINE_MAX(max_llong, long long)
DEFINE_MAX(max_ullong, unsigned long long)
DEFINE_MAX(max_float, float)
DEFINE_MAX(max_double, double)
#undef DEFINE_MAX
#undef max
#define max(a,b) _Generic((a) > (b) ? (a) : (b), \
                   _Bool: max_bool(a, b),        \
                    char: max_char(a, b),        \
             signed char: max_schar(a, b),       \
           unsigned char: max_uchar(a, b),       \
                   short: max_short(a, b),       \
          unsigned short: max_ushort(a, b),      \
                     int: max_int(a, b),         \
                unsigned: max_uint(a, b),        \
                    long: max_long(a, b),        \
           unsigned long: max_ulong(a, b),       \
               long long: max_llong(a, b),       \
      unsigned long long: max_ullong(a, b),      \
                   float: max_float(a, b),       \
                  double: max_double(a, b),      \
                 default: "bad type for max"(a, b))
  •  Tags:  
  • c gcc
  • Related