Как реализовать контракты интерфейса (в C) во время компиляции? - PullRequest
4 голосов
/ 26 октября 2010

Справочная информация:

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

Целевым языком будет C (C99, если быть точным).

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

При моделировании системы мы 'Мы определили набор четко определенных компонентов.Каждый компонент имеет свой собственный интерфейс, и многие из компонентов взаимодействуют со многими компонентами.

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

Поскольку советы / рекомендации могут зависеть от масштаба, я предоставлю некоторую информацию.Сейчас может быть около 12-15 компонентов, может вырасти до ~ 20?Не сотня компонентов.Скажем, в среднем каждый компонент взаимодействует с 25% других компонентов.

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

Вот в чем проблема: во многих случаях мы не хотим, чтобы «Компонент А» имел доступ к всем «Компонентам В»."интерфейс, т. е. мы хотим ограничить компонент A подмножеством интерфейса, предоставляемого компонентом B.

Вопрос / проблема:

Есть ли систематический, довольно простойспособ обеспечить - предпочтительно во время компиляции - контракты интерфейса, определенные на диаграмме компонентов?

Очевидно, что решения во время компиляции предпочтительнее решений во время выполнения (более раннее обнаружение, лучшая производительность, возможно меньший код).

Например, предположим, что компонент библиотеки "B" предоставляет функции X (), Y () и Z (), но я только хочу, чтобы компонент "A" мог вызывать функцию Z (), а неX () и Y ().Точно так же, хотя компонент «А» может быть способен принимать и обрабатывать целое множество различных сообщений через свою очередь сообщений, мы не имеем возможности отправлять какие-либо сообщения любому компоненту.

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

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

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

Код должен будет скомпилировать и аккуратно компилировать, поэтому подход «прототип функции межсетевого экрана» приемлемно кажется, что есть более идиоматический способ сделать это.

Ответы [ 3 ]

2 голосов
/ 27 октября 2010
#ifdef FOO_H_

   /* I considered allowing you to include this multiple times (probably indirectly)
      and have a new set of `#define`s switched on each time, but the interaction
      between that and the FOO_H_ got confusing. I don't doubt that there is a good
      way to accomplish that, but I decided not to worry with it right now. */

#warn foo.h included more than one time

#else /* FOO_H_ */

#include <message.h>

#ifdef FOO_COMPONENT_A

int foo_func1(int x);
static inline int foo_func2(message_t * msg) {
    return msg_send(foo, msg);
}
...

#else /* FOO_COMPONENT_A */

  /* Doing this will hopefully cause your compiler to spit out a message with
     an error that will provide a hint as to why using this function name is
     wrong. You might want to play around with your compiler (and maybe a few
     others) to see if there is a better illegal code for the body of the
     macros. */
#define foo_func1(x) ("foo_func1"=NULL)
#define foo_func2(x) ("foo_func2"=NULL)

...
#endif /* FOO_COMPONENT_A */

#ifdef FOO_COMPONENT_B

int foo_func3(int x);

#else /* FOO_COMPONENT_B */

#define foo_func3(x) ("foo_func3"=NULL)

#endif /* FOO_COMPONENT_B */
2 голосов
/ 27 октября 2010

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

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

1 голос
/ 27 октября 2010

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

Чтобы создать заголовок в этом ответе,как-то так (назовите это foo.comp):

[COMPONENT_A]
int foo_func1(int x);
static inline int foo_func2(message_t * msg) {
    return msg_send(foo, msg);
}

[COMPONENT_B]
int foo_func3(int x);

(и расширив пример, чтобы дать интерфейс, используемый несколькими компонентами):

[COMPONENT_B, COMPONENT_C]
int foo_func4(void);

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

Преимущества здесь:

  1. Немного синтаксического сахара, чтобы упростить обслуживание.
  2. Вы можете изменить схему защиты, изменив инструмент, если позже найдете более подходящий метод.Будет меньше мест для изменений, а это значит, что вы с большей вероятностью сможете внести изменения.(Например, позже вы можете найти альтернативу «недопустимому макрокоду», который предлагает nategoose.)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...