【问题标题】:How to conditionally determine which functions are called at compile time?如何有条件地确定在编译时调用了哪些函数?
【发布时间】:2009-12-03 15:50:15
【问题描述】:

我正致力于在 C 中实现一个非常、非常 的基本组件系统,但现在我想“动态”调用一些函数.设置非常简单:主程序只是一个无限的 while 循环,其中检查一些条件并为每个启用的组件调用一个“进程”函数。

例如,现在它是这样工作的:

while (1) {
  input_process();
  network_process();
  graphics_process();
}

但我想将它分成单独的组件,并以某种方式在中心位置定义使用哪些部分。这可以通过简单的定义来完成,如下所示:

#define HAS_NETWORK
...
while (1) {
  input_process();
#ifdef HAS_NETWORK
  network_process();
#endif
  graphics_process();
}

正如您所看到的,这对于 1 个或可能只有几个组件是可以的,但如果我想在未来对所有这些(输入、网络和图形)和其他组件执行此操作,我将不得不单独放置#ifdefs 每个都在里面,这很乏味。

在伪代码中,我想要完成的是:

components = {'input', 'network', 'graphics'}
...
foreach component in components
  execute component_process()

这样以后可以很容易地添加组件。 我真的不介意检查是在编译时还是运行时完成(虽然我显然更喜欢编译时,但我可以想象运行时更容易实现)。我什至不知道如何开始。

【问题讨论】:

  • 为什么投反对票?我认为这是一个完全合理的问题。

标签: c function dynamic c-preprocessor


【解决方案1】:

您需要指向函数的指针,创建指向函数的指针数组并动态索引它。

这里link关于函数指针。

【讨论】:

  • 这是 IMO 的最佳解决方案。至少,这正是我用函数式语言处理这种情况的方式。
  • 是的,在函数语言中,答案显然是函数列表。在 OO 语言中,答案显然是带有 process() 方法的对象数组。在结构化语言中,我认为它不太清楚......但话又说回来,我对 C 中的函数指针有一种非理性的厌恶......(请记住,在 Scheme 中爱它们,这只是我讨厌的 C 语法)
【解决方案2】:

编译时解决方案:预构建步骤并在该循环内包含指令,例如

while (1) {
#include "components.generated.c"
}

生成该文件的基本脚本可能类似于 (Python):

components = ('input', 'networking', 'graphics')
# this could also be e.g. a glob on a directory or a config file

with open('components.generated.c', 'w') as fp:
    for component in components:
        print >>fp, '%s_process();' % component

任何体面的构建系统都可以让你做到这一点。

【讨论】:

  • 一方面,这很酷。另一方面,脚本生成的代码#included 到了中间?哇。我无法确定这个解决方案是真棒还是糟糕。 B-)
  • 好吧,您可以将整个循环生成为一个单独的函数,然后从真实代码中编译、链接和调用它。
  • 简直太棒了!如果您不同意,那么您需要阅读“实用程序员”。特别是关于“编写代码的代码”部分。
  • 编写代码的问题在于,每当您需要进行微小的更改时,您都必须更改整个脚本。
  • 更改脚本和更改任何其他代码有什么不同?
【解决方案3】:

ol' if 条件有什么问题?

if (hasNetwork)
{
   network_process();
}

【讨论】:

  • 不适用于很多不同的组件。至少不是没有一个巨大的 if-then-else 结构或开关......只有一个组件,#if 工作得一样好......(它在编译时是已知的,所以运行时检查没有真正的理由取决于优化器的智能程度,可能会或可能不会被优化掉......)
  • if(hasNetwork) network_process(); if(hasSomething) something_process();与插入一行相比,编写所有这些过程需要很长时间。
【解决方案4】:

函数指针很棒!

typedef void (*process_fun)(void);

process_fun processes[] = 
         { input_process, network_process, graphics_process };

#define NELEMS(A) (sizeof(A) / sizeof((A)[0]))

while (1) {
  for (int i = 0; i < NELEMS(processes); i++)
    processes[i]();
}

NELEMS 宏是我从 Dave Hanson 那里学来的,也是我的最爱之一。


附:不惜一切代价避免#ifdef :-)

【讨论】:

  • 我很感兴趣,为什么要不惜一切代价避免#ifed? :)
  • 此外,在下面,Nikola Smiljanić 使用与号 (&) 将元素放入数组中,据我所知这是正确的,因为您想获取 / 指向函数的指针的地址。这是正确的,还是两者都有效?
  • 关于 & 符号:这是 C 的规则,当名称或数组或函数出现在右值上下文中时(即预期值时的上下文,例如在右侧= 符号),编译器将名称解释为代表数组或函数的 地址,即使没有包含显式 &。 (你可以说“编译器自动插入 &”,这是正确的想法,但有点草率。)我相信这个特性是在他们制定 1989 年 ANSI 标准时添加到 C 中的,但我不确定。很方便。
  • @pbean:我忍不住提出了#ifdef 问题:stackoverflow.com/questions/1851181。我相信会有很多有趣的答案。我自己写一个太累了:-)
  • 很抱歉,您完全错过了条件部分。即使这个答案有点用(NELEMS当然很棒),我也会试着强迫你再看一眼。我很乐意尽快恢复我的 [-1] :)
【解决方案5】:

在编译时使用 X 宏:

component.x 是一个包含 :

的文件
COMPONENT( graphic , "3D graphics rendering" )
COMPONENT( network , "Network" )
COMPONENT( other , "usefull stuff" )
#undef COMPONENT

使用它:

#define COMPONENT( what , description ) what ## _process();
while (1)
{
#include "components.x"
}

例如在另一个地方:

std::cout << "We use :\n" ;
#define COMPONENT( what , description )\
std::cout << #what << " : " << description << "\n" ;
#include "components.x"

这样您就可以将 HAS_ 定义放在 component.x 中的一个位置:

#ifdef HAS_GRAPHIC
COMPONENT( graphic , "3D graphics rendering" )
#endif
#ifdef HAS_NETWORK
COMPONENT( network , "Network" )
#endif
#ifdef HAS_OTHER
COMPONENT( other , "usefull stuff" )
#endif
#undef COMPONENT

【讨论】:

  • 这看起来很有趣......只需编辑 component.x 文件,它就会神奇地工作。以上是一些反对在代码中间包含文件的cmets,但为什么会不好呢?
  • 今天有点忘记了,但这是一个非常古老且众所周知的成语,您可以查看此以获取更多信息:en.wikipedia.org/wiki/C_preprocessor#X-Macrosddj.com/cpp/184401387
  • 我明白了!如果你问我,这个成语真的很天才!我测试了一下,似乎正是我想要的。但是还有一个问题:我也可以有条件地#include 吗?例如,如果我有需要特殊标题的组件“输入”和“网络”,我可以以同样的方式包含 input.h 和 network.h 吗?我认为不是,因为它们都是预处理器步骤。
  • 你不能在宏定义中使用#,所以你不能用这个#include。
【解决方案6】:

你可以用函数指针数组来做到这一点。一般我尽量避免像瘟疫一样的函数指针,但这可能是你最好的选择。

或者,您可以创建一个采用 int 参数的组件流程函数,然后使用令人讨厌的 switch 语句...但是要使其正常工作,您需要不断添加到 component_process 函数。

或者,您可以在 C++ 中执行此操作,创建一个虚拟组件类,它只有一个方法“进程”,带有一堆子类,然后您运行一组组件(实际上是子类的对象)并调用 process 方法。

【讨论】:

    【解决方案7】:

    你的组件应该是一个指向函数的指针数组

    enum components
    {
        input,
        network,
        graphics,
        num_components
    }
    
    void process_audio()
    {
    }
    
    void process_network()
    {
    }
    
    void process_graphics()
    {
    }
    
    void (*process_component[num_components])();
    
    process_component[0] = &process_audio;
    process_component[1] = &process_network
    process_component[2] = &process_graphics;
    
    for (int i = 0; i < num_components; i++)
        process_component[i]();
    

    【讨论】:

    • components 不是数组。如果它是一个数组,它就不是一个函数指针数组。这根本不可移植,因为不能保证枚举是从 0 开始的连续整数。
    • @Brian:在 C 和 C++ 中,枚举 保证是从零开始的连续整数(当然,除非您指定值)。否则你是对的......
    • @Jerry 我认为它依赖于编译器,但最终它们总是......不能保证,但仍然永远不会错误......再说一次,我没有看过规范一段时间,所以我可能记错了。
    • 我的错误,我为枚举和函数指针数组使用了相同的名称。
    【解决方案8】:

    以下是在运行时使用函数指针数组执行此操作的语法示例:

    无效f(无效){put(“f”); } 无效g(无效){put(“g”); } 无效h(无效){put(“h”); } void (*array[])(void) = { f, h, 0 }; 诠释主要(无效){ 无效(**t)(无效); for( t = 数组; *t; t++ ) (*t)(); }

    【讨论】:

      【解决方案9】:

      另一种可能性:保持循环不变,即

      while (1) {
        input_process();
        network_process();
        graphics_process();
      }
      

      并将以下预处理器指令添加到文件头:

      #ifndef HAS_NETWORK
      #define network_process() ((void)0)
      #endif
      
      #ifndef HAS_GRAPHICS
      #define graphics_process() ((void)0)
      #endif
      

      【讨论】:

      • 为此,您需要事先提供可能组件的完整列表,并将其包含在 while 循环中?那时你不能只在一个地方引入一个新组件,而必须在不同的地方引入它。
      猜你喜欢
      • 2017-05-11
      • 2013-10-23
      • 1970-01-01
      • 2020-04-24
      • 2020-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      相关资源
      最近更新 更多