Возможно, вам необходимо понять, что такое замыкание с и обратный вызов с. Функциональные указатели полезны, но могут быть недостаточны сами по себе (читайте больше, чтобы понять, как функциональные указатели полезны для реализации замыканий).
Вы должны больше узнать о Си, поэтому читайте хорошие книги по программированию на Си.Я рекомендую загрузить C11 стандарт n1570 и взглянуть на него.Очень важным понятием является неопределенное поведение , и вы должны быть напуганы .
, если возможно каким-либо образом вычислить (например, в массиве char, но также любым другим способом), который я добавлю и использую позже.Но не как строка, а как код.
Это невозможно в чисто стандартном C-коде (поскольку набор переводных единиц , составляющих вашу программу, фиксирован), и в принципе любое допустимое значение указателя на функцию должно указывать на некоторое существующая функция (поэтому stricto sensu вызов любого другого значения указателя на функцию будет неопределенным поведением).Тем не менее, некоторые реализации могут создавать (каким-то образом, специфичным для реализации), каким-то образом "допустимые" новые указатели функций (но это вне стандарта C11).На чисто Гарвардской архитектуре с кодом, находящимся в ПЗУ, это даже невозможно.
создание и динамическая загрузка плагинов
Однако, если вы работаете под современным операционная система (я рекомендую Linux) вы можете использовать динамическую загрузку и плагин .Я сосредотачиваюсь именно на Linux (для Windows детали очень разные, а зло в деталях; прочитайте книгу Левина Линкеры и загрузчики ).Прочитайте Операционные системы: три простых компонента , чтобы узнать больше об ОС.
Итак, что вы можете сделать (в Linux), это сгенерировать во время выполнения некоторый C-код в некоторыхвременный файл, такой как /tmp/generated.c
;вам нужно определить свои соглашения относительно этого файла (например, что он определяет функцию void pluginfun(int)
, имеющую некоторые дополнительные желательные свойства);затем вы разбираете команду компиляции, чтобы скомпилировать ее в общий объект (см. Как писать общие библиотеки от Drepper), то есть плагин.Таким образом, ваша программа может работать (возможно, используя system (3) или системные вызовы более низкого уровня, такие как fork (2) , execve (2) , waitpid (2) и т.д ....) процесс компиляции, например, gcc -Wall -O -fPIC /tmp/generated.c -shared -o /tmp/generatedplugin.so
;позже ваша основная программа загрузит этот плагин, используя dlopen (3) :
void* dlh = dlopen("/tmp/generatedplugin.so", RTLD_NOW);
if (!dlh) {
fprintf(stderr, "dlopen /tmp/generatedplugin.so failed: %s\n",
dlerror());
exit(EXIT_FAILURE);
}
Поскольку вы заботитесь о указателях функций, ваш код будет более читабельным, если вы объявите их подпись каквведите:
typedef void pluginfun_type(int);
Затем указатель функции на функции этой сигнатуры будет легко объявлен:
pluginfun_type* fptr = NULL;
, и ваша программа может получить с помощью dlsym (3) pluginfun
адрес функции в вашем плагине:
fptr = (pluginfun_type*) dlsym(dlh, "pluginfun");
if (!fptr) {
fprintf(stderr, "dlsym of pluginfun failed: %s\n", dlerror());
exit(EXIT_FAILURE);
}
и, наконец, используйте (*fptr)(3)
для вызова этой функции.
Вы должны использовать gcc -O -Wall foo.o bar.o -rdynamic -ldl -o foobarprog
, чтобы связать вашу основную программу.Вы могли бы, наконец, вызвать dlclose (3) , но вам не следует этого делать, если у вас есть еще какой-то фрейм вызова для функции внутри вашего плагина.
Такой подход генерирования плагина работает очень хорошов линуксеMy manydl.c - игрушечная программа, генерирующая «случайный» код C, разветвляющая компиляцию этого сгенерированного кода C в сгенерированный плагин и загружающая этот плагин (и может повторяться много раз).Это показывает, что на практике программа Linux может генерировать и загружать многие сотни тысяч (и, возможно, даже миллионы, если вы достаточно терпеливы) плагины. сегменты кода утечка на практике приемлема (и ее можно избежать, если осторожно использовать dlclose
)
И компьютер сегодня довольно быстрый.Вы можете сгенерировать небольшой плагин (менее тысячи строк C) при каждом взаимодействии REPL , скомпилировать его и загрузить (для такого маленького плагина это обычно занимает менее 0,1 секунды), иэто практически совместимо с человеческим взаимодействием (я сделал это в моем устаревшем проекте GCC MELT ; я сделаю это в моем проекте bismon , описанном в bismon-chariot-doc.pdf черновик отчета).
с использованием библиотеки JIT-компиляции
Для динамического генерирования кода во время выполнения вы также можете использовать некоторую JIT-компиляцию библиотека.Их много, включая libgccjit , LLVM (на C ++), asmjit , libjit , GNU lightning , tinycc со своим libtcc
.Некоторые из них способны быстро генерировать машинный код, но тогда производительность этого кода может быть не очень хорошей.Другие делают оптимизации как хороший компилятор C (в частности libgccjit , который использует GCC внутри).Конечно, эти оптимизации занимают некоторое время, поэтому машинный код генерируется намного медленнее, но его производительность не уступает оптимизированному коду C.
Встраивание интерпретатора
Во многих случаяхязыки сценариев предлагают что-то вроде eval .Несколько интерпретаторов разработаны , чтобы быть легко встраиваемыми в ваше приложение (с предостережениями и предостережениями), в частности Lua или Guile (и Nim ).Многие другие интерпретаторы (Ocaml, Python, Perl, Parrot, Ruby, ...) также могут быть как-то встроены в ваше приложение (вам может понадобиться понять терминологию мусора ).Конечно, все требуют некоторых соглашений о кодировании.И интерпретаторы на практике работают медленнее, чем скомпилированный код.
Терминология
Может кто-нибудь сказать мне, если это возможно в C или любом другом языке, и как этоконцепция называется?
Возможно, вам нужно больше узнать о метапрограммировании , eval , многоступенчатом программировании , гомоизоничности , отражение , продолжения , тип самоанализа , трассировка стека (рассмотрим libbacktrace Яна Тейлора).
Вас могут заинтересовать Lisp -подобные языки.Сначала прочитайте SICP , затем книгу Квиннека Lisp In Small Pieces и книгу Скотта Прагматика языка программирования .Обратите внимание, что SBCL (реализация Common Lisp) генерирует машинный код при каждом взаимодействии REPL.
Поскольку вы упоминаете интерес к искусственному интеллекту, вы можете заглянуть в блог J.Pitrat.Его система CAIA загружена , поэтому генерирует полный код C (почти 500KLOC).