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

如何在编译时强制执行接口契约(在C中)?,c,interface,uml,design-by-contract,contract,C,Interface,Uml,Design By Contract,Contract,背景: 我们正在为一个新的嵌入式系统的固件建模。目前固件是用UML建模的,但是UML建模工具的代码生成功能将不被使用 目标语言将是C(具体来说是C99) 低功耗(即性能、快速执行)和正确性很重要,但正确性是最高优先级,高于一切,包括代码大小和执行速度 在系统建模过程中,我们确定了一组定义良好的组件。每个组件都有自己的接口,许多组件与许多组件交互 模型中的大多数组件将是实时操作系统(RTOS)下的单个任务(线程),尽管有些组件只不过是库。任务之间完全通过消息传递/队列发布进行通信。与库的交互将以同

背景:

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

目标语言将是C(具体来说是C99)

低功耗(即性能、快速执行)和正确性很重要,但正确性是最高优先级,高于一切,包括代码大小和执行速度

在系统建模过程中,我们确定了一组定义良好的组件。每个组件都有自己的接口,许多组件与许多组件交互

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

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

在中,有用于表示组件之间接口的端口/连接器,即一个组件提供另一个组件所需的。到目前为止还不错

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

问题/问题:

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

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

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

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

在本练习中,您可以假设人们行为良好。换句话说,不要担心人们直接作弊、剪切和粘贴函数原型,或者包括他们不允许的头文件。如果不允许的话,他们不会直接从“a”发信息到“B”,等等

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


代码必须干净地编译&lint,因此“函数原型防火墙”方法是可以的,但似乎有一种更惯用的方法来实现这一点。

标题的想法是合理的,但是,取决于组件之间的交错,将每个组件的接口划分为多个子类别,并使用它们自己的头文件,而不是为每个组件连接提供头文件,这可能会更简洁

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

\ifdef FOO\u H_
#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 */
/*我考虑过允许您多次(可能是间接地)包含此内容 每次打开一组新的`#define`,但交互 在这和食物之间,我感到困惑。我毫不怀疑有一个好消息 实现这一目标的方法,但我决定现在不担心它*/ #warn foo.h包含多次 #else/*FOO_H_*/ #包括 #ifdef FOO_组件A int foo_func1(int x); 静态内联int foo_func2(消息\u t*msg){ 返回msg_send(foo,msg); } ... #else/*FOO_组件A*/ /*这样做将有希望使编译器用 一个错误,该错误将提示为什么不使用此函数名 错。您可能希望使用编译器(可能还有一些编译器) 其他)查看是否有更好的非法代码用于 宏*/ #定义foo_func1(x)(“foo_func1”=NULL) #定义foo_func2(x)(“foo_func2”=NULL) ... #endif/*FOO_组件A*/ #ifdef FOO_组件 int foo_func3(int x); #else/*FOO_组件*/ #定义foo_func3(x)(“foo_func3”=NULL) #endif/*FOO_组件*/

你应该考虑创建一个迷你语言和一个简单的工具来生成标题文件。 要在该答案中生成标题,如下所示(称之为

foo.comp
):

(并扩展示例以提供可由多个组件使用的接口):

这将很容易解析和生成头文件。如果您的接口(我特别怀疑消息传递可能是)是e
[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);