《Design by Contract for Embedded Software》 翻译( 三 )


而这正是契约设计(DbC)理念的体现 。DbC 是由 Bertrand Meyer 开创的,他将软件系统视为一组组件,这些组件的协作是基于精确定义的相互义务的规范--合同 1 。这样做有两个主要好处 。1)它自动帮助检测错误(而不是 "处理 "它们),2)它是记录代码的最佳方式之一 。
You can implement the most important aspects of DbC (the contracts) in C or C++ with assertions. The Standard C Library macro assert() is rarely applicable to embedded systems, however, because its default behavior (when the integer expression passed to the macro evaluates to 0) is to print an error message and exit. Neither of these actions makes much sense for most embedded systems, which rarely have a screen to print to and cannot really exit either (at least not in the same sense that a desktop application can). Therefore, in an embedded environment, you usually have to define your own assertions that suit your tools and allow you to customize the error response. I’d suggest, however, that you think twice before you go about “enhancing” assertions, because a large part of their power derives from their relative simplicity.
你可以用断言在 C 或 C++中实现 DbC 的最重要的方面(契约/合同) 。然而,标准 C 库的宏 assert() 很少适用于嵌入式系统,因为它的默认行为(当传递给宏的整数表达式求值为 0 时)是打印一个错误信息并退出 。这两种行为对大多数嵌入式系统来说都没有什么意义 , 它们很少有屏幕可以打?。膊荒苷嬲顺觯ㄖ辽俨荒芟褡烂娉绦蚰茄顺觯?。因此,在嵌入式环境中,你通常必须定义你自己的断言,以适应你的工具并允许你自定义错误响应 。然而 , 我建议你在 "加强 "断言之前三思而后行,因为断言的很大一部分力量来自于其相对的简单性 。
Listing 1. Embedded systems-friendly assertions
#ifndef qassert_h#define qassert_h/** NASSERT macro disables all contract validations * (assertions, preconditions, postconditions, and invariants). */#ifdef NASSERT /* NASSERT defined--DbC disabled */#define DEFINE_THIS_FILE#define ASSERT(ignore_)((void)0)#define ALLEGE(test_)((void)(test_))#else /* NASSERT not defined--DbC enabled */#ifdef __cplusplusextern "C"{#endif/* callback invoked in case of assertion failure */void onAssert__(char const *file, unsigned line);#ifdef __cplusplus}#endif#define DEFINE_THIS_FILE \static char const THIS_FILE__[] = __FILE__#define ASSERT(test_) \((test_) ? (void)0 : onAssert__(THIS_FILE__, __LINE__))#define ALLEGE(test_)ASSERT(test_)#endif /* NASSERT */#define REQUIRE(test_)ASSERT(test_)#define ENSURE(test_)ASSERT(test_)#define INVARIANT(test_) ASSERT(test_)#endif /* qassert_h */Listing 1 shows the simple embedded systems-friendly assertions that I’ve found adequate for a wide range of embedded projects. Listing 1 is similar to the standard <assert.h> (<cassert> in C++), except that the solution shown in Listing 1:
  • allows customizing the error response;
  • conserves memory by avoiding proliferation of multiple copies of the filename string;
  • provides additional macros for testing and documenting preconditions (REQUIRE), postconditions (ENSURE), and invariants (INVARIANT). (The names of the three last macros are a direct loan from Eiffel, the programming language that natively supports DbC.)
The all-purpose ASSERT() macro (lines 28-29 of Listing 1) is very similar to the standard assert(). If the argument passed to this macro evaluates to 0 (false), and if additionally the macro NASSERT is not defined, then ASSERT() will invoke a global callback onAssert__(). The function onAssert__() gives the clients the opportunity to customize the error response when the assertion fails. In embedded systems, onAssert__() typically first monopolizes the CPU (by disabling interrupts), then possibly attempts to put the system in a fail-safe mode, and eventually triggers a system reset. (Many embedded systems come out of reset in a fail-safe mode, so putting them in this mode before reset is often unnecessary.) If possible, the function should also leave a trail of bread crumbs from the cause, perhaps by storing the filename and line number in a nonvolatile memory. (The entry to onAssert__() is also an ideal place to set a breakpoint if you work with a debugger. TIP: Consult your debugger manual on how you can hard-code a permanent breakpoint in onAssert__().)
Compared to the standard assert(), the macro ASSERT() conserves memory (typically ROM) by passing THIS_FILE__ (Listing 1, line 26) as the first argument to 

推荐阅读