Это довольно часто встречается при написании тестового кода. например, вы хотите вызвать все функции, которые начинаются с test_. Таким образом, у вас есть сценарий оболочки, который grep просматривает все ваши файлы .C и извлекает имена функций, которые соответствуют test _. *. Затем этот скрипт генерирует файл test.c, который содержит функцию, которая вызывает все функции теста.
Например, сгенерированная программа будет выглядеть так:
int main() {
initTestCode();
testA();
testB();
testC();
}
Еще один способ сделать это - использовать некоторые трюки с линкерами. Это то, что ядро Linux делает для своей инициализации. Функции, которые являются кодом инициализации, отмечены квалификатором __init. Это определяется в linux / init.h следующим образом:
#define __init __section(.init.text) __cold notrace
Это заставляет компоновщик поместить эту функцию в раздел .init.text. Ядро будет восстанавливать память из этого раздела после загрузки системы.
Для вызова функций каждый модуль объявляет функцию initcall с некоторыми другими макросами core_initcall (func), arch_initcall (func) и так далее (также определенными в linux / init.h). Эти макросы помещают указатель на функцию в секцию компоновщика с именем .initcall.
Во время загрузки ядро будет "проходить" через раздел .initcall, вызывая все указатели там. Код, который просматривается, выглядит следующим образом:
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
Символы __initcall_start, __initcall_end и т. Д. Определяются в сценарии компоновщика.
В целом, ядро Linux делает некоторые из самых хитрых трюков с препроцессором, компилятором и компоновщиком GCC, которые возможны. Это всегда было отличным ориентиром для трюков на Си.