Поддержка отражения в C

Я знаю, что это не поддерживается, но мне интересно, есть ли у него какие-нибудь трюки. Любые советы?

Ответ 1

Отражение в целом - это средство для программы проанализировать структуру некоторого кода. Этот анализ используется для изменения эффективного поведения кода.

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

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

Необходим инструмент, который:

  • Разбирает язык исходного текста
  • Создает абстрактные синтаксические деревья, представляющие каждую деталь программы. (Полезно, если AST сохраняют комментарии и другие детали источника  расположение кода, например номера столбцов, буквенные значения осей и т.д.)
  • Создает таблицы символов, показывающие объем и значение каждого идентификатора
  • Может извлекать потоки управления из функций
  • Может вывести поток данных из кода
  • Можно построить граф вызовов для системы
  • Можно определить, на что указывает каждый указатель
  • Позволяет создавать собственные анализаторы, используя приведенные выше факты.
  • Может преобразовать код в соответствии с такими пользовательскими анализами (обычно путем пересмотра AST, которые представляют проанализированный код)
  • Может восстановить исходный текст (включая макет и комментарии) из пересмотренные AST.

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

  • Уровень детализации или объем анализа являются предметом амбиций (например, это не ограничено тем, что может делать только отражение во время выполнения)
  • Для достижения отраженного изменения в поведении не требуется никаких затрат времени выполнения
  • Используемый механизм может быть общим и применяться на многих языках, а чем ограничиваться тем, что обеспечивает конкретная языковая реализация.
  • Это совместимо с идеей C/C++, согласно которой вы не платите за то, что не используете. Если вам не нужно отражение, вам не нужен этот механизм. И твой язык не нужно иметь интеллектуальный багаж слабого отражения.

См. наш инструментарий реинжиниринга программного обеспечения DMS для получения информации о системе, которая может выполнять все вышеперечисленное для C, Java и COBOL, и большую часть для C++.

[ОБНОВЛЕНИЕ Август 2017: теперь обрабатывает C11 и C++ 2017]

Ответ 2

любые трюки вокруг него? Любые советы?

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

Трюк/подсказка может состоять в том, чтобы сгенерировать, а затем прочитать эти файлы.

Ответ 4

Советы и хитрости всегда существует. Взгляните на библиотеку Metaresc https://github.com/alexanderchuranov/Metaresc

Он предоставляет интерфейс для объявления типов, который также будет генерировать метаданные для типа. На основе метаданных вы можете легко сериализовать/десериализовать объекты любой сложности. Из коробки вы можете сериализовать/десериализовать XML, JSON, XDR, Lisp-подобную нотацию, нотацию C-init.

Вот простой пример:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "metaresc.h"

TYPEDEF_STRUCT (point_t,
                double x,
                double y
                );

int main (int argc, char * argv[])
{
  point_t point = {
    .x = M_PI,
    .y = M_E,
  };
  char * str = MR_SAVE_XML (point_t, &point);
  if (str)
    {
      printf ("%s\n", str);
      free (str);
    }
  return (EXIT_SUCCESS);
}

Эта программа выведет

$ ./point
<?xml version="1.0"?>
<point>
  <x>3.1415926535897931</x>
  <y>2.7182818284590451</y>
</point>

Библиотека прекрасно работает для последних gcc и clang.

Ответ 5

  • Реализация отражения для C будет намного проще... потому что C - простой язык.
  • Есть несколько базовых вариантов для программы-анализа, например, если функция существует, вызывая dlopen/dlsym - зависит от ваших потребностей.
  • Существуют инструменты для создания кода, который может изменять/расширять использование tcc.
  • Вы можете использовать вышеуказанный инструмент, чтобы создать свои собственные анализаторы кода.

Ответ 6

Мне нужно отражение в связке struct в проекте С++.
Я создал xml файл с описанием всех этих структур - к счастью, типы полей были примитивными типами.
Я использовал шаблон (а не С++ template) для автоматического создания class для каждого struct вместе с методами setter/getter.
В каждом class я использовал карту для связывания имен строк и членов класса (указатели на члены).

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

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

Ответ 7

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

Ответ 8

Я знаю следующие варианты, но все они стоят дорого и имеют множество ограничений:

  • Используйте libdl (#include <dfcln.h>)
  • Вызовите инструмент, например objdump или nm
  • Разбирайте объектные файлы самостоятельно (используя соответствующую библиотеку)
  • Привлекать парсер и генерировать необходимую информацию во время компиляции.
  • "Злоупотребление" компоновщиком для создания массивов символов.

Я буду использовать несколько фреймворков unit test в качестве примеров ниже, потому что автоматическое обнаружение тестов для фреймворков unit test является типичным примером, где отражение очень удобно, и это то, что большинство unit test фреймворков для C не хватает.

Использование libdl (#include <dfcln.h>) (POSIX)

Если вы находитесь в среде POSIX, можно немного отразить, используя libdl. Плагины разработаны таким образом.

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

#include <dfcln.h>

в исходном коде и ссылку с -ldl.

Затем у вас есть доступ к функциям dlopen(), dlerror(), dlsym() и dlclose(), с помощью которых вы можете загружать и запускать общие объекты во время выполнения. Однако это не дает вам легкого доступа к таблице символов.

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

Запуск nm или objdump

Вы можете запустить nm или objdump, чтобы отобразить таблицу символов и проанализировать вывод. Для меня nm -P --defined-only -g xyz.o дает хорошие результаты, а синтаксический анализ результатов тривиален. Вас будет интересовать первое слово каждой строки, которое является именем символа, и, возможно, второе, которое является типом раздела.

Если вы не знаете имя объекта каким-то статическим способом, т.е. объект на самом деле является общим объектом, по крайней мере на Linux, вам может понадобиться пропустить имена символов, начинающиеся с "_".

objdump, nm или аналогичные инструменты также часто доступны вне среды POSIX.

Анализ самих объектных файлов

Вы можете сами проанализировать объектные файлы. Вероятно, вы не хотите реализовывать это с нуля, но для этого используйте существующую библиотеку. Вот как реализованы nm, objdump и даже libdl. Вы можете заглянуть в исходный код nm, objdump и libdl и библиотеки, которые они используют, чтобы узнать, как они делают то, что они делают.

Привлечение анализатора

Вы можете написать генератор парсера и кода, который генерирует необходимую отражающую информацию во время компиляции и сохраняет ее в объектном файле. Тогда у вас есть много свободы и даже можно реализовать примитивные формы аннотаций. Это то, что некоторые unit test фреймворки вроде AceUnit делают.

Я обнаружил, что написание парсера, который содержит прямой синтаксис С, довольно тривиально. Написание парсера, который действительно понимает C и может иметь дело со всеми делами, не является тривиальным. Таким образом, это имеет ограничения, которые зависят от того, насколько экзотическим является синтаксис C, на который вы хотите задуматься.

"Нарушение" компоновщика для создания массивов символов

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

Я описал здесь инъекцию N-зависимостей в C - лучше, чем линеарно-определенные массивы?, как это работает.

Но будьте осторожны, это зависит от многих вещей и не очень портативных. Я только пробовал это с помощью GCC/ld, и я знаю, что он не работает со всеми компиляторами/линкерами. Кроме того, он почти гарантировал, что удаление мертвого кода не будет определять, как вы это называете, поэтому, если вы используете исключение мертвого кода, вам нужно будет добавить все отраженные символы в качестве точек входа.

Ловушки

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

Заключение

Объединение nm и libdl может дать неплохие результаты. Комбинация может быть почти такой же мощной, как уровень Reflection, используемый JUnit 3.x в Java. Уровень рефлексии достаточен для реализации фреймворка unit test JUnit 3.x для C, включая обнаружение тестового случая по соглашению об именах.

Вовлечение парсера - это больше работы и ограничено объектами, которые вы компилируете самостоятельно, но дает вам максимальную власть и свободу. Уровень отражения может быть достаточным для реализации фреймворка unit test JUnit 4.x для C, включая обнаружение тестовых случаев аннотациями. AceUnit - это фреймворк unit test для C, который выполняет именно это.

Объединение анализа и компоновщика для создания массивов символов может дать очень приятные результаты - если ваша среда находится под вашим контролем, вы можете гарантировать, что работа с компоновщиком будет работать для вас.

И, конечно же, вы можете комбинировать все подходы, чтобы сшить кусочки и кусочки, пока они не соответствуют вашим потребностям.

Ответ 9

Просто сделайте любую таблицу с ключом, дерево, связанный список или любую коллекцию, которую вам удобнее управлять или считаете эффективной. Добавьте ключ, будь то строка, тип/идентификатор combo или whatnot и укажите адрес функции или структуры. Супер наивная версия отражения может быть набором следующих:

struct reflectable{
    size_t size,id,type; // describes payload
    char* name;
    void* payload;
}

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

Ответ 10

Парсеры и символы отладки - отличные идеи. Тем не менее, проблема в том, что C на самом деле не имеет массивов. Просто указатели на вещи.

Например, невозможно прочитать исходный код, чтобы узнать, указывает ли символ * на символ, строку или фиксированный массив байтов на основе некоторого "близлежащего" поля длины. Это проблема для читателей, не говоря уже о любом автоматизированном инструменте.

Почему бы не использовать современный язык, такой как Java или .Net? Может быть быстрее, чем C.