4

我正在尝试简化大量遗留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 */
4

7 回答 7

13

当然,您可以-DWHATEVER在命令行上传递。或者-DWHATEVER_ELSE=NO,等等。也许对于路径你可以做类似的事情

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

然后通过

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

在命令行上。

于 2009-10-27T17:36:02.357 回答
5

我们曾经做的一件事是生成一个.h包含这些定义的文件,并使用脚本生成它。这帮助我们摆脱了很多脆弱的#ifs 和#ifdefs

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

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

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

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

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

于 2009-10-27T17:49:40.003 回答
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

如果您选择好默认值,则可以节省大量能源。

可重定位软件

我不确定只能安装在一个位置的软件的设计。在我的书中,你需要能够在机器上同时安装产品的旧版本 1.12 和新的 2.1 版本,并且它们应该能够独立运行。硬编码的路径名可以解决这个问题。

按功能而不是平台参数化

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

(不要太从字面上理解这个示例标题;这是我试图克服的概念。)

将平台视为一组功能

作为前一点的推论,您确实需要检测平台并定义适用于该平台的功能。这是您拥有定义配置功能的特定于平台的配置标头的地方。

应在标题中启用产品功能

详细说明我对另一个答案的评论。

假设您在产品中有一堆功能需要有条件地包含或排除。例如:

KVLOCKING
B1SECURITY
C2SECURITY
DYNAMICLOCKS

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

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

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

相信我,经过二十年的发展和人员流动,人们不知道功能是否仍在使用(因为它是稳定的,从不引起问题 - 可能是因为它从未使用过)。而且,如果在 Makefiles 中查找是否仍然定义了 KVLOCKING 的唯一位置,那么 cscope 之类的工具就没有多大用处了——这使得在稍后尝试清理时修改代码更容易出错。

于 2009-10-27T18:50:04.827 回答
3

使用起来更理智:

#if SOMETHING

..从平台到平台,以避免混淆损坏的预处理器。然而,任何现代编译器最终都应该有效地争论你的情况。如果您提供有关您的平台、编译器和预处理器的更多详细信息,您可能会收到更简洁的答案。

考虑到其中的操作系统和变体过多,条件编译是一种必要的弊端。if、ifdef 等绝对不是对预处理器的滥用,只是按预期执行它。

于 2009-10-27T17:45:23.163 回答
3

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

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

这样做的好处是,试图找出代码(可能是调试)的人不必费力地通过(并且可能被误导)他们甚至没有运行的平台的幻像代码。

于 2009-10-27T17:56:51.830 回答
2

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

#include PLATFORM_CONFIG
#include BUILD_CONFIG

并且编译器以以下方式启动:

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

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

这具有让您在一组文件中分离平台设置和在另一组文件中的配置设置的效果。平台设置管理处理一个平台上可能不存在或格式不正确的库调用,以及定义重要的大小相关类型——特定于平台的东西。构建设置处理输出中启用的功能。

于 2009-10-27T18:08:33.913 回答
1

一般性

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

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

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

我很欣赏 Glenn Fowlernmake和 Phong Vo 的iffe(如果存在功能),我认为它们比 GNU 工具设计得更好。但是这些工具是 AT&T 软件技术套件的一部分,如果不购买整个 AST 做事方式,我就无法弄清楚如何使用它们,而我并不总是理解这一点。

你的例子

显然需要

extern char MasterSkipFile[];

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

有条件地包含“平台的正确 .h 文件集”是我会通过在可能的情况下尝试坚持 ANSI C 来处理的事情,如果不可能,在特定于平台的 .h 文件中定义兼容层。事实上,我不知道#includes 试图导入什么名称,所以我无法给出更具体的建议。

于 2009-10-27T23:41:12.290 回答