3

My system uses libc6 2.29. In /usr/include/assert.h I can find the definition of assert() macro:

/* The first occurrence of EXPR is not evaluated due to the sizeof,
   but will trigger any pedantic warnings masked by the __extension__
   for the second occurrence.  The ternary operator is required to
   support function pointers and bit fields in this context, and to
   suppress the evaluation of variable length arrays.  */
#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (expr)                             \
        ; /* empty */                           \
      else                              \
        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);   \
    }))

I wonder why to use the comma operator, and what is meant by 'The first occurrence of EXPR is not evaluated due to the sizeof'.

What problem there would be using the following definition:

#  define assert(expr)                      \
  ({                                        \
      if (expr)                             \
           ; /* empty */                            \
      else                              \
           __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);    \
    })

Edit:

what value does the operator ({ }) get if expr is true?

Is it possible to rewrite the definition of assert() as follows?

#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (!expr)                                \
          __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \
    }))

Where are the problems with this last definition?

dbc
  • 104,963
  • 20
  • 228
  • 340
mastupristi
  • 1,240
  • 1
  • 13
  • 29

2 Answers2

4
  1. ({ is not standard C and will trigger warnings or errors in standard C compilation modes.
  2. So they are using __extension__, which will disable any such diagnostics.
  3. However __extension__ will also mask non-standard constructs in expr, which you do want diagnosed.
  4. Which is why they need expr repeated twice, once inside __extension__ and once outside.
  5. However expr only needs to be evaluated once.
  6. So they inhibit another evaluation by placing the other occurrence of expr inside sizeof.
  7. Just sizeof(expr) is not enough though, because it won't work for things like function names.
  8. So sizeof((expr) ? 1 : 0) is used instead, which doesn't have this problem.
  9. So the two parts of the generated expression are (a) sizeof((expr) ? 1 : 0) and (b) the __extension__(...) part .
  10. The first part is only needed to produce diagnostics if something is wrong with the expr.
  11. The second part does the actual assertion.
  12. Finally, the two parts are connected with the comma operator.
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
4

I'm not 100% certain on this but I'll give it a go.

First, let's review a few things being used here.

  • The comma operator discards the first n-1 expression results and returns the nth result. It's often used as a sequence point, as it's guaranteed that the expressions will be evaluated in order.

  • The use of __extension__ here, which is a GNU LibC macro, is used to mask any warnings about GNU-specific extensions in headers under compilation environments that specify pedantic warnings, either via -ansi or -pedantic, etc. Usually under such compilers, using a compiler-specific extension would throw a warning (or an error if you're running under -Werror, which is quite common), but since in cases where GNU libraries and compilers are being used, libc allows itself to use some extensions where it can safely do so.

Now, since the actual assertion logic might use a GNU extension (as is indicated by the use of __extension__, any real warnings that the expression itself might have raised given its semantics (that is, the expression passed to assert(expr)) would be masked since that expression would be semantically located within the __extension__ block and thus masked.

Therefore, there needed to be a way to allow the compiler the chance to show those warnings, but without evaluating the actual expression (since the expression could have side effects and a double-evaluation could cause undesired behaviour).

You can do this by using the sizeof operator, which takes an expression, looks at its type, and finds the number of chars it takes up - without actually evaluating the expression.

For example, if we have a function int blow_up_the_world(), then the expression sizeof(blow_up_the_world()) would find the size of the result of the expression (in this case, int) without actually evaluating the expression. Using sizeof() in this case meant the world would, in fact, not be blown up.

However, if the expr passed to assert(expr) contained code that would otherwise trigger a compiler warning (e.g. using an extension under -pedantic or -ansi modes), the compiler would still show those warnings even though the code was inside the sizeof() - warnings that would otherwise be masked inside the __extension__ block.

Next, we see that instead of passing expr directly to sizeof, they instead use a ternary. That's because the type of a ternary is whatever type both resulting expressions have - in this case is int or something equivalent. This is because passing certain things to sizeof will result in a runtime value - namely in the case of variable length arrays - which could have undesired effects, or might produce an error, such as when passing sizeof a function name.

Lastly, they wanted all of that, but before the actual evaluation and wanted to keep assert() as an expression, so instead of using a do{}while() block or something similar, which would ultimately result in assert() being a statement, they instead used the comma operator to discard the result of the first sizeof() trick.

Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
  • if I understand correctly the operator ({ }) is necessary due to the use of the comma operator, right? It is needed that the last element in the list is an expression that can evaluates to a value. But what is the value evaluated when the expr is true? I have edited my question for more details – mastupristi May 28 '19 at 18:45
  • There's nothing special about `({})` other than `__extension__` is a function-like identifier (like a macro function, i.e. `#define __extension__(...) ...`) that expects a block. I'm not sure what you mean when the expression is true - the return value is whatever `__extension__` returns. I'm not sure that the return value of `assert()` is specified, or even usable. – Qix - MONICA WAS MISTREATED May 29 '19 at 13:47