Инициализировать глобальный массив указателей на функции во время компиляции или во время выполнения перед main () - PullRequest
4 голосов
/ 11 ноября 2010

Я пытаюсь инициализировать глобальный массив указателей на функции во время компиляции, в C или C ++. Примерно так:

module.h

typedef int16_t (*myfunc_t)(void);
extern myfunc_array[];

module.cpp

#include "module.h"
int16_t myfunc_1();
int16_t myfunc_2();
...
int16_t myfunc_N();

// the ordering of functions is not that important
myfunc_array[] = { myfunc_1, myfunc_2, ... , myfunc_N };

func1.cpp, func2.cpp, ... funcN.cpp (символические ссылки на один файл func.cpp для создания различных объектных файлов: func1.o, func2.o, func3.o, ..., funcN.o. NUMBER определяется с помощью g++ -DNUMBER=N)

#include "module.h"
#define CONCAT2(x, y) x ## y
#define CONCAT(x, y) CONCAT2(x, y)

int16_t CONCAT(myfunc_, NUMBER)() { ... }

При компиляции с использованием g ++ -DNUMBER = N, после предварительной обработки становится:

func1.cpp

...
int16_t myfunc_1() { ... }

func2.cpp

...
int16_t myfunc_2() { ... }

и т. Д.

Объявления myfunc_N() и инициализация myfunc_array[] не являются крутыми, поскольку N часто изменяется и может составлять от 10 до 200. Я предпочитаю не использовать скрипт или Makefile для их генерации. Порядок функций не так важен, я могу обойти это. Есть ли более аккуратный / умный способ сделать это?

Ответы [ 5 ]

18 голосов
/ 11 ноября 2010

Как создать реестр функций низкого уровня

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

/* original typedef from question: */
typedef int16_t (*myfunc)(void);

#define myfunc_register(N) \
    static myfunc registered_##myfunc_##N \
      __attribute__((__section__(".myfunc_registry"))) = myfunc_##N

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

myfunc_register(NUMBER);

Теперь, когда вы компилируете свой файл (каждый раз), он будет иметь указатель на вашу функцию в разделе .myfunc_registry. Все это будет скомпилировано как есть, но без скрипта компоновщика ничего хорошего не получится. Спасибо caf за указание на относительно новую функцию INSERT AFTER:

SECTIONS
{
    .rel.rodata.myfunc_registry : {
        PROVIDE(myfunc_registry_start = .);
        *(.myfunc_registry)
        PROVIDE(myfunc_registry_end = .);
    }
}
INSERT AFTER .text;

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

Связь с gcc -Wl,-Tlinkerscript.ld ... Опция -T улучшит (а не заменит) существующий скрипт компоновщика.

Теперь компоновщик соберет все ваши указатели с атрибутом section вместе и услужливо предоставит символ, указывающий до и после вашего списка:

extern myfunc myfunc_registry_start[], myfunc_registry_end[];

Теперь вы можете получить доступ к вашему массиву:

/* this cannot be static because it is not know at compile time */
size_t myfunc_registry_size = (myfunc_registry_end - myfunc_registry_start);
int i;

for (i = 0; i < myfunc_registry_size); ++i)
    (*myfunc_registry_start[i])();

Они не будут в каком-то определенном порядке. Их можно пронумеровать, поместив их в __section__(".myfunc_registry." #N), а затем в сборщик компоновщиков *(.myfunc_registry.*), но сортировка будет лексографической, а не числовой.

Я проверил это с gcc 4.3.0 (хотя части gcc были доступны в течение долгого времени) и ld 2.18.50 (вам нужен довольно свежий ld для магии INSERT AFTER).

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

Вы можете найти примеры этого в ядре Linux, например, __initcall очень похоже на это.

0 голосов
/ 11 ноября 2010

Хорошо, я разработал решение на основе совета Мэтта Джойнера:

module.h

typedef int16_t (*myfunc_t)(void);
extern myfunc_array[];

class FunctionRegistrar {
public:
    FunctionRegistrar(myfunc_t fn, int fn_number) {
        myfunc_array[fn_number - 1] = fn; // ensures correct ordering of functions (not that important though)
    }
}

module.cpp

#include "module.h"

myfunc_array[100]; // The size needs to be #defined by the compiler, probably

func1.cpp, func2.cpp, ... funcN.cpp

#include "module.h"

static int16_t myfunc(void) { ... }

static FunctionRegistrar functionRegistrar(myfunc, NUMBER);

Спасибо всем!

0 голосов
/ 11 ноября 2010

Поскольку вы разрешаете C ++, ответ, очевидно, да, с шаблонами:

template<int N> int16_t myfunc() { /* N is a const int here */ }
myfunc_array[] = { myfunc<0>, myfunc<1>, myfunc<2> }

Теперь вы можете задаться вопросом, можете ли вы создать этот список инициализатора переменной длины с некоторым макросом.Ответ - да, но макрос нужен безобразно.Так что я не буду их здесь писать, но укажу на Boost :: Preprocessor

Однако, вам действительно нужен такой массив?Вам действительно нужно имя myfunc_array[0] для myfunc<0>?Даже если вам нужен аргумент времени выполнения (myfunc_array[i]), есть другие приемы:

inline template <int Nmax> int16_t myfunc_wrapper(int i) {
assert (i<Nmax);
return (i==Nmax) ? myfunc<Nmax> : myfunc_wrapper(i-1);
}
inline int16_t myfunc_wrapper(int i) { 
  return myfunc_wrapper<NUMBER>(i); // NUMBER is defined on with g++ -DNUMBER=N
}
0 голосов
/ 11 ноября 2010

Ваше решение звучит слишком сложно и подвержено ошибкам.

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

Я бы использовал собственные имена для всех функций, ничего из вашей схемы нумерации, а затем я бы создал файл "module.cpp" с этим сценарием и инициализировал таблицу с именами.

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

Редактировать: Думая об этом, даже это может быть чрезмерной инженерией. Если в любом случае вам нужно вести список своих функций где-нибудь, почему бы не просто внутри файла "module.cpp"? Просто включите туда все заголовочные файлы всех ваших функций и перечислите их в инициализаторе таблицы.

0 голосов
/ 11 ноября 2010

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

Вы можете сделать myfunc_array вектором или обернуть эквивалент C и предоставить функцию для вставки myfunc s в него. Теперь, наконец, вы можете создать класс (снова вы можете сделать это в C), который берет myfunc и помещает его в глобальный массив. Все это произойдет непосредственно перед вызовом основного. Вот некоторые фрагменты кода, которые заставят вас задуматься:

// a header

extern vector<myfunc> myfunc_array;

struct _register_myfunc { 
    _register_myfunc(myfunc lolz0rs) {
        myfunc_array.push_back(lolz0rs);
    }
}

#define register_myfunc(lolz0rs) static _register_myfunc _unique_name(lolz0rs);

// a source

vector<myfunc> myfunc_array;

// another source

int16_t myfunc_1() { ... }
register_myfunc(myfunc_1);

// another source

int16_t myfunc_2() { ... }
register_myfunc(myfunc_2);

Имейте в виду следующее:

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