【问题标题】:C - alternative to #ifdefC - #ifdef 的替代品
【发布时间】:2010-12-10 14:03:16
【问题描述】:

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

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

这是我正在处理的代码的 sn-p

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

【问题讨论】:

  • 您使用的是哪种构建系统?我注意到 Linux、Unix 和 Windows 的定义,所以没有明显的候选者。如果不了解构建系统,将很难提供帮助。 FWIW,#ifdef 在多平台代码中至关重要,而且几乎不气馁,尽管您看到它可能会被糟糕地使用。
  • 同意 - 诀窍是使用它,而不是避免使用它。构建系统对此有很大帮助 - 例如,整个 autoconf 管道可以自动生成一个 config.h,您的所有代码都包含该 config.h 以获得适当的#defines。
  • 主要是在 *nix 上构建的,所以假设 makefile。这反过来将由 exec-maven-plugin 触发,因为这只是整体构建所涵盖的非常分散的代码库的一部分
  • 大家好 - 非常感谢您的 cmets 和建议,这就是我喜欢 Stackoverflow 的原因。在宣布获胜者之前,我会花一些时间进行分析和原型设计 :)

标签: c c-preprocessor conditional-compilation hardcoded


【解决方案1】:

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

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

然后通过

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

在命令行上。

【讨论】:

  • 我同意 MasterSkipFile 的定义应该与所示的行一致;不太令人满意的是将定义隐藏在构建控制文件中。最好有一个“config.h”文件(这是由 autoconf 创建的名称)或“porting.h”或 VCS 下的任何文件,并由 cscope 或 iDE 等程序扫描。我在一个系统上工作,其中有 60 个仅在 makefile 中出现的定义;很难确定源代码中的哪些#ifdef 仍然被实际定义,因为这些定义对 cscope 等是不可见的。
  • 我不会说命令行。它将是由外部构建过程(maven)触发的make something
  • @Jonathan 想说的是,自动生成的标头可能是您更好的解决方案。这是很有可能的,并且可能会清理复杂的 makefile 混乱。
  • 我的问题是 - 遗留代码最终会消失,所以我至少了解它,至少我修改它更好。我想我最终会结合命令行参数、预定义的特定于平台的 .h 文件和#ifdef(哦,好吧)
【解决方案2】:

使用起来更明智:

#if SOMETHING

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

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

【讨论】:

    【解决方案3】:

    我们曾经做的一件事是生成一个包含这些定义的.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 文件中。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

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

        #include PLATFORM_CONFIG
        #include BUILD_CONFIG
        

        编译器被启动:

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

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

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

        【讨论】:

          【解决方案6】:

          平台特定的配置头

          我有一个系统可以将特定于平台的配置生成到用于所有构建的标头中。 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 的地方是在 Makefiles 中,那么像 cscope 这样的工具就没有多大用处了——这使得在稍后尝试清理时修改代码更容易出错。

          【讨论】:

            【解决方案7】:

            一般性

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

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

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

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

            你的例子

            显然需要

            extern char MasterSkipFile[];
            

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-09-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多