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


68

我是一年级计算机专业的学生,我的教授说#define的行业标准,以及被禁止#if#ifdef#else,和其他一些预处理指令。由于出现意外行为,他使用了“禁止”一词。

这个准确吗?如果可以,为什么?

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


62
首先,我听说过。没有; #define等被广泛使用。有时使用得太广泛,但肯定会使用。在某些地方C标准要求使用宏-您不能轻易避免使用这些宏。您可以检查MISRA C标准;他们倾向于禁止事情,但是我很确定,即使他们也意识到#define有时也需要et al。
乔纳森·莱夫勒

18
更好的经验法则是“仅在没有其他作用时使用#define”。
8月Karlstrom

21
也许会问,由谁禁止,在哪个行业中,与该禁止令相关的出版物有哪些?
Random832

33
您的教授不仅是错误的,而且是可笑的错误。要直言不讳地说出来,他们必须拥有零经验和几乎零的C知识。此人没有商务教学。
Leushenko

4
@psraganvesh您的问题可能被错误地标记了吗?你是说C ++吗?因为在C ++中,可以使用更多方法来“避开” #define。不过他还是错的。

Answers:


141

首先,我听说过。

没有; #define等被广泛使用。有时使用得太广泛,但肯定会使用。在某些地方C标准要求使用宏-您不能轻易避免使用这些宏。例如,§7.5错误<errno.h>指出:

宏是

可以扩展为具有类型int,不同的正值的整数常量表达式,并且适合在#if预处理指令中使用;…

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

您可以检查一种标准MISRA C(2012)标准;往往会禁止使用某些东西,但即使承认#define有时也需要et al(第8.20节,规则20.1至20.14涵盖了C预处理程序)。

NASA GSFC(哥达德太空飞行中心)C编码标准只是说:

仅在必要时才使用宏。宏的过度使用会使代码难以阅读和维护,因为代码不再读取或表现得像标准C。

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

CERT C编码标准有很多关于使用预处理程序的指导方针,并暗示你应该尽量少用预处理程序,但并没有禁止其使用。

Stroustrup希望使预处理器在C ++中无关紧要,但这尚未发生。正如Peter所 指出的那样,某些C ++标准(例如,大约在2005年推出的JSF AV C ++编码标准联合打击战斗机,飞机))规定了对C预处理程序的最少使用。本质上,JSF AV C ++规则将其限制为#include#ifndef XYZ_H/ #define XYZ_H/ ... /#endif跳舞防止一个头的多个夹杂物。C ++有一些C语言中没有的选项-尤其是对类型常量的更好支持,然后可以在C不允许使用它们的地方使用它们。又见static constVS #defineVSenum为的问题有一个讨论。

尽量减少对预处理器的使用是一个好主意-滥用它的程度通常至少与所使用的一样多(有关使用C预处理器的说明,请参见Boost 预处理器的“库”)。

概要

预处理器是C和的一个组成部分#define#if等不能完全避免。在这个问题的教授的说法是不普遍有效的:#define被禁止在沿着行业标准#if#ifdef#else和一些其它的宏是最好过的语句,但可能受到支持并明确提及具体的行业标准(但相关标准不包括ISO / IEC 9899:2011(C标准)。


需要注意的是大卫Hammen提供的信息,约一个特定的C编码标准-在JPLÇ编码标准-禁止很多事情,很多人用C使用,其中包括限制使用的C预处理(和限制使用的动态内存分配,并禁止递归-请仔细阅读以了解原因,并确定这些原因是否与您有关。


3
MISRA的一些规则要求/建议使用宏的某些限制,但它们相当普遍。实际上,他们的宽容让我感到惊讶。即使#define MAX(a,b) ((a>b)?(a):(b))有潜在危险,Even也可以。
Sneftel

1
@Sneftel:谢谢。我没有阅读所有的MISRA规则(在“待办事项”列表中,但没有优先考虑),但这是“行业标准”的一种可能来源,确实限制了您可以做什么该标准允许。
乔纳森·莱夫勒

1
对于安全性或任务关键性开发,有许多准则或标准禁止在C和C ++中使用功能宏。
彼得

1
@Peter:你能举一个具体的例子吗?MISRA并没有按照Sneftel所说的做,而且我的扫描很少。我只是去查看NASA GSFC(Goddard太空飞行中心)C编码标准指南,它说:仅在必要时才使用宏。宏的过度使用会使代码更难于阅读和维护,因为代码不再读取或表现得像标准C。而是继续介绍函数宏的示例。
Jonathan Leffler 2015年

5
阅读C标准中的7.5错误。它需要使用宏。从字面上看。
安德鲁·亨利

33

不,不禁止使用宏。

实际上,#include在头文件中使用防护是一种常见的技术,通常是强制性的,并且受到公认的编码准则的鼓励。有些人声称这#pragma once是一种替代方法,但是问题是#pragma once-顾名思义,因为编译指示是标准针对特定于编译器的扩展所提供的一个钩子-即使是许多编译器支持的,它也是非标准的。

就是说,#include由于宏引入的问题(不尊重范围等),因此有许多行业准则和鼓励的做法会积极地阻止除警卫之外的所有宏使用。在C ++开发中,对宏的使用比在C开发中更为强烈。

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


30

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

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

某些标准可能不鼓励或禁止使用#ifdefs进行条件编译。例如,以下代码使用条件编译来正确打印出一个size_t值。对于C99和更高版本,请使用%zu转换说明符;否则,请使用转换说明符。对于C89及更早版本,请使用%lu并将其强制转换unsigned long

某些标准可能会强制要求您执行此模块两次,而不是执行此一次,一次是针对C89及更早版本,一次是针对C99及更高版本:

然后让Make(或Ant或您使用的任何构建工具)处理编译和链接正确的版本。对于此示例,这是荒谬的过大杀伤力,但我看到的代码是#ifdefs的不可追踪的嵌套,应该将条件代码分解为单独的文件。

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


16

宏不能被“禁止”。声明是胡说八道。从字面上看。

例如,部分7.5错误<errno.h>的的C标准 需要使用宏:

1标头<errno.h>定义了几个宏,这些宏都与错误情况的报告有关。

2宏是

可以扩展为具有类型int,不同的正值的整数常量表达式,并且适合在#if预处理指令中使用;和

它扩展为具有类型int和线程本地存储持续时间的可修改左值,多个库函数将其值设置为正错误数。如果取消了宏定义以访问实际对象,或者程序使用name定义了一个标识符,则errno该行为未定义。

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


不使用它们会导致未定义的行为,似乎与文本所说的不完全相同(或者我误读/误解了)?它说在UB时,“抑制了宏定义”或“定义了一个名为errno的标识符”,而我看不到如何绘制从该链接到“不使用宏”的链接。相反,这两个动作(抑制/定义)都是您在使用宏时(而不是在不使用它们时)执行的典型操作?
stijn

1
@stijn-如何errno在不调用UB的情况下不使用宏来访问?该标准明确指出errno是一个宏,并且明确指出禁止该errno宏或以其他方式errno在UB中定义您自己的结果。而且,通常如何不使用就可以编写健壮的代码errno? 即使您的所有信号标志都已设置,某些系统调用仍可能会中断并返回errno设置为的失败。如此,如果您使用的话,除非调用UB,否则没有宏就无法编码。EINTRSA_RESTARTerrno
安德鲁·亨利

是的,这是完全正确的,而且我不主张一个人不应该使用宏,并且绝对不应该一个人使用errno,而不仅仅是挑剔,但是您的评论imo仍然没有一对一的证据证明不单独使用宏代码以某种方式导致UB。(请注意您的最后一句话“如果您使用errno”,那么首先,如果您还不满意,那么,其次:即使您确实使用了errno,您实际上已经在使用宏了,仍然使用更多的宏来调用UB,因此又一次使用它们
stijn 2015年

@stijn我的回答指出,不仅宏是C的必需部分,在某些情况下,不使用它们会导致未定义的行为。 7.5节明确将宏作为C语言的必需部分。并且,如果您尝试使用errno而不使用必需的宏,则将调用UB。明确?
安德鲁·亨利

1
@Andew:我相信stijn的观点是“不使用errno”和“不使用宏而不使用errno”之间存在区别,因此导致UB的不是“不使用它们”,而是错误地使用了它们。nitpick是英国人,而不是C人
Ray

15

不,#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; }

再一次,我们的想法是通过使用语言的功能而不是预处理程序,可以使编译器进行类型检查,并(可能)生成更好的代码。

拥有足够的编码经验之后,您将确切知道何时适合使用#define。在此之前,我认为对您的老师强加某些规则和编码标准是一个好主意,但最好让他们自己应该知道并能够解释原因。全面禁止#define是没有道理的。


12

这是完全错误的,宏在C语言中被大量使用。初学者经常滥用宏,但这并不是禁止它们进入行业的原因。一个经典的坏用法是#define succesor(n) n + 1。如果您希望2 * successor(9)给出20,那么您错了,因为该表达式将被转换为2 * 9 + 119(而不是20)。使用括号来获得预期的结果。


2
我不确定该示例是否明确说明了初学者的错误用法,
psrag anvesh 2015年

1
的确,每个人都应该知道宏需要扩展为等效的单令牌,这在扩展过程中几乎强制使用括号…以及以下论点:#define successor(n) ((n) + 1)
mirabilos 2015年

11

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


2
多平台和交叉编译器,我会说:)
Andrea Corbellini 2015年

真的吗?如何创建一个可在POSIX线程和/或Windows API上运行而不依赖于第三方填充程序(a-la cygwin或mingw)的线程框架?我们不仅在谈论同一个操作系统,多个硬件,还涉及多个操作系统或编译器供应商。是否曾经尝试过支持针对Linux和Integrity-OS的系统,而这两个系统都需要不同的编译器?否定所有您想要的。无论如何,在肮脏的地面上的事实不会改变。
luis.espinal

8
我100%同意你的看法。我只是补充说预处理器在编写交叉编译器代码时也很有用。
Andrea Corbellini 2015年

8

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

#define是预处理器宏,条件编译和某些约定需要预处理器宏,而这些不是简单地用C语言构建的。例如,在最新的C标准(即C99)中,添加了对布尔值的支持。但是语言不支持“本机”,而是预处理器支持#define。请参阅对stdbool.h的引用


2
在我第二次问他之后,他第二次说:“由于不确定的行为而被行业禁止”,因为我再也没有听说过
psrag anvesh 2015年

未定义的行为?:|
Andrea Corbellini 2015年

3
@psraganvesh未定义的行为是由例如*NULLchar foo[2]; foo[2];所有编程错误引起的。为了更好地了解什么是不确定行为(通常简称为UB),我建议这样做:c2.com/cgi/wiki?UndefinedBehavior
Superlokkus

2
@psraganvesh根据C标准,禁止宏将导致未定义的行为。看到我的回答errno-标准特别声明不使用errno宏会导致未定义的行为。,
Andrew Henle 2015年

1
@psraganvesh如果您的老师实际上说#define,#if等导致未定义的行为,那么他应该在C课上上课。很有可能他从未用C编写过任何代码,并且欺骗了自己。我会考虑与更高的人讨论这个问题。
管道

6

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

也许您的课程实际上是在C ++上进行的,尽管许多人没有这样做,但应该将它与C区别开来,因为它是另一种语言,我不能在那儿讲宏。也许教授是说他要禁止他们上课。无论如何,我确定SO社区会对听到他在谈论哪个标准感兴趣,因为我非常确定所有C标准都支持使用宏。


3
笑我怎能不知道我正在听他哪一类专门说了两遍之后,我问他第二次
psrag anvesh

2
C ++中也需要宏。例如,没有其他方法(我知道)放入标头后卫(#ifndef HEADER_H #define HEADER_H ... #endif
R_Kapp

1
您从未听说过@R_Kapp#pragma once吗?绝对可以查找它-它尚未成为标准,但得到每个供应商(GCC,Clang,EDG,MSVC,英特尔,Green Hills,ARM等)的支持,并大大减少了产生像这样的愚蠢错别字的机会#ifndef FOO_H #define FOOH#ifdef FOO_H #define FOO_H在两个不同的.h文件中使用相同的宏。(它还提高了编译时间,并消除了关于#ifndef-style包含卫兵的缩进和缩进的神圣战争。)当然,条件编译有其用处,但“穷人#pragma once不是其中之一。
Quuxplusone

2
@Quuxplusone #pragma几乎肯定不会因为成为pragma而使其成为C标准。保留了用于实现定义的行为的语法,因此将其添加到标准中会破坏以不符合该新假定标准的方式使用它的每个编译器。因此,我想说的是,如果可能的话,最好避免使用,并且这对于#ifndef是非常有效的用法
2015年

2
比起撰写提案,要使标准更重要。在C和C ++标准中,编译指示的要点是为实现定义的行为提供一个钩子,实现可以随意忽略的任何使用#pragma。因此,建议任何杂用语都将被标准化只是一种幻想-这个概念与杂用语完全不符。几个供应商将其某些特定于供应商的功能误解为标准的事实并没有做到这一点。
彼得

6

与迄今为止的所有答案相反,在高可靠性计算中通常禁止使用预处理程序指令。对此有两个例外,在此类组织中强制使用。这些是#include指令,并且在头文件中使用包含保护。在C ++中而不是在C中更可能出现此类禁令。

这只是一个示例:16.1.1仅将预处理器用于实现包含保护,以及包含带有包含保护的头文件

另一个示例,这次是C而不是C ++:C编程语言的JPL机构编码标准。这个C编码标准并没有完全禁止使用预处理器,但是已经接近了。具体来说,它说

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


我既不容忍也不反对这些标准。但是说它们不存在真是可笑。


而问题被打上C.
JXH

@jxh-我曾在那些有这样的禁令的组织中工作过,涉及C而不是C ++。我个人不喜欢它,但是它们确实存在。
大卫·哈门

是的,其结果是它消除了标准化的代码生成机制,并导致使用脚本语言来生成正确的源代码而非条件编译的黑客行为。
jxh 2015年

6
至少有一种禁止宏的环境与说整个行业禁止宏完全不同。因此,现有的答案既不是错误的,也不是“荒谬的”。
Lightness Races in Orbit

2
@LightnessRacesinOrbit-我并不是说整个行业都禁止使用它们。评分最高(并且被IMNHO错误接受)的答案是“首先我听说过”,以及其他一些类似的答案。我们这里是一位教授,他可能只知道一个行业严重限制了预处理器的使用,一位学生极有可能误解了教授所说的话,以及一堆答案都过度解释了这个被误解的问题。这不仅仅是XY问题。这是一个XYZW问题。
大卫·哈门

2

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


但这可以#include "c++/header.h"在C ++代码中完成,其中文件"c++/header.h"包含:(extern "C" { / #include "c/header.h" / }单斜杠表示换行-当然,子目录名称的选择在很大程度上是任意的),这样可以避免条件编译,但这样做的代价是多余的文件。我同意,您显示的是最常用的(我自己使用)。但是,如果设置正确,实际上并没有必要。
乔纳森·勒夫勒

有条件的编译可以替换为一个makefile,该makefile根据条件选择不同的源文件。它并不能使其成为更好的解决方案,它只是符合“否#if”的要求。两个头文件(其中一个头文件仅包含另一个头文件)会产生问题。
jxh

1

查看任何头文件,您将看到类似以下内容:

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

编译器还将在gcc中使用define,如此处所示可以让您测试诸如编译器版本之类的功能,这非常有用。我目前正在开发一个需要使用avr-gcc进行编译的项目,但是我们有一个测试环境,尽管我们也可以运行我们的代码。为了防止avr特定的文件和寄存器阻止我们的测试代码运行,我们执行以下操作:

在生产代码中使用此代码,可以在不使用avr-gcc的情况下编译补充测试代码,并且上述代码仅使用avr-gcc进行编译。


1
在禁止使用处理器的组织中,包括警卫人员在内的人员不算作该禁令的一部分。(实际上,在此类组织中强制使用包含警戒。)
David Hammen 2015年

1

如果您刚刚提到过#define,我可能会认为他是在暗示它用于枚举,最好使用它enum以避免诸如两次分配相同数值之类的愚蠢错误。

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

然而,并称#if#ifdef等也不应使用仅仅是怪异。当然,可能不应该滥用它们,但是在现实生活中,有许多使用它们的理由。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.