Limit scope of #define labels

2019-01-26 14:01发布

What is the correct strategy to limit the scope of #define labels and avoid unwarranted token collision?

In the following configuration:

Main.c

# include "Utility_1.h"
# include "Utility_2.h"
# include "Utility_3.h"
VOID Main() { ... }

Utility_1.h

# define ZERO "Zero"
# define ONE  "One"
BOOL Utility_1(); // Uses- ZERO:"Zero" & ONE:"One"

Utility_2.h

# define ZERO '0'
# define ONE  '1'
BOOL Utility_2(); // Uses- ZERO:'0' & ONE:'1'

Utility_3.h

const UINT ZERO = 0;
const UINT ONE = 1;
BOOL Utility_3(); // Uses- ZERO:0 & ONE:1

Note: Utility _1, Utility_2 and Utility_3 have been written independently


Error: Macro Redefinition and Token Collision
Also, Most Worrying: Compiler does not indicate what replaced what incase of token replacement

{Edit} Note: This is meant to be a generic question so please: do not propose enum or const

i.e. What to do when: I MUST USE #define & _Please comment on my proposed solution below.. __

9条回答
老娘就宠你
2楼-- · 2019-01-26 14:31

#defines don't have scope that corresponds to C++ code; you cannot limit it. They are naive textual replacement macros. Imagine asking "how do I limit the scope when I replace text with grep?"

You should avoid them whenever you possibly can, and favor instead using real C++ typing.

Proper use of macros will relieve this problem almost by itself via naming convention. If the macro is named like an object, it should be an object (and not a macro). Problem solved. If the macro is named like a function (for example a verb), it should be a function.

That applies to literal values, variables, expressions, statements... these should all not be macros. And these are the places that can bite you.

In other cases when you're using like some kind syntax helper, your macro name will almost certainly not fit the naming convention of anything else. So the problem is almost gone. But most importantly, macros that NEED to be macros are going to cause compile errors when the naming clashes.

查看更多
在下西门庆
3楼-- · 2019-01-26 14:32

Some options:

  1. Use different capitalization conventions for macros vs. ordinary identifiers.

    const UINT Zero = 0;

  2. Fake a namespace by prepending a module name to the macros:

     #define UTIL_ZERO '0'
     #define UTIL_ONE  '1'

  3. Where available (C++), ditch macros altogether and use a real namespace:

     namespace util {
         const char ZERO = '0';
         const char ONE  = '1';
     };

查看更多
冷血范
4楼-- · 2019-01-26 14:32

There are two types of #define Macros:

  1. One which are need only in a single file. Let's call them Private #defines
    eg. PI 3.14 In this case:

    As per the standard practice: the correct strategy is to place #define labels - in only the implementation, ie. c, files and not the header h file.

  2. Another that are needed by multiple files: Let's call these Shared #defines
    eg. EXIT_CODE 0x0BAD In this case:

    Place only such common #define labels in header h file.

Additionally try to name labels uniquely with False NameSpaces or similar conventions like prefixing the label with MACRO_ eg: #define MACRO_PI 3.14 so that the probability of collision reduces

查看更多
Evening l夕情丶
5楼-- · 2019-01-26 14:33

I think you really just have to know what it is you're including. That's like trying to include windows.h and then declare a variable named WM_KEYDOWN. If you have collisions, you should either rename your variable, or (somewhat of a hack), #undef it.

查看更多
乱世女痞
6楼-- · 2019-01-26 14:44

The correct strategy would be to not use

#define ZERO '0'
#define ONE  '1'

at all. If you need constant values, use, in this case, a const char instead, wrapped in a namespace.

查看更多
Bombasti
7楼-- · 2019-01-26 14:44
  1. I think the correct strategy would be to place #define labels - in only the implementation, ie. c, files
  2. Further all #define could be put separately in yet another file- say: Utility_2_Def.h
    (Quite like Microsoft's WinError.h:Error code definitions for the Win32 api functions)

    Overheads:

    1. an extra file
    2. an extra #include statement

    Gains:

    1. Abstraction: ZERO is: 0, '0' or "Zero" as to where you use it
    2. One standard place to change all static parameters of the whole module

Utility_2.h

BOOL Utility_2();

Utility_2_Def.h

# define ZERO '0'
# define ONE  '1'

Utility_2.c

# include "Utility_2.h"
# include "Utility_2_Def.h"

BOOL Utility_2()
{
    ...
}
查看更多
登录 后发表回答