C - #ifdef 的替代方案

发布于 2024-08-09 06:36:28 字数 1038 浏览 2 评论 0原文

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

I'm trying to streamline large chunk of legacy C code in which, even today, before doing the build guy who maintains it takes a source file(s) and manually modifies the following section before the compilation based on the various types of environment.

The example follows but here's the question. I'm rusty on my C but I do recall that using #ifdef is discouraged. Can you guys offer better alternative? Also - I think some of it (if not all of it) can be set as environment variable or passed in as a parameter and if so - what would be a good way of defining these and then accessing from the source code?

Here's snippet of the code I'm dealing with

#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 */

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

原来分手还会想你 2024-08-16 06:36:28

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

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

,然后

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

在命令行上传递。

Sure, you can pass -DWHATEVER on the command line. Or -DWHATEVER_ELSE=NO, etc. Maybe for the paths you could do something like

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

and then pass

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

on the command line.

陌上青苔 2024-08-16 06:36:28

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

您需要小心放置在那里的内容,但特定于机器的参数是很好的候选者 - 这就是 autoconf/automake 的工作方式。

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

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

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

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

One thing we used to do is have a generated .h file with these definitions, and generate it with a script. That helped us get rid of a lot of brittle #ifs and #ifdefs

You need to be careful about what you put there, but machine-specific parameters are good candidates - this is how autoconf/automake work.

EDIT: in your case, an example would be to use the generated .h file to define INCLUDE_SYS_PARAM and INCLUDE_PARAM, and in the code itself use:

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

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

Makes it much easier to port to new platforms - the existence of a new platform doesn't trickle into the code, only to the generated .h file.

独自唱情﹋歌 2024-08-16 06:36:28

平台特定的配置标头

我有一个系统可以将平台特定的配置生成到所有构建中使用的标头中。 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 代码仍在使用。

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

Platform specific configuration headers

I'd have a system to generate the platform-specific configuration into a header that is used in all builds. The AutoConf name is 'config.h'; you can see 'platform.h' or 'porting.h' or 'port.h' or other variations on the theme. This file contains the information needed for the platform being built. You can generate the file by copying a version-controlled platform-specific variant to the standard name. You can use a link instead of copying. Or you can run configuration scripts to determine its contents based on what the script finds on the machine.

Default values for configuration parameters

The code:

#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

Would be better replaced by:

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

const char MasterSkipFile[] = MASTER_SKIP_FILE_PATH;

Those who want the build in a different location can set the location via:

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

Note the use of single and double quotes; try to avoid doing this on the command line with backslashes in the path. You can use a similar default mechanism for all sorts of things:

#ifndef DEFAULTABLE_PARAMETER
#define DEFAULTABLE_PARAMETER default_value
#endif

If you choose your defaults well, this can save a lot of energy.

Relocatable software

I'm not sure about the design of the software that can only be installed in one location. In my book, you need to be able to have the old version 1.12 of the product installed on the machine at the same time as the new 2.1 version, and they should be able to operate independently. A hard-coded path name defeats that.

Parameterize by feature not platform

The key difference between the AutoConf tools and the average alternative system is that the configuration is done based on features, not on platforms. You parameterize your code to identify a feature that you want to use. This is crucial because features tend to appear on platforms other than the original. I look after code where there are lines like:

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

It would be much, much better to have:

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

And then on the platforms where it is needed, generate:

#define INCLUDE_SYS_TYPES_H

(Don't take this example header too literally; it is the concept I am trying to get over.)

Treat platform as a bundle of features

As a corollary to the previous point, you do need to detect platform and define the features that are applicable to that platform. This is where you have the platform-specific configuration header which defines the configuration features.

Product features should be enabled in a header

(Elaborating on a comment I made to another answer.)

Suppose you have a bunch of features in the product that need to be included or excluded conditionally. For example:

KVLOCKING
B1SECURITY
C2SECURITY
DYNAMICLOCKS

The relevant code is included when the appropriate define is set:

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

If you use a source code analysis tool like cscope, then it is helpful if it can show you when KVLOCKING is defined. If the only place where it is defined is in some random Makefiles scattered around the build system (let's assume there are a hundred sub-directories that are used in this), it is hard to tell whether the code is still in use on any of your platforms. If the defines are in a header somewhere - the platform specific header, or maybe a product release header (so version 1.x can have KVLOCKING and version 2.x can include C2SECURITY but 2.5 includes B1SECURITY, etc), then you can see that KVLOCKING code is still in use.

Believe me, after twenty years of development and staff turnover, people don't know whether features are still in use or not (because it is stable and never causes problems - possibly because it is never used). And if the only place to find whether KVLOCKING is still defined is in the Makefiles, then tools like cscope are less helpful - which makes modifying the code more error prone when trying to clean up later.

等往事风中吹 2024-08-16 06:36:28

使用起来更加明智:

#if SOMETHING

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

鉴于操作系统及其变体过多,条件编译是一种不可避免的罪恶。 if、ifdef 等绝对不是对预处理器的滥用,只是按预期使用它。

Its much saner to use :

#if SOMETHING

.. from platform to platform, to avoid confusing broken preprocessors. However any modern compiler should effectively argue your case in the end. If you give more details on your platform, compiler and preprocessor you might receive a more concise answer.

Conditional compilation, given the plethora of operating systems and variants therein is a necessary evil. if, ifdef, etc are most decidedly not an abuse of the preprocessor, just exercising it as intended.

惟欲睡 2024-08-16 06:36:28

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

因此,在本例中,该文件中将包含 #include "OS_Specific.h"。您放置不同的包含以及该平台的 MasterSkipFile 的定义。您可以通过在编译器命令行上指定不同的 -I (包括路径目录)来在它们之间进行选择。

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

My preferred way would be to have the build system do the OS detection. Complex cases you'd want to isolate the machine-specific stuff into a single source file, and have completely different source files for the different OSes.

So in this case, you'd have a #include "OS_Specific.h" in that file. You put the different includes, and the definition of MasterSkipFile for this platform. You can select between them by specifying different -I (include path directories) on your compiler command line.

The nice thing about doing it this way is that somebody trying to figure out the code (perhaps debugging) doesn't have to wade through (and possibly be misled by) phantom code for a platform they aren't even running on.

葬花如无物 2024-08-16 06:36:28

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

#include PLATFORM_CONFIG
#include BUILD_CONFIG

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

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

这可能需要反斜杠转义)

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

I've seen build systems in which most of the source files started something off like this:

#include PLATFORM_CONFIG
#include BUILD_CONFIG

and the compiler was kicked off with:

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

(this may need backslash escapes)

this had the effect of letting you separate out the platform settings in one set of files and the configuration settings in another. Platform settings manages handling library calls that may not exist on one platform or not in the right format as well as defining important size dependent types--things that are platform specific. Build settings handles what features are being enabled in the output.

离不开的别离 2024-08-16 06:36:28

概述

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

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

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

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

您的示例

显然需要位于

extern char MasterSkipFile[];

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

有条件地包含“平台的正确 .h 文件集”是我会通过在可能的情况下尝试坚持使用 ANSI C 来处理的问题,而在不可能的情况下,在特定于平台的 .h 文件中定义兼容层。事实上,我无法判断 #include 试图导入什么名称,因此我无法提供更具体的建议。

Generalities

I'm a heretic who has been cast out from the Church of the GNU Autotools. Why? Because I like to understand what the hell my tools are doing. And because I've had the experience of trying to combine two components, each of which insisted on a different, incompatible version of autotools being the default version installed on my computer.

I work by creating one .h file or .c filed for every combination of platform and significant abstraction. I work hard to define a central .h file that says what the interface is. Often this means I wind up creating a "compatibility layer" that insulates me from differences between platforms. Often I wind up using ANSI Standard C whenever possible, instead of platform-specific functionality.

I sometimes write scripts to generate platform-dependent files. But the scripts are always written by hand and documented, so I know what they do.

I admire Glenn Fowler's nmake and Phong Vo's iffe (if feature exists), which I think are better engineered than the GNU tools. But these tools are part of the AT&T Software Technology suite, and I haven't been able to figure out how to use them without buying into the whole AST way of doing things, which I don't always understand.

Your example

There clearly needs to be

extern char MasterSkipFile[];

in a .h file somewhere, and you can then link against a suitable .o.

The conditional inclusion of the "right set of .h files for the platform" is something I would handle by trying to stick to ANSI C when possible, and when not possible, defining a compatibility layer in a platform-specific .h file. As it is, I can't tell what names the #includes are trying to import, so I can't give more specific advice.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文