Как условно определить, какие функции вызываются во время компиляции?

Я работаю над реализацией очень, очень базовая компонентная система в 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()

таким образом компоненты могут быть легко добавлены в будущем. Я не возражаю, если проверка выполняется во время компиляции или во время выполнения (хотя я, очевидно, предпочитаю время компиляции, но я могу представить, что время выполнения проще реализовать). Я понятия не имею, с чего начать.

9 ответов


во время компиляции с макросом X:


вам нужны указатели на функции, создать массив указателей на функции и подключать ее динамически.

здесь ссылке про указатели на функции.


решение времени компиляции: шаг предварительной сборки и включить директиву внутри этого цикла, например

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

любая приличная система сборки позволит вам это сделать.


указатели на функции отлично!

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 макрос, который я узнал от Дэйва Хэнсона, также является одним из моих любимых.


С. П. Избежать #ifdef любой ценой :-)


что не так с условием ol ' if?

if (hasNetwork)
{
   network_process();
}

вы можете сделать это с помощью массива указателей на функции.Обычно я стараюсь избегать указателей функций, таких как чума, но это может быть ваш лучший выбор.

кроме того, вы можете создать функцию процесса компонента, которая принимает аргумент int, а затем имеет неприятный оператор switch... но для этого вам нужно продолжать добавлять функцию component_process.

альтернативно-альтернативно, вы можете сделать это в C++, создать класс виртуального компонента, который просто имеет один метод "process", с кучей подклассов, и вы запускаете массив компонентов (фактически объекты подклассов) и вызываете метод process.


ваши компоненты должны быть массивом указателей на функции

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]();

вот пример синтаксиса, чтобы сделать это во время выполнения с массивом указателей функций:


void f( void ) { puts( "f" ); }
void g( void ) { puts( "g" ); }
void h( void ) { puts( "h" ); }
void (*array[])(void) = { f, h, 0 };
int main(void) {
    void (**t)(void);
    for( t = array; *t; t++ )
        (*t)();
}



другая возможность: держите цикл как есть, т. е.

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