为什么只定义一个尚未定义的宏?


93

在我们的C代码库中,我看到每个宏都以以下方式定义:

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

#ifndef BEEPTRIM_ROLL_RATE_DEGPS
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#endif

#ifndef FORCETRIMRELEASE_HOLD_TIME_MS
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#endif

#ifndef TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f
#endif

进行这些定义检查而不是仅仅定义宏的原理是什么?

#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f

我在网上的任何地方都找不到这种做法的解释。


6
保证在代码中其他地方更改常量可以保证以此方式工作。如果有人在其他地方定义了这些宏之一,则它们在解析此文件时不会被预处理器覆盖。
恩佐·费伯

8
这是WET设计原则的一个示例。
2015年

发布带有示例的答案,尝试进行编译。
恩佐·费伯

Answers:


141

这使您可以在编译时覆盖宏:

gcc -DMACRONAME=value

头文件中的定义用作默认值。


51

就像我在评论中说的,想象一下这种情况:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

bar.c

#include "foo.h"
#include "defs.h"

#include <stdio.h>

int main(void)
{
    printf("%d%d", FOO, BAR);
    return 0;
}

将打印44

但是,如果ifndef不存在该条件,则结果将是MACRO重定义的编译警告,并且将打印64

$ gcc -o bar bar.c
In file included from bar.c:2:0:
defs.h:1:0: warning: "FOO" redefined [enabled by default]
 #define FOO 6
 ^
In file included from bar.c:1:0:
foo.h:1:0: note: this is the location of the previous definition
 #define FOO 4
 ^

1
这是特定于编译器的。除非重新定义是“相同的”,否则重新定义类似对象的宏是非法的(对此存在更多的技术规范,但在这里并不重要)。非法代码需要诊断,并且在发出诊断(在此为警告)后,编译器可以自由执行任何操作,包括编译具有实现特定结果的代码。
皮特·贝克尔

7
如果您有冲突DEFS了相同的宏,难道你宁愿让大多数情况下,警告?而不是默默地使用第一个定义(因为第二个ifdef定义使用来避免重新定义)。
彼得·科德斯

@PeterCordes大多数情况下,#infdefs 下的定义用作“后备”或“默认”值。基本上,“如果用户进行了配置,可以。如果没有,请使用默认值。”
Angew不再为2015年

@Angew:好的,所以如果您#defines在库头文件中包含一些属于库ABI的内容,则不要将它们包装在中#ifndef。(或者更好的方法是使用enum)。我只是想说清楚,#ifndef仅当在一个编译单元中对某项进行自定义定义而没有另一种定义时,才是合适的。如果a.c以不同的顺序包含标头b.c,则它们可能会获得的不同定义max(a,b),并且其中一个定义可能会与断开max(i++, x),但另一个可能在GNU语句表达式中使用临时属性。至少仍然令人困惑!
彼得·科德斯

@PeterCordes在这种情况下,我想做的是#ifdef FOO #error FOO already defined! #endif #define FOO x
科尔·约翰逊

17

我不知道上下文,但是它可以用于为用户提供覆盖这些宏定义所设置的值的可用性。如果用户为这些宏中的任何一个显式定义了一个不同的值,则将使用它代替此处使用的值。

例如在g ++中,您可以-D在编译过程中使用该标志将值传递给宏。


14

这样做是为了使头文件的用户可以从其代码或编译器的-D标志中覆盖这些定义。


7

任何C项目都驻留在多个源文件上。当处理单个源文件时,检查似乎(实际上)毫无意义,但是当处理大型C项目时,在定义常量之前检查现有定义是一种好习惯。这个想法很简单:您需要在该特定源文件中使用该常量,但是它可能已经在另一个文件中定义了。


2

您可以考虑为用户提供一个默认预设的框架/库,该预设允许用户对其进行编译和处理。这些定义分布在不同的文件中,建议最终用户包括它的config.h文件,供他配置其值。如果用户忘记了某些定义,则由于预设,系统可以继续工作。


1

使用

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

允许用户使用命令行参数(在gcc / clang / VS中)定义宏的值-DBEEPTRIM_PITCH_RATE_DEGPS=0.3f

还有另一个重要原因。重新定义预处理器宏是错误的。请参阅另一个SO问题的答案。如果在编译器调用中用作命令行参数,则不进行#ifndef检查的情况下,编译器应产生错误-DBEEPTRIM_PITCH_RATE_DEGPS=0.3f

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.