在this example powered by Godbolt中,并复制如下,
可以创建一个需要其参数遵守某些条件(i >= 0)
,ptr != NULL
等的函数。
// EXPECT() : will trigger a warning if the condition is not guaranteed
#define EXPECT(c) (void)((c) ? (void)0 : never_reach())
// assume() : tell the compiler that `cond` is true
// note : only works for simple conditions, such as `i>=0`.
// Avoid complex conditions invoking functions.
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
// *********************************************************
// Function declaration
// *********************************************************
int positive_plus1_body(int v);
#define positive_plus1_preconditions(v) ((v)>=0) // Let's first define the preconditions.
// Name is long, because conditions must be unique to the function
// The inlined function express the contract, with both pre and post conditions.
// `inline` is essential, so that actual input values get verified,
// and result gets enriched with `assume` conditions.
// Problem is, the error is now located INTO the inlined function, not at the place where it's called.
// This makes it very hard to identify where the problem happens.
static inline int positive_plus1(int v) // Ideally, this function should also have additional attribute to not trigger warnings when it's not invoked
{
int r;
EXPECT(positive_plus1_preconditions(v)); // preconditions
r = positive_plus1_body(v);
assume(r > 0); // postconditions
return r;
}
通过Godbolt实时示例,可以调用参数正确和错误的函数,并见证对编译诊断的影响。
如果调用者不遵守条件,则编译器将生成警告。立即检测条件违规非常方便。常见的替代方法是在函数体内使用assert()
,但是要依靠运行时测试来检测潜在的条件违规情况,这种情况不太即时,也不太确定。
问题是,如果用错误的参数调用inline
函数,则编译器将在inline
函数的内部 中定位条件违规。该诊断的作用有限。相反,我们想读取使用错误参数调用inline
函数的行。
解决方法是改用宏。但是,宏将触发著名的多重评估问题,因为它的参数必须多次使用。这足以避免发生危险。另外,可以通过宏自动生成封装inline
函数,但是另一个宏不能生成宏。
另一种解决方案是使用statement expression,它可以解决多重评估问题。但是,它是不可移植的,并且仍然是一个宏,因此不能由另一个宏生成。
如果可能的话,最好保留示例的inline
函数。但是在条件违反发生的地方生成正确的行号非常重要。
有没有办法在保持inline
函数方法的同时实现这一目标?