是否有某些关键字不应该由我“#defined”?

Are there certain keywords that should not be "#defined" by me?

我正在 C/C++ 中为我的应用程序开发一个平台层。

我愿意

#define WINDOWS // on Windows machines

#define ANDROID // on Android phones

由于与其他库的冲突,定义像 "WINDOWS" 或 "ANDROID" 这样非常常见的关键字是个坏主意吗?在这些关键字前加上一些东西是否有意义:

#define MYLIB_WINDOWS // Not used by any other 3rdparty libraries

#define MYLIB_ANDROID

除了

,您可以#define任何事情
  1. 任何以前导下划线和大写字母开头的内容。

  2. 任何包含双下划线的内容。

  3. 语言关键字。

  4. std.

编译器编写者的工作之一是确保您可以免费使用其他任何东西。

请注意,POSIX 对此有进一步的限制,包括不允许任何带有 _t 后缀的内容。

但请注意,尽管第 3 方图书馆也应遵循这些规则,因此您有责任确保不与它们发生冲突。 (例如 windows.h 中定义的东西。)尽可能避免使用宏并使用 namespaces &c。可以采取一些措施来防止冲突。

首先,您可能要考虑使用一些通常在这些平台上预定义的现有宏,或者已经提供一组不错的宏的库,例如 BOOST_OS_ANDROID BOOST_OS_WINDOWS from boost.predef,而不是定义您自己的宏。

如果您打算编写自己的条件编译宏,那么您绝对应该确保您 select 的名称相当独特,并且没有在其他地方用于不相关的目的,因为名称冲突不是什么你要处理。

I would like to

谨防在您的库的 header 文件中公开带有 'common' 名称的宏。

还要注意私有代码中通常命名的宏 - 特别是如果它 #includes(直接或间接)其他库的 headers.

您不能指望所有库维护者都像您一样well-behaved。

and would it make sense to prefix these keywords with something:

总的来说,是的。

BOOST 库套件就是一个很好的例子。 BOOST 非常注意确保其 header 文件导出的所有宏都具有前缀 BOOST_。前缀与库的命名空间名称 boost::.

相匹配并非巧合

总而言之,如果您的库是在命名空间中实现的(它应该是,否则您会污染全局命名空间),请务必使用与之匹配的宏前缀。

示例:

namespace mylib { namespace innerthing { }}

#define MYLIB_ON 1
#define MYLIB_OFF 0
#define MYLIB_SETTING MYLIB_ON
#define MYLIB_INNERTHING_SETTING MYLIB_OFF

TL;DR 版本

具有通用名称的宏迟早会导致问题,因此简短的回答是 "NEVER use a common name for your macro"。

建议

一种非常有用的方法是在您非常简单的名称前加上您的项目名称,这在 include guards 中是很常见的做法。

正确解释(举例)

举个例子,过去我正在研究程序,特别是 header P(roject),并且必须包含一个文件,该文件通过一个不太深的包含链包含一个 header 在那里你可以找到一个名为 "Log" 的成员函数(将此 header C 称为 class)。

碰巧的是,通过一个非常不同的包含链,我还导入了一个以相同方式命名的宏的定义:"Log"。将此 header M 称为宏。

结果是,当我尝试编译时,根据项目中包含的顺序,我要么没问题,要么最终出现错误。

编译器执行的第一步是对当前正在编译的源文件调用预处理器。每次如果找到#include,它都会用您要包含的整个 header 的复制粘贴替换该行。在预处理结束时,您有一个大文件,其中包含源代码中的所有代码以及所有递归包含的 headers.

如果此最终文件中的代码按以下顺序(文件从上到下):

C

P

那么一切都很好,因为 M 中的宏只影响它后面的代码。 如果顺序是:

C

P

预处理器会将函数名称与宏的内容相结合。

假设M中的代码是:

#define Log printf("Oops")

C 中的代码是:

class L {
    void Log(const char* message) { printf("%s\n", message); }
};

在预处理器阶段之后,每个日志行(在宏声明之后)都将被替换为宏的内容。也就是说,C 中的代码现在看起来像这样:

class L {
    void printf("Oops")(const char* message) { printf("%s\n", message); }
};

这段代码显然无法编译。

不过,主要问题是编译错误与该行无法编译的原因有关,这不是真正的问题:宏替换才是。

请注意,根据宏和被替换的代码,您的代码可能最终会编译但执行与您编写的代码不同的事情(例如,考虑替换一个常量值,因为您的常量在与宏相同的方式)。

有用的注释

在调试最糟糕的宏问题时,我的 gcc 版本 6.something 完全没用,因为它只关注 post-preprocessor 代码。像往常一样,Clang 版本 3.something 是一个可取之处:它立即告诉我有一个 X11 宏(从我编码的地方开始有 3 个库层!)具有一个相对合理的名称 (long-ish 2字名)。不幸的是,你的库的合理名称可能对库用户代码也是合理的,这就是为什么即使是罕见的名称也不够。

每个宏都需要 _ 前缀。