C/C++ 中 assert(false) 的更好替代方案
Better alternatives to assert(false) in C/C++
目前,我写
assert(false);
在我的代码不应该到达的地方。一个非常 C 风格的例子是:
int findzero( int length, int * array ) {
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
assert(false);
}
我的编译器识别出程序在达到 assert(false) 时结束。但是,每当我出于性能原因使用 -DNDEBUG 进行编译时,最后一个断言就会消失,并且编译器会警告执行完成函数时没有 return 语句。
如果已经达到了假定无法到达的代码部分,有什么更好的方法可以结束程序?解决方案应该
- 被编译器识别并且不产生警告(如上面或其他)
- 也许甚至允许自定义错误消息。
无论是现代 C++ 还是 90 年代的 C,我都对解决方案很感兴趣。
替换您的 assert(false)
正是 "unreachable" 内置函数的用途。
它们在语义上等同于您对 assert(false)
的使用。其实VS的拼法很像
GCC/Clang/Intel:
__builtin_unreachable()
MSVS:
__assume(false)
无论 NDEBUG
(不同于 assert
)或优化级别如何,这些都有效。
你的编译器,特别是上面的内置函数,但也可能是你的 assert(false)
,点头表示你有希望那部分永远达不到功能。它可以使用它在某些代码路径上执行一些优化,并且它会消除关于丢失 returns 的警告,因为你已经 承诺 这是故意的。
权衡是语句本身具有未定义的行为(很像前进并从函数的末尾流出已经是)。在某些情况下,您可能希望考虑抛出异常(或返回一些 "error code" 值),或者如果您只想终止程序则调用 std::abort()
(在 C++ 中)。
有人提议 (P0627R0),将其作为标准属性添加到 C++。
If control flow reaches the point of the __builtin_unreachable
, the program is undefined. It is useful in situations where the compiler cannot deduce the unreachability of the code. [..]
风格指南中有时会出现的一条规则是
"Never return from the middle of a function"
All functions should have a single return, at the end of the function.
遵循此规则,您的代码将如下所示:
int findzero( int length, int * array ) {
int i;
for( i = 0; i < length; i++ )
{
if( array[i] == 0 )
break; // Break the loop now that i is the correct value
}
assert(i < length); // Assert that a valid index was found.
return i; // Return the value found, or "length" if not found!
}
我使用自定义断言,当 NDEBUG
打开时,它变成 __builtin_unreachable()
或 *(char*)0=0
(我还使用枚举变量而不是宏,以便我可以轻松设置 NDEBUG每个范围)。
在伪代码中,它类似于:
#define my_assert(X) do{ \
if(!(X)){ \
if (my_ndebug) MY_UNREACHABLE(); \
else my_assert_fail(__FILE__,__LINE__,#X); \
} \
}while(0)
__builtin_unreachable()
应该消除警告并同时帮助优化,但在调试模式下,最好在那里有一个断言或 abort();
这样你就可以获得可靠的恐慌。 (__builtin_unreachable()
到达时只会给你未定义的行为)。
assert
适用于在执行过程中实际上应该不可能发生的场景。在调试中指出 "Hey, turns out what you thought to be impossible is, in fact, not impossible." 很有用 看起来你 应该 在给定的例子中做的是表达函数的失败,也许通过返回 -1
作为那不是一个有效的索引。在某些情况下,设置 errno
可能有助于阐明错误的确切性质。然后,根据这些信息,调用函数可以决定如何处理此类错误。
根据此错误对应用程序其余部分的严重程度,您可能会尝试从中恢复,或者您可能只是记录错误并调用 exit
使其摆脱困境。
我推荐C++ Core Gudelines's Expects and Ensures。它们可以配置为中止(默认)、抛出或在违规时什么都不做。
要在无法到达的分支上抑制编译器警告,您还可以使用 GSL_ASSUME
。
#include <gsl/gsl>
int findzero( int length, int * array ) {
Expects(length >= 0);
Expects(array != nullptr);
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
Expects(false);
// or
// GSL_ASSUME(false);
}
我相信您收到错误的原因是因为断言通常用于调试您自己的代码。当这些函数在 运行 发布时,应该使用异常来代替 std::abort() 的退出来指示程序异常终止。
如果您仍想使用断言,PSkocik 提供了关于定义自定义断言的答案,link 这里有人建议使用自定义断言以及如何在 cmake 中启用它们here 还有。
作为一个完全可移植的解决方案,考虑一下:
[[ noreturn ]] void unreachable(std::string_view msg = "<No Message>") {
std::cerr << "Unreachable code reached. Message: " << msg << std::endl;
std::abort();
}
消息部分当然是可选的。
我喜欢用
assert(!"This should never happen.");
...也可以与条件一起使用,如
assert(!vector.empty() || !"Cannot take element from empty container." );
这样做的好处是,如果断言不成立,字符串会显示在错误消息中。
目前,我写
assert(false);
在我的代码不应该到达的地方。一个非常 C 风格的例子是:
int findzero( int length, int * array ) {
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
assert(false);
}
我的编译器识别出程序在达到 assert(false) 时结束。但是,每当我出于性能原因使用 -DNDEBUG 进行编译时,最后一个断言就会消失,并且编译器会警告执行完成函数时没有 return 语句。
如果已经达到了假定无法到达的代码部分,有什么更好的方法可以结束程序?解决方案应该
- 被编译器识别并且不产生警告(如上面或其他)
- 也许甚至允许自定义错误消息。
无论是现代 C++ 还是 90 年代的 C,我都对解决方案很感兴趣。
替换您的 assert(false)
正是 "unreachable" 内置函数的用途。
它们在语义上等同于您对 assert(false)
的使用。其实VS的拼法很像
GCC/Clang/Intel:
__builtin_unreachable()
MSVS:
__assume(false)
无论 NDEBUG
(不同于 assert
)或优化级别如何,这些都有效。
你的编译器,特别是上面的内置函数,但也可能是你的 assert(false)
,点头表示你有希望那部分永远达不到功能。它可以使用它在某些代码路径上执行一些优化,并且它会消除关于丢失 returns 的警告,因为你已经 承诺 这是故意的。
权衡是语句本身具有未定义的行为(很像前进并从函数的末尾流出已经是)。在某些情况下,您可能希望考虑抛出异常(或返回一些 "error code" 值),或者如果您只想终止程序则调用 std::abort()
(在 C++ 中)。
有人提议 (P0627R0),将其作为标准属性添加到 C++。
If control flow reaches the point of the
__builtin_unreachable
, the program is undefined. It is useful in situations where the compiler cannot deduce the unreachability of the code. [..]
风格指南中有时会出现的一条规则是
"Never return from the middle of a function"
All functions should have a single return, at the end of the function.
遵循此规则,您的代码将如下所示:
int findzero( int length, int * array ) {
int i;
for( i = 0; i < length; i++ )
{
if( array[i] == 0 )
break; // Break the loop now that i is the correct value
}
assert(i < length); // Assert that a valid index was found.
return i; // Return the value found, or "length" if not found!
}
我使用自定义断言,当 NDEBUG
打开时,它变成 __builtin_unreachable()
或 *(char*)0=0
(我还使用枚举变量而不是宏,以便我可以轻松设置 NDEBUG每个范围)。
在伪代码中,它类似于:
#define my_assert(X) do{ \
if(!(X)){ \
if (my_ndebug) MY_UNREACHABLE(); \
else my_assert_fail(__FILE__,__LINE__,#X); \
} \
}while(0)
__builtin_unreachable()
应该消除警告并同时帮助优化,但在调试模式下,最好在那里有一个断言或 abort();
这样你就可以获得可靠的恐慌。 (__builtin_unreachable()
到达时只会给你未定义的行为)。
assert
适用于在执行过程中实际上应该不可能发生的场景。在调试中指出 "Hey, turns out what you thought to be impossible is, in fact, not impossible." 很有用 看起来你 应该 在给定的例子中做的是表达函数的失败,也许通过返回 -1
作为那不是一个有效的索引。在某些情况下,设置 errno
可能有助于阐明错误的确切性质。然后,根据这些信息,调用函数可以决定如何处理此类错误。
根据此错误对应用程序其余部分的严重程度,您可能会尝试从中恢复,或者您可能只是记录错误并调用 exit
使其摆脱困境。
我推荐C++ Core Gudelines's Expects and Ensures。它们可以配置为中止(默认)、抛出或在违规时什么都不做。
要在无法到达的分支上抑制编译器警告,您还可以使用 GSL_ASSUME
。
#include <gsl/gsl>
int findzero( int length, int * array ) {
Expects(length >= 0);
Expects(array != nullptr);
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
Expects(false);
// or
// GSL_ASSUME(false);
}
我相信您收到错误的原因是因为断言通常用于调试您自己的代码。当这些函数在 运行 发布时,应该使用异常来代替 std::abort() 的退出来指示程序异常终止。
如果您仍想使用断言,PSkocik 提供了关于定义自定义断言的答案,link 这里有人建议使用自定义断言以及如何在 cmake 中启用它们here 还有。
作为一个完全可移植的解决方案,考虑一下:
[[ noreturn ]] void unreachable(std::string_view msg = "<No Message>") {
std::cerr << "Unreachable code reached. Message: " << msg << std::endl;
std::abort();
}
消息部分当然是可选的。
我喜欢用
assert(!"This should never happen.");
...也可以与条件一起使用,如
assert(!vector.empty() || !"Cannot take element from empty container." );
这样做的好处是,如果断言不成立,字符串会显示在错误消息中。