繁体   English   中英

如何在编译时强制执行接口契约(在C中)?

[英]How to enforce interface contracts (in C) at compile time?

背景:

我们正在为新的嵌入式系统建模固件。 目前,固件正在使用UML建模,但不会使用UML建模工具的代码生成功能。

目标语言为C(C99,具体)。

低功耗(即性能,快速执行)和正确性很重要,但正确性是首要任务 ,高于其他一切,包括代码大小和执行速度。

在对系统进行建模时,我们已经确定了一组明确定义的组件。 每个组件都有自己的接口,许多组件与许多组件交互。

模型中的大多数组件都是实时操作系统(RTOS)下的单个任务(线程),尽管有些组件只不过是库。 任务通过消息传递/队列发布完全相互通信。 与库的交互将采用同步函数调用的形式。

由于建议/建议可能取决于规模,我将提供一些信息。 现在可能有大约12-15个组件,可能会增长到~20? 不是100多个组件。 让我们说平均来说,每个组件与25%的其他组件交互。

组件图中 ,有用于表示组件之间接口的端口/连接器,即一个组件提供其他组件所需的内容。 到现在为止还挺好。

这就是问题:在很多情况下我们不希望“组件A”访问所有 “组件B”接口,即我们希望将组件A限制为组件B提供的接口的子集。

问题/问题:

是否有一种系统的,相当直接的方式来强制执行 - 最好是在编译时 - 组件图上定义的接口契约?

显然,编译时解决方案优于运行时解决方案(早期检测,更好的性能,可能更小的代码)。

例如,假设库组件“B”提供函数X(),Y()和Z(),但我只希望组件“A”能够调用函数Z(),而不是X()和Y() 。 类似地,即使组件“A”能够通过其消息队列接收和处理大量不同的消息,我们也没有任何组件能够向任何组件发送任何消息。

我能想到的最好的方法是为每个组件 - 组件接口提供不同的头文件,并且只显示(通过头文件)允许组件使用的接口部分。 显然这可能导致很多头文件。 这也意味着组件之间的消息传递不会直接与OS API完成,而是通过函数调用完成,每个函数调用构建并发送特定的(允许的)消息。 对于同步调用/库,仅公开API的允许子集。

对于本练习,您可以假设人们会表现得很好。 换句话说,不要担心人们直接欺骗和剪切和粘贴函数原型,或者包括他们不允许的头文件。 如果不允许,他们不会直接将消息从“A”发布到“B”,依此类推......

也许有一种方法可以使用编译时断言强制执行合同。 也许有更优雅的方式在运行时检查/强制执行此操作,即使它会产生一些开销。

代码必须干净地编译和lint,所以“函数原型防火墙”方法是可以的,但似乎可能有更惯用的方法来做到这一点。

标题的想法是合理的,但是,根据组件之间的隔行扫描,将每个组件的接口划分为多个具有自己的头文件的子类别而不是为每个组件提供头文件可能更简洁-component连接。

子类别不一定是不相交的,但要确保(通过预处理器指令)可以混合类别而不需要重新定义; 这可以通过为每个类型或函数声明创建一个头文件以及它自己的包含保护,然后从这些原子块构建子类头来以系统的方式实现。

#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 */

你应该考虑创建一个迷你语言和一个简单的工具来生成头文件,就像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提出的“非法宏代码”的替代方案。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM