#define 是否在行业标准中被禁止?

Is #define banned in industry standards?

我是计算机科学专业的一年级学生,我的教授说 #define 在行业标准中与 #if#ifdef#else 和其他一些预处理器指令。由于意外行为,他使用了 "banned" 这个词。

这准确吗?如果是,为什么?

事实上,是否有任何标准禁止使用这些指令?

第一次听说

否; #define等被广泛使用。有时使用太广泛,但绝对使用。 C 标准在某些地方强制使用宏——您无法轻易避免这些地方。例如,§7.5 错误 <errno.h> 说:

The macros are

     EDOM
     EILSEQ
     ERANGE

which expand to integer constant expressions with type int, distinct positive values, and which are suitable for use in #if preprocessing directives; …

鉴于此,显然并非所有行业标准都禁止使用 C 预处理器宏指令。然而,有来自不同组织的 'best practices' 或 'coding guidelines' 标准对 C 预处理器的使用进行了限制,尽管 none 完全禁止它的使用——它是 C 的固有部分,不能完全避免。通常,这些标准适用于在安全关键区域工作的人员。

一个标准,您可以查看 MISRA C (2012) 标准;这往往会禁止一些事情,但即使这样也承认有时需要 #define 等(第 8.20 节,规则 20.1 到 20.14 涵盖 C 预处理器)。

NASA GSFC(戈达德 Space 飞行中心)C Coding Standards 简单地说:

Macros should be used only when necessary. Overuse of macros can make code harder to read and maintain because the code no longer reads or behaves like standard C.

介绍性陈述之后的讨论说明了函数宏的可接受用法。

CERT C Coding Standard 有许多关于预处理器使用的指南,暗示您应该尽量减少预处理器的使用,但并不禁止它的使用。

Stroustrup 想让预处理器在 C++ 中无关紧要,但这还没有发生。至于Peter , some C++ standards, such as the JSF AV C++ Coding Standards (Joint Strike Fighter, Air Vehicle) from circa 2005, dictate minimal use of the C preprocessor. Essentially, the JSF AV C++ rules restrict it to #include and the #ifndef XYZ_H / #define XYZ_H / … / #endif dance that prevents multiple inclusions of a single header. C++ has some options that are not available in C — notably, better support for typed constants that can then be used in places where C does not allow them to be used. See also static const vs #define vs enum那里有问题的讨论。

尽量减少预处理器的使用是个好主意——它经常被滥用,至少与它被使用的次数一样多(请参阅 Boost preprocessor 'library' 以了解您可以达到何种程度的说明使用 C 预处理器)。

总结

预处理器是C的一个组成部分,#define#if等不能完全避免。问题中教授的陈述通常无效:#define 在行业标准中与 #if#ifdef#else 和其他一些宏 充其量只是夸大其词,但可以通过明确参考特定行业标准来支持(但所讨论的标准不包括 ISO/IEC 9899:2011 — C标准)。


请注意 David Hammen has about one specific C coding standard — the JPL C Coding Standard — 这禁止了许多人在 C 中使用的很多东西,包括限制 C 预处理器的使用(并限制动态内存分配的使用,以及禁止递归 — 阅读看看原因,并确定这些原因是否与您相关)。

不,你的教授错了,或者你听错了什么。

#define是预处理器宏,条件编译和一些约定都需要预处理器宏,不是简单的C语言内置的。例如,在最近的 C 标准(即 C99)中,添加了对布尔值的支持。但是语言 "native" 不支持它,预处理器 #define 不支持它。参见 this reference to stdbool.h

宏在 GNU land C 中被大量使用,如果没有条件预处理器命令,就无法正确处理同一源文件的多个包含,因此对我来说它们似乎是必不可少的语言功能。

也许您的 class 实际上是在 C++ 上,尽管很多人没有这样做,但应该与 C 区分开来,因为它是一种不同的语言,我不能在那里谈论宏。或者也许教授的意思是他在 class 中禁止使用它们。无论如何,我确信 SO 社区会对他所谈论的标准感兴趣,因为我很确定所有 C 标准都支持使用宏。

这完全是错误的,宏在 C 中被大量使用。初学者经常使用它们很糟糕,但这并不是禁止它们进入工业界的理由。一个典型的错误用法是 #define succesor(n) n + 1。如果您期望 2 * successor(9) 给出 20,那么您就错了,因为该表达式将被翻译为 2 * 9 + 1,即 19 而不是 20。使用括号获得预期结果。

不,不禁止使用宏。

事实上,在头文件中使用 #include 保护是一种常见的技术,通常是强制性的,并且受到公认的编码准则的鼓励。有些人声称 #pragma once 是替代方案,但问题是 #pragma once - 根据定义,因为 pragmas 是标准为编译器特定扩展提供的钩子 - 是非标准的,甚至如果它被许多编译器支持。

也就是说,由于宏引入的问题(不考虑作用域等),有许多行业指南和鼓励的做法积极阻止 #include 守卫以外的所有宏的使用。在 C++ 开发中,宏的使用比在 C 开发中更令人不快。

阻止使用某些东西与禁止它不同,因为仍然可以合法地使用它 - 例如,通过记录理由。

没有。它没有被禁止。说实话,没有它就不可能编写重要的多平台代码。

一些编码标准可能不鼓励甚至禁止使用 #define 创建带有参数的 类似函数的宏 ,例如

#define SQR(x) ((x)*(x))

因为 a) 这样的宏不是类型安全的,并且 b) 有人会 不可避免地 SQR(x++),这是糟糕的 juju。

一些标准可能不鼓励或禁止使用 #ifdefs 进行条件编译。例如,以下代码使用条件编译正确打印出 size_t 值。对于 C99 and later, you use the %zu conversion specifier; for C89 及更早版本,您使用 %lu 并将值转换为 unsigned long:

#if __STDC_VERSION__ >= 199901L
#  define SIZE_T_CAST
#  define SIZE_T_FMT "%zu"
#else
#  define SIZE_T_CAST (unsigned long)
#  define SIZE_T_FMT "%lu"
#endif
...
printf( "sizeof foo = " SIZE_T_FMT "\n", SIZE_T_CAST sizeof foo );

一些标准可能要求您实现模块两次,一次针对 C89 及更早版本,一次针对 C99 及更高版本:

/* C89 version */
printf( "sizeof foo = %lu\n", (unsigned long) sizeof foo );

/* C99 version */
printf( "sizeof foo = %zu\n", sizeof foo );

然后让 Make (or Ant 或您正在使用的任何构建工具) 处理编译和链接正确的版本。对于这个例子,这将是荒谬的矫枉过正,但我​​已经看到代码是一个无法追踪的 #ifdefs 的老鼠巢, 应该 已经将条件代码分解到单独的文件中.

但是,据我所知,没有任何公司或行业组织完全禁止使用预处理器语句。

宏不能"banned"。这种说法是无稽之谈。从字面上看。

例如7.5 错误<errno.h>C Standard需要使用宏:

1 The header <errno.h> defines several macros, all relating to the reporting of error conditions.

2 The macros are

EDOM
EILSEQ
ERANGE

which expand to integer constant expressions with type int, distinct positive values, and which are suitable for use in #if preprocessing directives; and

errno

which expands to a modifiable lvalue that has type int and thread local storage duration, the value of which is set to a positive error number by several library functions. If a macro definition is suppressed in order to access an actual object, or a program defines an identifier with the name errno, the behavior is undefined.

因此,不仅宏是 C 的 必需的 部分,在某些情况下,不使用它们会导致未定义的行为。

不,#define 没有被禁止。然而,滥用 #define 可能会令人不悦。

例如,您可以使用

#define DEBUG

在您的代码中,以便稍后您可以使用 #ifdef DEBUG 指定部分代码进行条件编译,仅用于调试目的。我认为任何头脑正常的人都不会想要禁止这样的事情。使用 #define 定义的宏也广泛用于可移植程序,enable/disable 编译 platform-specific 代码。

但是,如果您使用的是

#define PI 3.141592653589793

你的老师可能会正确地指出,将 PI 声明为具有适当类型的常量会更好,例如,

const double PI = 3.141592653589793;

因为它允许编译器在使用 PI 时进行类型检查。

同样(上面John Bode提到的),使用function-like宏可能是不被认可的,尤其是在可以使用模板的C++中。所以而不是

#define SQ(X) ((X)*(X))

考虑使用

double SQ(double X) { return X * X; }

或者,在 C++ 中,更好的是,

template <typename T>T SQ(T X) { return X * X; }

再一次,这个想法是通过使用语言的工具而不是预处理器,您允许编译器进行类型检查,并且(可能)生成更好的代码。

一旦你有足够的编码经验,你就会知道什么时候使用 #define 是合适的。在那之前,我认为你的老师强加某些规则和编码标准是个好主意,但最好他们自己应该知道并能够解释原因。全面禁止 #define 是荒谬的。

查看任何头文件,您都会看到类似这样的内容:

#ifndef _FILE_NAME_H
#define _FILE_NAME_H
//Exported functions, strucs, define, ect. go here
#endif /*_FILE_NAME_H */

这些定义不仅是允许的,而且在本质上是至关重要的,因为每次在文件中引用头文件时,它都会被单独包含。这意味着如果没有定义,您将多次重新定义守卫之间的所有内容,最好的情况无法编译,最坏的情况会让您稍后摸不着头脑,为什么您的代码无法按您希望的方式工作。

编译器也将使用 define as seen here with gcc 让你测试诸如编译器版本之类的东西,这非常有用。我目前正在开发一个需要使用 avr-gcc 编译的项目,但我们有一个测试环境,我们也 运行 我们的代码。为了防止 avr 特定文件和寄存器使我们的测试代码不被 运行ning 我们做这样的事情:

#ifdef __AVR__
//avr specific code here
#endif

在生产代码中使用它,补充测试代码可以在不使用 avr-gcc 的情况下编译,上面的代码仅使用 avr-gcc.

编译

如果你刚刚提到 #define,我会以为他可能是在暗示它用于枚举,最好使用 enum 以避免愚蠢的错误,例如分配相同的数字价值两倍。

请注意,即使在这种情况下,有时使用 #defines 比使用枚举更好,例如,如果您依赖于与其他系统交换的数值并且实际值必须保持不变,即使您add/delete 个常量(为了兼容性)。

但是,添加 #if#ifdef 等也不应该使用,这很奇怪。当然,它们可能不应该被滥用,但在现实生活中有许多使用它们的理由。

他的意思可能是(在适当的情况下),你不应该在源代码中硬编码行为(这需要 re-compilation 来获得不同的行为),而是使用某种形式的 run-time 配置。

这是我能想到的唯一有意义的解释。

与迄今为止的所有答案相反,在 high-reliability 计算中通常禁止使用预处理器指令。有两个例外,此类组织强制使用它们。这些是 #include 指令,以及在头文件中使用包含保护。这些类型的禁令在 C++ 中比在 C 中更有可能。

这只是一个例子:16.1.1 Use the preprocessor only for implementing include guards, and including header files with include guards.

另一个例子,这次是 C 而不是 C++:JPL Institutional Coding Standard for the C Programming Language 。这个 C 编码标准并没有完全禁止使用预处理器,但已经接近了。具体来说,它说

Rule 20 (preprocessor use) Use of the C preprocessor shall be limited to file inclusion and simple macros. [Power of Ten Rule 8].


我既不宽恕也不谴责这些标准。但是说它们不存在是荒谬的。

如果您希望您的 C 代码与 C++ 代码互操作,您将需要在 extern "C" 命名空间中声明您的外部可见符号,例如函数声明。这通常使用条件编译来完成:

#ifdef __cplusplus
extern "C" {
#endif

/* C header file body */

#ifdef __cplusplus
}
#endif