Автоматически генерировать таблицу указателей на функции в C - PullRequest
5 голосов
/ 20 марта 2010

Я ищу способ автоматически (как часть процесса компиляции / сборки) генерировать «таблицу» указателей на функции в 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 определены в других исходных файлах.

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

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

Но почему?

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

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

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

Ответы [ 6 ]

6 голосов
/ 20 марта 2010

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

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

#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;
}
3 голосов
/ 20 марта 2010

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

{ .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 голосов
/ 20 марта 2010

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

dumpbin /symbols  foo.obj > foo_symbols.txt

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

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

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

1 голос
/ 20 марта 2010

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

Вы могли бы использовать «семантический макрос» в стиле 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-liner), sort (1), затем Фильтр генерации (я бы использовал здесь awk). Однако вам придется сгенерировать верхний / нижний колонтитул отдельно и сделать два прохода или сохранить результат извлечения во временном файле, чтобы сгенерировать как внешние объявления, так и записи массива.

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

Все, что осталось сделать, - это добавить этот этап генерации в ваш make-файл, как в ответе 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} >>$@
0 голосов
/ 20 марта 2010

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

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

Просто идея.

0 голосов
/ 20 марта 2010

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

Редактировать: Сложность заключается в извлечении информации («есть функция с именем« func1 () »)) из исходного кода. Требуется парсинг исходного файла C. Компилятор C уже делает это (это его работа), поэтому имеет смысл использовать выходные данные компилятора C. Результатом является объектный файл с таблицей символов, которая содержит эту информацию. Таким образом, идея состоит в том, чтобы проанализировать вывод nm и сгенерировать исходный файл C, который определяет «таблицу функций». Это можно автоматизировать из make-файла, чтобы таблица всегда создавалась.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...