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

时间:2015-12-28 15:42:30

标签: c c-preprocessor preprocessor-directive

我是第一年的计算机科学专业的学生,​​我的教授说#define在行业标准中被禁止,#if#ifdef#else以及其他一些预处理程序指令。由于出乎意料的行为,他使用了“禁止”一词。

这准确吗?如果是这样的话?

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

13 个答案:

答案 0 :(得分:140)

首先我听说过它。

没有; #define等被广泛使用。有时使用过于广泛,但绝对使用。有些地方C标准要求使用宏 - 你无法轻易避免这些。例如,§7.5错误<errno.h> 表示:

  

宏是

     EDOM
     EILSEQ
     ERANGE
     

扩展为类型为int的整数常量表达式,不同的正值,适用于#if预处理指令; ...

鉴于此,很明显并非所有行业标准都禁止使用C预处理器宏指令。但是,各种组织都有“最佳实践”或“编码指南”标准规定了对C预处理器使用的限制,尽管没有完全禁止使用它 - 它是C的固有部分,无法完全避免。通常,这些标准适用于在安全关键领域工作的人员。

您可以检查MISRA C(2012)标准的一个标准;这往往会取代某些东西,但即使这样也承认有时需要#define等(第8.20节,规则20.1到20.14涵盖了C预处理器)。

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

  

只应在必要时使用宏。过度使用宏会使代码更难以读取和维护,因为代码不再读取或行为类似于标准C.

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

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

Stroustrup希望使预处理器在C ++中无关紧要,但这还没有发生。作为Peter notes,一些C ++标准,例如2005年左右的JSF AV C++ Coding Standards联合攻击战斗机,飞行器),规定C预处理器的使用最少。从本质上讲,JSF AV C ++规则将其限制为#include#ifndef XYZ_H / #define XYZ_H / ... / #endif舞蹈,以防止单个标头的多个包含。 C ++有一些在C中不可用的选项 - 特别是对类型常量的更好支持,然后可以在C不允许使用它们的地方使用它们。另请参阅static const vs #define vs enum以了解有关问题的讨论。

最好尽量减少预处理器的使用 - 它经常被滥用至少与使用它一样多(请参阅Boost preprocessor'库'来说明你有多远可以使用C预处理器。)

摘要

预处理器是C的组成部分,#define#if等不能完全避免。教授在该问题中的陈述通常无效: #define在行业标准中被禁止与#if#ifdef#else以及一些其他宏充其量只是一种过度陈述,但可以通过明确引用特定行业标准来支持(但所涉及的标准不包括ISO / IEC 9899:2011 - C标准)。

请注意,David Hammen有一个provided information关于一个特定的C编码标准 - JPL C Coding Standard - 禁止许多人在C中使用的很多东西,包括限制使用C预处理器(并限制使用动态内存分配,并禁止递归 - 读取它以查明原因,并确定这些原因是否与您相关)。

答案 1 :(得分:33)

不,禁止使用宏。

事实上,在头文件中使用#include警卫是一种常见的技术,通常是强制性的,并受到公认编码指南的鼓励。有些人声称#pragma once是另一种选择,但问题是#pragma once - 根据定义,因为编译指示是编译器特定扩展的标准提供的钩子 - 是非标准的,甚至是如果它得到了许多编译器的支持。

尽管如此,由于宏引入的问题(不考虑范围等),有许多行业指南和鼓励的做法主动阻止除#include警卫之外的所有宏的使用。在C ++开发中,宏的使用比在C开发中更加强烈。

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

答案 2 :(得分:30)

某些编码标准可能会阻止甚至禁止使用#define来创建带有参数的类似函数的宏,例如

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

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

某些标准可能会阻止或禁止使用#ifdef进行条件编译。例如,以下代码使用条件编译来正确打印size_t值。对于C99及更高版本,您使用%zu转换说明符;对于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(或Ant,或者您正在使用的任何构建工具)处理编译和链接正确的版本。对于这个荒谬过度的例子,我已经看到代码是#ifdef的一个无法追踪的老鼠的巢,应该将条件代码考虑在内分成文件。

但是,我不知道有任何公司或行业组织完全禁止使用预处理程序语句。

答案 3 :(得分:17)

宏不能被禁止&#34;。声明是无稽之谈。字面上。

例如,C Standard 7.5错误<errno.h> 部分要求使用宏:

  

1标题<errno.h>定义了几个宏,所有宏都与报告错误条件有关。

     

2宏是

EDOM
EILSEQ
ERANGE
     

扩展为类型为int的不同的整数常量表达式   正值,适用于#if预处理   指令;和

errno
     

扩展为具有类型int和线程的可修改左值   本地存储持续时间,其值设置为正误差   由几个库函数编号。如果是宏定义   抑制为了访问实际对象,或程序定义   名称为errno的标识符,行为未定义。

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

答案 4 :(得分:14)

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

例如,您可以使用

#define DEBUG

在您的代码中,以便稍后,您可以使用#ifdef DEBUG为条件编译指定部分代码,仅用于调试目的。我不认为心智正常的人会想要禁止这样的事情。使用#define定义的宏也广泛用于可移植程序,以启用/禁用特定于平台的代码的编译。

但是,如果您使用的是

#define PI 3.141592653589793

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

const double PI = 3.141592653589793;

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

类似地(如上面的John Bode所述),类似函数的宏的使用可能会被拒绝,尤其是在可以使用模板的C ++中。而不是

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

考虑使用

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

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

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

再一次,我们的想法是通过使用语言的工具而不是预处理器,允许编译器键入check并且(可能)生成更好的代码。

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

答案 5 :(得分:12)

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

答案 6 :(得分:11)

没有。它没有被禁止。说实话,没有它就不可能做非平凡的多平台代码。

答案 7 :(得分:8)

没有你的教授错了或你听错了。

#define是一个预处理器宏,条件编译需要预处理器宏,还有一些约定,它们不是简单地用C语言构建的。例如,在最近的C标准,即C99中,增加了对布尔的支持。但它不受支持&#34; native&#34;通过语言,但由预处理器#define s。见this reference to stdbool.h

答案 8 :(得分:6)

在GNU land C中使用宏非常多,没有条件预处理器命令就无法正确处理相同源文件的多个包含,这使得它们看起来像是我必不可少的语言功能。

也许你的课实际上是在C ++上,尽管许多人没有这样做,但是应该区别于C,因为它是一种不同的语言,我不能在那里代表宏。或许教授意味着他在班上禁止他们。无论如何,我确信SO社区会对他正在谈论的标准感兴趣,因为我非常确定所有C标准都支持使用宏。

答案 9 :(得分:5)

与迄今为止的所有答案相反,在高可靠性计算中经常禁止使用预处理程序指令。这有两个例外,这些组织的使用是强制性的。这些是#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编码标准并没有完全禁止使用预处理器,但它接近了。具体来说,它说

  

规则20(预处理器使用)   使用C预处理器 仅限于文件包含和简单宏。 [十条规则8的权力]。

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

答案 10 :(得分:2)

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

#ifdef __cplusplus
extern "C" {
#endif

/* C header file body */

#ifdef __cplusplus
}
#endif

答案 11 :(得分:1)

查看任何头文件,您将看到如下内容:

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

这些定义不仅是允许的,而且本质上是关键的,因为每次在文件中引用头文件时它都将单独包含。这意味着如果没有定义,你会多次重新定义防护之间的所有内容,最好的情况是无法编译,更糟糕的情况会让你后悔,为什么你的代码不能按照你想要的方式工作。

编译器还将使用define作为看见here with gcc,它允许您测试编译器的版本,这是非常有用的。我目前正在开发一个需要用avr-gcc编译的项目,但我们有一个测试环境,我们也运行我们的代码。为了防止avr特定文件和寄存器阻止我们的测试代码运行,我们执行以下操作:

#ifdef __AVR__
//avr specific code here
#endif

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

答案 12 :(得分:1)

如果您刚刚提到#define,我可能会想到他可能会暗指使用枚举,最好使用enum来避免愚蠢的错误,例如两次分配相同的数值

请注意,即使对于这种情况,有时最好使用#define而不是枚举,例如,如果您依赖与其他系统交换的数值,即使添加/,实际值也必须保持不变删除常量(兼容性)。

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

他的意思可能是(在适当的情况下),您不应该在源代码中硬编码行为(这需要重新编译以获得不同的行为),而是使用某种形式的运行时改为配置。

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