【问题标题】:Limit scope of #define labels限制#define 标签的范围
【发布时间】:2012-09-20 09:39:32
【问题描述】:

限制#define 标签范围并避免无根据的令牌冲突的正确策略是什么?

在以下配置中:

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

注意: Utility _1Utility_2Utility_3 是独立编写的


错误:宏重新定义和令牌冲突
另外,最令人担忧的:编译器没有指出在令牌替换的情况下是什么替换了什么

{Edit} 注意:这是一个通用问题,所以请:不要提出enumconst

即什么时候做:我必须使用#define & _请在下面评论我提出的解决方案.. __

【问题讨论】:

  • 您在某处缺少#define VOID int。顺便说一句,我认为这是一个非常令人困惑的宏。
  • 您问的是 C 还是 C++ - 或两者兼而有之?答案会因情况而异。
  • 您应该给出一个必须使用#define 然后的实际示例(如 SO FAQ 中所述,您应该询问您遇到的实际问题)。否则,它是理论上的,而不是实际意义上的建设性的。
  • 请不要过度编辑问题。您最近的编辑完全改变了含义。对于未来,请尝试提前发布所有要求。
  • @MSalters:如果你也有#define Main main,你只需要#define VOID int

标签: c++ c scope include c-preprocessor


【解决方案1】:

正确的策略是不使用

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

完全没有。如果您需要常量值,请在这种情况下使用 const char 代替,包装在命名空间中。

【讨论】:

  • 请注意,此答案仅适用于 C++。在 C 中,const char 不能用作编译时常量,因此可能不适合替换字符文字。
  • 这要求我必须为每个以前使用的# define 标签确定type
【解决方案2】:

#define 宏有两种类型:

  1. 仅在单个文件中需要的。我们就叫他们Private #defines
    例如。 PI 3.14 在这种情况下:

    按照标准做法:正确的策略是放置#define 标签 - 仅在实现中,即。 c,文件而不是标题 h 文件。

  2. 多个文件需要的另一个:我们称之为Shared #defines
    例如。 EXIT_CODE 0x0BAD 在这种情况下:

    只在 h 文件头文件中放置这些常见的#define 标签。

另外尝试使用False NameSpaces 或类似约定命名标签唯一,例如在标签前加上MACRO_,例如:#define MACRO_PI 3.14,以便碰撞概率降低

【讨论】:

  • 正如我在其他提议的解决方案中提到的那样,正如@tenfour 所指出的那样
  • Fake|@MikeSeymour JohnBode 提到的伪命名空间
【解决方案3】:

#defines 没有对应于 C++ 代码的范围;你不能限制它。它们是天真的文本替换宏。想象一下问“当我用 grep 替换文本时如何限制范围?”

您应该尽可能避免使用它们,而倾向于使用真正的 C++ 类型。

正确使用宏几乎可以通过命名约定自行解决这个问题。如果宏被命名为对象,它应该一个对象(而不是宏)。问题解决了。如果宏被命名为函数(例如动词),它应该一个函数。

这适用于文字值、变量、表达式、语句……这些都不应该是宏。这些是可以咬你的地方。

在其他情况下,当您使用某种语法助手时,您的宏名称几乎肯定不符合其他任何名称的命名约定。所以问题几乎没有了。但最重要的是,当命名冲突时,需要是宏的宏会导致编译错误。

【讨论】:

  • 当然它们有一个作用域,但是它是一个适用于预处理器的作用域,而不是编译器。
  • 它们有一个范围,从定义点到对应的#undef 或翻译单元的结尾。
  • 预处理器指令确实有范围(与变量范围的含义不同,但它们确实有)...
  • 他们有范围,但在回答这个问题时没有任何意义。宏的作用域与代码中的任何作用域都不对应。
  • @UjjwalSingh 我认为对它的投票正是如此...... :)
【解决方案4】:

一些选项:

  1. 对宏和普通标识符使用不同的大写约定。

    const UINT 零 = 0;
  2. 通过在宏前面添加模块名称来伪造命名空间:

    #define UTIL_ZERO '0' #define UTIL_ONE '1'
  3. 在可用的情况下 (C++),完全抛弃宏并使用真正的命名空间:

    命名空间实用程序{ 常量字符零 = '0'; 常量字符一 = '1'; };

【讨论】:

    【解决方案5】:

    限制#define 范围和避免无根据的令牌冲突的正确策略是什么。

    1. 避免使用宏,除非它们确实必要。在 C++ 中,通常可以使用常量变量和内联函数。它们的优点是它们是有类型的,并且可以在命名空间、类或代码块内限定范围。在 C 语言中,更经常需要宏,但在引入宏之前请仔细考虑替代方案。

    2. 使用命名约定,明确哪些符号是宏,哪些是语言级标识符。保留ALL_CAPITALS 名称以供宏专用是很常见的;如果你这样做,那么宏只能与其他宏发生冲突。这也将注意力吸引到代码中更有可能存在错误的部分。

    3. 在每个宏名称上包含一个“伪命名空间”前缀,这样来自不同库/模块/其他内容的宏以及具有不同用途的宏就不太可能发生冲突。所以,如果你正在设计一个狡猾的库,想要为数字 0 定义一个字符常量,可以将其命名为 DODGY_DIGIT_ZERO。只是 ZERO 可能意味着很多事情,并且很可能与另一个狡猾的库定义的零值常量发生冲突。

    【讨论】:

      【解决方案6】:

      限制#define 范围和避免无根据的令牌冲突的正确策略是什么。

      一些简单的规则:

      1. 尽量减少预处理器令牌的使用。
        一些组织甚至沿着这条路走,并将预处理器符号限制为仅限#include 守卫。我没有走这么远,但是将预处理器符号保持在最低限度是个好主意。
        • 使用枚举而不是命名整数常量。
        • 使用const static 变量而不是命名的浮点常量。
        • 使用内联函数而不是宏函数。
        • 使用 typedef 而不是#defined 类型名称。
      2. 采用排除冲突的命名约定。
        例如,
        • 预处理器符号的名称只能由大写字母和下划线组成。
        • 任何其他类型的符号都不能具有仅由大写字母和下划线组成的名称。

      const UINT ZERO = 0; // Programmer not aware of what's inside Utility.h

      首先,如果程序员不了解 Utility.h 中的内容,为什么程序员要使用 #include 语句?显然UINT 来自某个地方……

      其次,程序员通过命名变量ZERO来自找麻烦。为预处理器符号保留所有大写名称。如果你遵守规则,你就不必知道 Utility.h 里面有什么。只需假设 Utility.h 遵循规则。将该变量命名为zero

      【讨论】:

      • 如果它们是多个标题 - 因此是多个 #define 标签。
      • @UjjwalSingh - 你也需要为#include 守卫制定一个好的命名约定。您想要发生的最后一件事是需要两个标题,它们都使用相同名称的 #include 保护在另一个文件中使用(您也需要)。命名约定很重要。
      【解决方案7】:

      我认为你真的只需要知道你所包含的内容。这就像试图包含 windows.h,然后声明一个名为 WM_KEYDOWN 的变量。如果你有冲突,你应该重命名你的变量,或者(有点像黑客),#undef it。

      【讨论】:

        【解决方案8】:

        C 是一种结构化的编程语言。它有其局限性。这就是面向对象系统获得第一名的原因。在 C 中似乎没有其他方法,然后了解你的头文件的变量以 _VARIABLE 表示法开头,这样它被覆盖的机会就更少了。

        in header file 
        _ZERO 0
        
        in regular file
        
        ZERO 0
        

        【讨论】:

        • 以下划线后跟大写字母开头的名称保留供实现使用:您不得在自己的代码中使用此类名称。
        【解决方案9】:
        1. 我认为正确的策略是放置#define 标签 - 仅在实现中,即。 c,文件
        2. 此外,所有#define 都可以单独放在另一个文件中——比如:Utility_2_Def.h
          (很像微软的WinError.h:Win32 api 函数的错误代码定义

          开销:

          1. 一个额外的文件
          2. 额外的#include 语句

          收获:

          1. 抽象:ZERO 是:0'0'"Zero" 以了解您在哪里使用它
          2. 更改整个模块的所有静态参数的标准位置

        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()
        {
            ...
        }
        

        【讨论】:

        • 使用较长的名称也有助于避免冲突。
        • 无论如何,您的第一个建议是标准做法,而您的第二个建议对名称冲突没有任何帮助。
        • Utility.h 的用户不需要的任何东西首先都不应该存在,无论它是否是宏。如果它们仅被一个源文件需要,那么我认为将它们粘贴到另一个包含文件中没有任何意义;当然不会给它起一个奇怪的、非常规的名字。
        • 所有约定都是在某一点创建的。更清晰的代码是目标。
        • 这不是“更清洁的代码”。如果必须,请调用该文件实用程序_defs.h。不要使用只有您知道其含义的非标准名称。更好的是,不要在标题中直接不需要的东西放在标题中。如果这些#defines 不直接在 Utility.h 中使用,则它们不属于 Utility.h。更好的是,不要这样做。避免预处理器符号之间冲突的最简单方法是不定义任何预处理器符号。
        猜你喜欢
        • 2022-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多