Поддержка отражений в Си - PullRequest
       5

Поддержка отражений в Си

27 голосов
/ 30 августа 2009

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

Ответы [ 10 ]

18 голосов
/ 30 августа 2009

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

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

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

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

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

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

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

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

7 голосов
/ 30 августа 2009

какие-нибудь хитрости вокруг этого? Любые советы?

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

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

5 голосов
/ 30 августа 2009

На основе ответов на Как добавить отражение в приложение C ++? (Переполнение стека) и тот факт, что C ++ считается «надмножеством» C, я бы сказал, что вам не повезло.

Есть также хороший длинный ответ о , почему в C ++ нет отражения (переполнение стека) .

4 голосов
/ 09 августа 2015

Советы и хитрости всегда существуют. Взгляните на библиотеку 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.

3 голосов
/ 30 августа 2009

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

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

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

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

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

1 голос
/ 07 марта 2015

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

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

Я буду использовать немного платформ модульных тестов в качестве примеров ниже, потому что автоматическое обнаружение тестов для платформ модульных тестов является типичным примером, когда рефлексия очень удобна, и это то, что большинству платформ модульных тестов для 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 и библиотек, которые они используют, чтобы узнать, как они делают то, что делают.

Вовлечение парсера

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

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

«Злоупотребление» компоновщиком для генерации символьных массивов

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

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

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

Ловушки

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

Заключение

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

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

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

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

0 голосов
/ 19 октября 2018

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

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

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

0 голосов
/ 07 сентября 2017

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

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

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

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