C - 替代#ifdef

时间:2009-10-27 17:33:20

标签: c c-preprocessor conditional-compilation hardcoded

我正在尝试简化大量遗留的 C 代码,其中即使在今天,在执行维护它的构建人员之前需要一个源文件并在之前手动修改以下部分基于各种环境的编译。

示例如下,但这是问题所在。我在 C 上生锈了,但我记得不鼓励使用#ifdef。你们能提供更好的选择吗?另外 - 我认为其中一些(如果不是全部)可以设置为环境变量或作为参数传入,如果是这样 - 什么是定义这些然后从源代码访问的好方法?

以下是我正在处理的代码片段

#define DAN          NO
#define UNIX         NO
#define LINUX        YES
#define WINDOWS_ES   NO
#define WINDOWS_RB   NO

/* Later in the code */
#if ((DAN==1) || (UNIX==YES))
#include <sys/param.h>
#endif

#if ((WINDOWS_ES==YES) || (WINDOWS_RB==YES) || (WINDOWS_TIES==YES))
#include <param.h>
#include <io.h>
#include <ctype.h>
#endif

/* And totally insane harcoded paths */
#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/test/MasterSkipFile";
#endif

#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif

#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif

/* So on for every platform and combination */

7 个答案:

答案 0 :(得分:13)

当然,您可以在命令行上传递-DWHATEVER。或-DWHATEVER_ELSE=NO等。也许你可以做一些像

这样的路径
char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

然后传递

-DSOME_COMMAND_LINE_DEFINITION="/home/whatever/directory/filename"

在命令行上。

答案 1 :(得分:5)

我们过去做的一件事是生成带有这些定义的.h文件,并使用脚本生成它。这帮助我们摆脱了许多脆弱的#ifs和#ifdefs

你需要注意你放在那里的东西,但机器特定的参数是很好的选择 - 这就是autoconf / automake的工作方式。

编辑:在您的情况下,一个示例是使用生成的.h文件来定义INCLUDE_SYS_PARAMINCLUDE_PARAM,并在代码本身中使用:

#ifdef INCLUDE_SYS_PARAM
#include <sys/param.h>
#endif

#ifdef INCLUDE_PARAM
#include <param.h>
#endif

使移植到新平台变得更加容易 - 新平台的存在不会渗透到代码中,只会导致生成的.h文件。

答案 2 :(得分:4)

特定于平台的配置标头

我有一个系统可以将特定于平台的配置生成到所有版本中使用的标头中。 AutoConf名称是'config.h';您可以在主题上看到'platform.h'或'porting.h'或'port.h'或其他变体。此文件包含正在构建的平台所需的信息。您可以通过将版本控制的特定于平台的变体复制到标准名称来生成文件。您可以使用链接而不是复制。或者,您可以根据脚本在计算机上找到的内容运行配置脚本以确定其内容。

配置参数的默认值

代码:

#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/MasterSkipFile";
#endif

#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif

#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif

将更好地替换为:

#ifndef MASTER_SKIP_FILE_PATH
#define MASTER_SKIP_FILE_PATH "/opt/tretools/MasterSkipFile"
#endif

const char MasterSkipFile[] = MASTER_SKIP_FILE_PATH;

那些想要在不同位置进行构建的人可以通过以下方式设置位置:

-DMASTER_SKIP_FILE_PATH='"/ptehome/tregtp/tre1/tretools/PinkElephant"'

注意使用单引号和双引号;尽量避免在命令行中使用路径中的反斜杠执行此操作。您可以对各种事物使用类似的默认机制:

#ifndef DEFAULTABLE_PARAMETER
#define DEFAULTABLE_PARAMETER default_value
#endif

如果你选择了默认值,这可以节省很多精力。

可重定位软件

我不确定只能安装在一个位置的软件设计。在我的书中,您需要能够在新的2.1版本的同时在机器上安装旧版本的1.12产品,并且它们应该能够独立运行。硬编码的路径名称会失败。

按功能而非平台

进行参数化

AutoConf工具和普通替代系统之间的主要区别在于配置是基于功能而不是在平台上完成的。您可以参数化代码以标识要使用的功能。这是至关重要的,因为功能往往出现在原始平台以外的平台上。我照看有代码的代码:

#if defined(SUN4) || defined(SOLARIS_2) || defined(HP_UX) || \
    defined(LINUX) || defined(PYRAMID) || defined(SEQUENT) || \
    defined(SEQUENT40) || defined(NCR) ...
#include <sys/types.h>
#endif

拥有它会好得多:

#ifdef INCLUDE_SYS_TYPES_H
#include <sys/types.h>
#endif

然后在需要它的平台上生成:

#define INCLUDE_SYS_TYPES_H

(不要太过字面意思地使用这个示例标题;这是我试图克服的概念。)

将平台视为一组功能

作为前一点的必然结果,您需要检测平台并定义适用于该平台的功能。这是您具有特定于平台的配置标头,用于定义配置功能。<​​/ p>

应在标题

中启用产品功能

阐述我对另一个答案的评论。

假设产品中有许多功能需要有条件地包含或排除。例如:

KVLOCKING
B1SECURITY
C2SECURITY
DYNAMICLOCKS

设置适当的define时包含相关代码:

#ifdef KVLOCKING
...KVLOCKING stuff...
#else
...non-KVLOCKING stuff...
#endif

如果您使用像cscope这样的源代码分析工具,那么在定义KVLOCKING时它可以显示给您很有帮助。如果定义它的唯一地方是散布在构建系统周围的一些随机Makefile(让我们假设有一百个子目录在这里使用),很难判断代码是否仍在使用你的平台。如果定义位于某个标题中 - 特定于平台的标题,或者可能是产品发布标题(因此版本1.x可以包含KVLOCKING,而版本2.x可以包含C2SECURITY但2.5包含B1SECURITY等),那么您可以看到KVLOCKING代码仍在使用中。

相信我,经过二十年的发展和人员流动,人们不知道功能是否仍然在使用(因为它是稳定的,永远不会导致问题 - 可能是因为它从未使用过)。如果找到KVLOCKING是否仍然定义的唯一地方是在Makefile中,那么像cscope这样的工具就不那么有用了 - 这使得在以后尝试清理时修改代码更容易出错。

答案 3 :(得分:3)

使用它更加合理:

#if SOMETHING

..从平台到平台,避免混淆破碎的预处理器。但是,任何现代编译器最终都应该有效地论证你的情况。如果您在平台,编译器和预处理器上提供更多详细信息,您可能会收到更简洁的答案。

条件编译,鉴于其中过多的操作系统和变体是必要的邪恶。如果,ifdef等最明显滥用预处理器,只是按预期行使它。

答案 4 :(得分:3)

我首选的方法是让构建系统执行操作系统检测。您希望将特定于机器的内容隔离到单个源文件中的复杂案例,并为不同的操作系统提供完全不同的源文件。

所以在这种情况下,你在该文件中有一个#include "OS_Specific.h"。您为此平台添加了不同的包含和MasterSkipFile的定义。您可以通过在编译器命令行上指定不同的-I(包括路径目录)来在它们之间进行选择。

这样做的好处是,有人试图找出代码(可能是调试)并不需要为他们甚至没有运行的平台的幻像代码(并且可能被误导)

答案 5 :(得分:2)

我见过构建系统,其中大多数源文件都是这样开始的:

#include PLATFORM_CONFIG
#include BUILD_CONFIG

并且编译器开始于:

cc -DPLATFORM_CONFIG="linuxconfig.h" -DBUILD_CONFIG="importonlyconfig.h"

(这可能需要反斜杠转义)

这样可以让您将一组文件中的平台设置与另一组文件中的配置设置分开。平台设置管理处理可能不存在于一个平台上的库调用,或者不正确格式,以及定义重要的依赖于大小的类型 - 特定于平台的东西。构建设置处理输出中启用的功能。

答案 6 :(得分:1)

普通

我是一位从GNU Autotools教堂被赶出的异教徒。为什么?因为我想了解我的工具到底在做什么。而且因为我有尝试组合两个组件的经验,每个组件都坚持使用不同的,不兼容的autotools版本作为我计算机上安装的默认版本。

我通过为平台和重要抽象的每个组合创建一个.h文件或.c文件来工作。我努力定义一个中心.h文件,说明接口是什么。通常这意味着我最终会创建一个“兼容层”,使我免受平台之间的差异的影响。我总是尽可能使用ANSI标准C,而不是特定于平台的功能。

我有时会编写脚本来生成与平台相关的文件。但是脚本总是手工编写并记录下来,所以我知道他们做了什么。

我很欣赏Glenn Fowler的nmake和Phong Vo的iffe(如果存在特征),我认为它比GNU工具更好。但是这些工具是AT&amp; T软件技术套件的一部分,而且我还没有弄清楚如何使用它们而不购买整个AST的做事方式,我并不总是理解。

你的例子

显然需要

extern char MasterSkipFile[];

在某个.h文件中,然后您可以链接到合适的.o。

有条件地包含“适用于平台的.h文件集”是我可以通过尽可能坚持使用ANSI C来处理的,并且在不可能的情况下,在特定于平台的.h中定义兼容层。文件。事实上,我无法分辨#include试图导入的名称,因此我无法提供更具体的建议。