Rationale why is macro redefinition without undef leads to ill-formed program

146 views Asked by At

According the standard macro redefinition is prohibited without using #undef before:

But the following redefinitions are invalid:

#define OBJ_LIKE    (0)         // different token sequence
#define OBJ_LIKE    (1 - 1)     // different whitespace

At the same time it's allowed to #undef for macros that were not defined at all before:

It is ignored if the specified identifier is not currently defined as a macro name.

I'm interested what is the rationale for this, i.e. forbidding redefinition without #undef in the middle? Some compiler optimization purposes or what?

3

There are 3 answers

0
Artyer On BEST ANSWER

Because it was inherited from C.

ISO/IEC 9899:1990 §6.8.3 (AKA C90) contains similar wording:

An identifier currently defined as a macro without use of lparen (an object-like macro) may be redefined by another #define preprocessing directive provided that the second definition is an object-like macro definition and the two replacement lists are identical.

An identifier currently defined as a macro using lparen (a function-like macro) may be redefined by another #define preprocessing directive provided that the second definition is a function-like macro definition that has the same number and spelling of parameters, and the two replacement lists are identical.

And looking at N802 produced by WG14 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n802.pdf) "Rationale for International Standard - Programming Language - C" §6.8.3, the reason is stated as:

The Committee desired to disallow "pernicious redefinitions'' such as
(in header1.h)

#define NBUFS 10

(in header.h)

#define NBUFS 12

which are clearly invitations to serious bugs in a program. There remained, however, the question of "benign redefinitions,'' such as (in header1.h)

#define NULL_DEV 0

(in header.h)

#define NULL_DEV 0

The Committee concluded that safe programming practice is better served by allowing benign redefinition where the definitions are the same. This allows independent headers to specify their understanding of the proper value for a symbol of interest to each, with diagnostics generated only if the definitions differ.

So "benign" redefinitions are encouraged, where the alternative would be something like:

#ifdef SPEED_OF_LIGHT
#if SPEED_OF_LIGHT != 299792458
#error Some other header defines SOME_UNIVERSAL_CONSTANT wrong
#endif
#else
#define SPEED_OF_LIGHT 299792458
#endif

Simply #define SPEED_OF_LIGHT 299792458 will generate a warning if another header defines it differently.


And a similar thing for #undef. The rationale given for §6.8.3.5:

It is explicitly permitted to #undef a macro that has no current definition. This capability is exploited in conjunction with the standard library (see §7.1.8).

Where §7.1.8 of C99 / §7.1.7 of C90 says:

The use of #undef to remove any macro definition will also ensure than an actual function is referred to.

For example, a standard library header might have double abs(double); as well as #define abs(x) _BUILTIN_abs(x), and #undef abs was legal and allowed you to make sure abs referred to a bona fide function.

Of course, this doesn't apply to C++, where standard library functions cannot be macros. But the other rationale ("It is explicitly permitted to #undef a macro that has no current definition") still applies, i.e., "the committee says so".

2
Jan Schultke On

It's disallowed because silently redefining a macro to mean something else is a likely mistake. Consider the following example:

// in assertions.hpp
#define assert(expr, msg) if (!expr) std::cout << msg << '\n';
#include "assertions.hpp"
// ...
#include <cassert> // could be included directly or indirectly
// ...

int main() {
    // expected to fail and print "huh ?!" to std::cout
    assert(false, "huh ?!");
    // may expand to:
    // ((false, "huh ?!") ? void() : __fail("huh ?!"))
}

This assertion now succeeds because assert in <cassert> redefined what an assertion is. In general, re-defining things may have a number of consequences:

  • simply producing compiler errors
  • silently redefining global constants to have different values
  • silently redefining function-like macros to have different behavior

Keep in mind that the order of #includes matters and that every macro can spill into any other header this way. Bugs related to macro re-definitions may be very difficult to track down.

If you want to repurpose a macro name, you can just #undef it first.

0
Jarod42 On

We cannot check MACRO definition, we only can check if MACRO is defined or not.

So, if you want to check that #undef is only done on defined MACRO, you might check it yourself:

#ifdef MY_MACRO
# undef MY_MACRO
#else
# error "Expected previous definition"
#end

For redefining MACRO, it is not possible

#define MY_MACRO some_def

// #ifdef MY_MACRO // only check it is defined not its definition
                   // so cannot differentiate for below two cases

#define MY_MACRO some_def    // OK (same def)
#define MY_MACRO another_def // Invalid