我是一年级计算机专业的学生,我的教授说#define
的行业标准,以及被禁止#if
,#ifdef
,#else
,和其他一些预处理指令。由于出现意外行为,他使用了“禁止”一词。
这个准确吗?如果可以,为什么?
实际上,是否有任何禁止使用这些指令的标准?
我是一年级计算机专业的学生,我的教授说#define
的行业标准,以及被禁止#if
,#ifdef
,#else
,和其他一些预处理指令。由于出现意外行为,他使用了“禁止”一词。
这个准确吗?如果可以,为什么?
实际上,是否有任何禁止使用这些指令的标准?
Answers:
首先,我听说过。
没有; #define
等被广泛使用。有时使用得太广泛,但肯定会使用。在某些地方C标准要求使用宏-您不能轻易避免使用这些宏。例如,§7.5错误<errno.h>
指出:
宏是
EDOM EILSEQ ERANGE
可以扩展为具有类型
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 const
VS #define
VSenum
为的问题有一个讨论。
尽量减少对预处理器的使用是一个好主意-滥用它的程度通常至少与所使用的一样多(有关使用C预处理器的说明,请参见Boost 预处理器的“库”)。
预处理器是C和的一个组成部分#define
和#if
等不能完全避免。在这个问题的教授的说法是不普遍有效的:#define
被禁止在沿着行业标准#if
,#ifdef
,#else
和一些其它的宏是最好过的语句,但可能受到支持并明确提及具体的行业标准(但相关标准不包括ISO / IEC 9899:2011(C标准)。
需要注意的是大卫Hammen已提供的信息,约一个特定的C编码标准-在JPLÇ编码标准-禁止很多事情,很多人用C使用,其中包括限制使用的C预处理(和限制使用的动态内存分配,并禁止递归-请仔细阅读以了解原因,并确定这些原因是否与您有关。
#define MAX(a,b) ((a>b)?(a):(b))
有潜在危险,Even也可以。
不,不禁止使用宏。
实际上,#include
在头文件中使用防护是一种常见的技术,通常是强制性的,并且受到公认的编码准则的鼓励。有些人声称这#pragma once
是一种替代方法,但是问题是#pragma once
-顾名思义,因为编译指示是标准针对特定于编译器的扩展所提供的一个钩子-即使是许多编译器支持的,它也是非标准的。
就是说,#include
由于宏引入的问题(不尊重范围等),因此有许多行业准则和鼓励的做法会积极地阻止除警卫之外的所有宏使用。在C ++开发中,对宏的使用比在C开发中更为强烈。
禁止使用某种东西与禁止使用某物是不同的,因为仍然可以合法地使用它-例如,通过记录理由。
一些编码标准可能会阻止甚至禁止使用#define
来创建带有参数的类似函数的宏,例如
#define SQR(x) ((x)*(x))
因为a)这样的宏不是类型安全的,并且b)不可避免地会有人写SQR(x++)
,这是不好的做法。
某些标准可能不鼓励或禁止使用#ifdef
s进行条件编译。例如,以下代码使用条件编译来正确打印出一个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
s的不可追踪的嵌套,应该将条件代码分解为单独的文件。
但是,我不知道有任何公司或行业组织完全禁止使用预处理程序语句。
宏不能被“禁止”。声明是胡说八道。从字面上看。
例如,部分7.5错误<errno.h>
的的C标准 需要使用宏:
1标头
<errno.h>
定义了几个宏,这些宏都与错误情况的报告有关。2宏是
EDOM EILSEQ ERANGE
可以扩展为具有类型
int
,不同的正值的整数常量表达式,并且适合在#if
预处理指令中使用;和errno
它扩展为具有类型
int
和线程本地存储持续时间的可修改左值,多个库函数将其值设置为正错误数。如果取消了宏定义以访问实际对象,或者程序使用name定义了一个标识符,则errno
该行为未定义。
因此,不仅宏是C的必需部分,在某些情况下,不使用宏也会导致不确定的行为。
errno
而不使用必需的宏,则将调用UB。明确?
不,#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
是没有道理的。
这是完全错误的,宏在C语言中被大量使用。初学者经常滥用宏,但这并不是禁止它们进入行业的原因。一个经典的坏用法是#define succesor(n) n + 1
。如果您希望2 * successor(9)
给出20,那么您错了,因为该表达式将被转换为2 * 9 + 1
19(而不是20)。使用括号来获得预期的结果。
#define successor(n) ((n) + 1)
不。它不是被禁止的。实话实说,没有它,就不可能进行非平凡的多平台代码。
没有你的教授错了,或者你听错了什么。
#define
是预处理器宏,条件编译和某些约定需要预处理器宏,而这些不是简单地用C语言构建的。例如,在最新的C标准(即C99)中,添加了对布尔值的支持。但是语言不支持“本机”,而是预处理器支持#define
。请参阅对stdbool.h的引用
*NULL
或char foo[2]; foo[2];
所有编程错误引起的。为了更好地了解什么是不确定行为(通常简称为UB),我建议这样做:c2.com/cgi/wiki?UndefinedBehavior
errno
-标准特别声明不使用errno
宏会导致未定义的行为。,
宏在GNU land C中大量使用,并且没有条件预处理程序命令,就无法正确处理同一源文件的多个包含,因此对我来说它们似乎是必不可少的语言功能。
也许您的课程实际上是在C ++上进行的,尽管许多人没有这样做,但应该将它与C区别开来,因为它是另一种语言,我不能在那儿讲宏。也许教授是说他要禁止他们上课。无论如何,我确定SO社区会对听到他在谈论哪个标准感兴趣,因为我非常确定所有C标准都支持使用宏。
#ifndef HEADER_H #define HEADER_H ... #endif
)
#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
”不是其中之一。
#pragma
。因此,建议任何杂用语都将被标准化只是一种幻想-这个概念与杂用语完全不符。几个供应商将其某些特定于供应商的功能误解为标准的事实并没有做到这一点。
与迄今为止的所有答案相反,在高可靠性计算中通常禁止使用预处理程序指令。对此有两个例外,在此类组织中强制使用。这些是#include
指令,并且在头文件中使用包含保护。在C ++中而不是在C中更可能出现此类禁令。
这只是一个示例:16.1.1仅将预处理器用于实现包含保护,以及包含带有包含保护的头文件。
另一个示例,这次是C而不是C ++:C编程语言的JPL机构编码标准。这个C编码标准并没有完全禁止使用预处理器,但是已经接近了。具体来说,它说
规则20(使用预处理器)C预处理器的使用应限于文件包含和简单宏。[十条规则八的力量]。
我既不容忍也不反对这些标准。但是说它们不存在真是可笑。
如果希望C代码与C ++代码互操作,则需要在extern "C"
名称空间中声明外部可见的符号,例如函数声明。这通常使用条件编译来完成:
#ifdef __cplusplus
extern "C" {
#endif
/* C header file body */
#ifdef __cplusplus
}
#endif
#include "c++/header.h"
在C ++代码中完成,其中文件"c++/header.h"
包含:(extern "C" { / #include "c/header.h" / }
单斜杠表示换行-当然,子目录名称的选择在很大程度上是任意的),这样可以避免条件编译,但这样做的代价是多余的文件。我同意,您显示的是最常用的(我自己使用)。但是,如果设置正确,实际上并没有必要。
#if
”的要求。两个头文件(其中一个头文件仅包含另一个头文件)会产生问题。
查看任何头文件,您将看到类似以下内容:
#ifndef _FILE_NAME_H
#define _FILE_NAME_H
//Exported functions, strucs, define, ect. go here
#endif /*_FILE_NAME_H */
这些定义不仅是允许的,而且本质上很关键,因为每次在文件中引用头文件时,头文件都会被单独包含。这意味着,如果没有定义,您将多次重新定义防护之间的所有内容,最好的情况是无法编译,最坏的情况是您日后scratch不休,为什么您的代码无法按照您希望的方式工作。
编译器还将在gcc中使用define,如此处所示,可以让您测试诸如编译器版本之类的功能,这非常有用。我目前正在开发一个需要使用avr-gcc进行编译的项目,但是我们有一个测试环境,尽管我们也可以运行我们的代码。为了防止avr特定的文件和寄存器阻止我们的测试代码运行,我们执行以下操作:
#ifdef __AVR__
//avr specific code here
#endif
在生产代码中使用此代码,可以在不使用avr-gcc的情况下编译补充测试代码,并且上述代码仅使用avr-gcc进行编译。
#define
等被广泛使用。有时使用得太广泛,但肯定会使用。在某些地方C标准要求使用宏-您不能轻易避免使用这些宏。您可以检查MISRA C标准;他们倾向于禁止事情,但是我很确定,即使他们也意识到#define
有时也需要et al。