Автоматически генерируйте таблицу указателей функции в C

Я ищу путь к автоматически (как часть компиляции/процесса сборки), генерируют "таблицу" указателей функции в C.

А именно, я хочу генерировать массив структур что-то как:

typedef struct {
  void (*p_func)(void);
  char * funcName;
} funcRecord;

/* Automatically generate the lines below: */

extern void func1(void);
extern void func2(void);
/* ... */

funcRecord funcTable[] =
{
  { .p_func = &func1, .funcName = "func1" },
  { .p_func = &func2, .funcName = "func2" }
  /* ... */
};

/* End automatically-generated code. */

... где func1 и func2 определяются в других исходных файлах.

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

Я понимаю, что это, вероятно, не достижимое использование языка C или одного только препроцессора, поэтому считайте любого распространенным *справедливая игра инструментов отклонять-стиля (например, сделайте, жемчуг, сценарии оболочки (если Вы имеете к)).

Но почему?

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

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

Надо надеяться, кто-то там сделал что-то вроде этого прежде. Спасибо, сообщество StackOverflow!

5
задан jeremytrimble 19 March 2010 в 21:54
поделиться

6 ответов

Использование макросов

Как насчет создания списка макросов в виде

#define FUNC_LIST \
  FUNC( func1 ) \
  FUNC( func2 ) \
  FUNC( func3 ) \
  FUNC( func4 ) \
  FUNC( func5 ) 

и затем расширения extern определений в виде

#define FUNC( _name ) extern void _name(void);

FUNC_LIST

#undef FUNC

и затем расширения таблицы в виде

#define FUNC( _name ) { .p_func = &_name, .funcName = #_name },

funcRecord funcTable[] = {
  FUNC_LIST
};

#undef FUNC

Использование dlsym(.... )

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

Пример

#include <stdio.h>
#include <dlfcn.h>

void test2() {
  printf("Second place is the first loser!\n");
}

void test42() {
  printf("Read The Hitchhikers Guide To The Galaxy!\n");
}

int main() {
  int i;
  for (i=1; i<100; i++) {
    char fname[32];
    void (*func)();
    sprintf(fname, "test%d", i);
    func = dlsym(RTLD_DEFAULT, fname);
    if (func)
      func();
  }
  return 0;
}
6
ответ дан 18 December 2019 в 14:44
поделиться

Я предпочитаю поступать иначе. Когда я что-то разрабатываю, я обычно думаю: правильный файл OK C с кодом в нем для такой функции, скажем, в /project-devel/project/new_function.c . Когда я пишу это, я обычно создаю /project-devel/tests/test_new_function.c и / project-devel / tests / make_new_function_test , которые, как вы уже догадались, создают тест. двоичный файл, включая все необходимое для тестирования этой новой функции.

Вопрос, наверное, в том, могли бы вы это автоматизировать? Я не собираюсь писать для него код (потому что я не думал об этом), но вы могли бы создать сценарий perl / python, который создает файл makefile, new function.c / .h и тестовый загрузчик за один раз. Вы можете определить нужные вам включения, выполнив регулярное выражение вроде r "#include \\\" (\ s +?). H \\\ "" и создав "скрипт обновления" тестовой функции, который повторно создает тестовый пример / make-файл на основе новой информации. Он по-прежнему полагается на то, что вы напишете реальный test.c, но это тривиально с учетом идеи.

Просто идея.

0
ответ дан 18 December 2019 в 14:44
поделиться

Если вы используете MSVC, вы можете использовать

dumpbin /symbols  foo.obj > foo_symbols.txt

, чтобы получить все имена символов (а не только функции) в текстовый файл. Затем проанализируйте полученный файл, чтобы извлечь имена функций. Функции будут Внешними символами в разделе, отличном от UNDEF

. Кроме того, вы можете связать свои объекты во временный exe или dll, а затем просмотреть файл .MAP, созданный компоновщиком для получить имена функций.

Или вы можете написать свой собственный код для анализа файлов .obj, они находятся в модифицированном формате COFF, и найти таблицу символов и декодировать ее не так уж и сложно. Я сделал частичное декодирование формата COFF один раз, чтобы добраться до таблицы символов, и мне потребовалось несколько дней, чтобы написать код. http://en.wikipedia.org/wiki/COFF

3
ответ дан 18 December 2019 в 14:44
поделиться

Это делает компилятор языка C. В системе Linux попробуйте вызвать nm foo.o на скомпилированном C-файле. Он выведет "таблицу символов", которая включает все функции, в том числе статические.

Edit: Сложность заключается в извлечении информации ("есть функция с именем 'func1()'") из исходного кода. Для этого нужно разобрать исходный файл на языке Си. Компилятор Си уже делает это (это его работа), поэтому имеет смысл использовать результат работы компилятора Си. На выходе получается объектный файл с таблицей символов, которая содержит эту информацию. Идея заключается в том, чтобы разобрать вывод nm и сгенерировать исходный файл C, определяющий "таблицу функций". Это можно автоматизировать из makefile, чтобы таблица генерировалась всегда.

0
ответ дан 18 December 2019 в 14:44
поделиться

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

{ .p_func = &functionname, .funcName = "functionname" },

Затем скажите системе сборки сгенерировать файл заголовка. В make это может выглядеть как

UTILITY_FUNCTION_HEADER:= func1.h func2.h func3.h
func_table.h: ${UTILITY_FUNCTION_HEADERS}
        write_header.sh > $@
        for file in @^; do extract_function_name.sh >> $@; done
        write_footer.sh >>$@
3
ответ дан 18 December 2019 в 14:44
поделиться

Что касается проблемы извлечения, я думаю, что я бы как-то пометил функции, которые я хочу экспортировать, а затем извлек бы их в процессе сборки.

Можно использовать "семантический макрос" а-ля gettext (т.е. макрос, который ничего не делает, кроме предоставления семантической информации внешним инструментам):

#define TEST_CASE(f) f

T TEST_CASE(f)(D x, ...)
{
        /* ... */
}

Затем вы можете легко извлечь эту информацию с помощью sed или awk, или что вы предпочитаете, и сделать список в правильном формате на основе этого. Вот простой код на awk, поскольку это то, что я знаю лучше всего, но вы можете использовать что-то другое:

match($0, /TEST_CASE\([a-zA-Z_][a-zA-Z_0-9]*\)/) {
        name = substr($0, RSTART, RLENGTH)
        sub(/^TEST_CASE\(/, "", name)
        sub(/\)$/, "", name)
        funcs[name]
}

END {
        for (f in funcs)
                printf "func_type %s;\n", f
        print "funcRecord funcTable[] = {"
        for (f in funcs)
                printf "\t{ .p_func = %s, .funcName = \"%s\" },\n", f, f
        print "};"
}

Если вы собираетесь сортировать имена (полезно для bsearch()-ing), я бы рекомендовал использовать три фильтра: фильтр извлечения (здесь подходит sed one-liner), sort(1), затем фильтр генерации (здесь я бы использовал awk). Однако вам придется отдельно генерировать заголовок/заглушку и делать два прохода или хранить результат извлечения во временном файле, чтобы сгенерировать и объявления extern, и записи массива.

Я не думаю, что это хорошая идея - пытаться извлечь функции с заданным прототипом, например, void (void). Лучше использовать typedef (func_type в моем примере) и явный семантический макрос, IMHO это более устойчиво (к изменениям, а также к различным стилям кодирования, например, размещение возвращаемого типа в строке самостоятельно или без него).

Все, что остается сделать, это добавить этот проход генерации в ваш makefile, как в ответе dmckee (хотя на самом деле вы захотите поместить весь этот сгенерированный код в .c, а не в .h, я думаю). Для полноты картины, вот моя версия:

TEST_SRCS=      test1.c test2.c test3.c

test_funcs.c: ${TEST_SRCS}
        echo '#include "test.h"' >$@
        awk -f extract_test_funcs.awk ${TEST_SRCS} >>$@
1
ответ дан 18 December 2019 в 14:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: