from:http://www.oschina.net/question/1579_45444
首先,一小段理论知识
未定义行为是某些编程语言的特性(尤其在C和C++中),在某些情形下产生的结果將依赖于编译器的实现或指定的优化选项。换句话说,规范并没有定义 某情况下该语言的行为,仅仅是说:“在 A 条件下,B 结果是未定义的”。在这种情况下错误在你的程序中被认为是允许的,甚至在一些特别的编译器中执行良好。这样的程序不能跨平台,并有可能在不同的电脑,不同 的操作系统甚至不同的编译器设置中导致失败。
一个时序点可以是程序中的任意点,它保证在它之前所有运算的副作用已完成,而后继运算的副作用未开始.学习更多关于时序和跟时序点相关的未定义行为,查看该贴:http://www.viva64.com/en/t/0065/.
例 1. Chromium项目。不正确的使用智能指针。
1
|
void AccessibleContainsAccessible(...)
|
4
|
auto_ptr<VARIANT>
child_array( new VARIANT[child_count]);
|
The error was found through the V554 diagnostic:
Incorrect use of auto_ptr. The memory allocated with 'new []' will be cleaned using 'delete'. interactive_ui_tests accessibility_win_browsertest.cc 171
该例子演示了使用智能指针的时候,有可能导致未定义的行为。它可能通过堆破坏,程序崩溃,未完全的对象析构函数或任何其它的错误传达. 该错误是:内存被new [] 操作分配,而被‘auto_ptr'类构析函数里的 delete 操作释放:
为修复这类问题,你应为实例使用一个更恰当的类,boost::scoped_array.
例 2. IPP
Samples 项目。经典的未定义行为。
01
|
template < typename T,
Ipp32s size> void HadamardFwdFast(...)
|
06
|
a[0]
= pTemp[0*4] + pTemp[1*4];
|
07
|
a[1]
= pTemp[0*4] - pTemp[1*4];
|
08
|
a[2]
= pTemp[2*4] + pTemp[3*4];
|
09
|
a[3]
= pTemp[2*4] - pTemp[3*4];
|
The error was found through the V567 diagnostic:
未定义行为. 'pTemp' 变量被修改且在时序点间使用了两次. me umc_me_cost_func.h 168
这是一个关于未定义程序行为的经典例子. 各类文章中都拿它来演示未定义行为. ‘pTemp'自增与否是未知的。两个改变 pTemp 变量值的操作位于一个时序点中.这意味着编译器可能创建如下的代码:
pTemp = pTemp + 1;
pTemp = pTemp;
也可能创建另一版本的代码:
TMP = pTemp;
pTemp = pTemp + 1;
pTemp = TMP;
创建何种版本的代码依赖于编译器和优化选项。
例 3.Fennec
Media Project 项目。复杂的表达式。
1
|
uint32
CUnBitArrayOld::DecodeValueRiceUnsigned(uint32 k)
|
4
|
while (!(m_pBitArray[m_nCurrentBitIndex
>> 5] &
|
5
|
Powers_of_Two_Reversed[m_nCurrentBitIndex++
& 31])) {}
|
The error was found through the V567 diagnostic:
'm_nCurrentBitIndex' 变量被修改并在单个时序点中被使用了两次.MACLib unbitarrayold.cpp 78
'm_nCurrentBitIndex' 变量的使用在两者间并没有时序点. 意味着标准并未指定该变量何时增长. 情况不同, 该代码可能工作方式不同,依赖于编译器和优化选项.
例 4.Miranda
IM 项目. 复杂的表达式.
1
|
short ezxml_internal_dtd(ezxml_root_t
root,
|
5
|
while (*(n
= ++s + strspn (s,
EZXML_WS)) && *n != '>' )
{
|
The error was found through the V567 diagnostic:
未定义行为. 's' 变量被修改且在时序点间使用了两次. msne zxml.c
这里使用了前缀++. 但不代表什么:无法保证 's' 变量值会在 strspn() 函数调用前增长。
为了更容易理解例子,让我们先回顾运算符优先级列表。
例 1.MySQL 项目。!
和 & 运算的优先级
1
|
int ha_innobase::create(...)
|
6
|
&&
(!create_info->options & HA_LEX_CREATE_TMP_TABLE)) {
|
The error was found through the V564 diagnostic:
'&' 操作被用到了bool类型的值上. 你很可能忘记加入圆括号或有意的使用'&&'操作。innobase ha_innodb.cc 6789
程序员想用表达式的某部分来检查 'create_info->options' 变量中的某个特定的比特位等于是否为 0 . 但 '!' 操作的优先级高于 '&' 操作,这就是该表达式使用下面运算规则的原因:
1
|
((!create_info->options)
& HA_LEX_CREATE_TMP_TABLE)
|
2
|
We
should use additional parentheses if we
want the code to work properly:
|
3
|
(!(create_info->options
& HA_LEX_CREATE_TMP_TABLE))
|
或者,我们发现更好的方式,用下面的方式编写代码:
1
|
((create_info->options
& HA_LEX_CREATE_TMP_TABLE) == 0)
|
例 2. Emule 项目.
* 和 ++ 优先级.
2
|
CCustomAutoComplete::Next(..., ULONG *pceltFetched)
|
5
|
if (pceltFetched
!= NULL)
|
The error was found through the V532 diagnostic:考虑审查
'*pointer++' 部分. 可能的意思是:'(*pointer)++'. emule customautocomplete.cpp 277
如果 'pceltFetched' 不是一个空指针,该函数必须增加该指针指向的ULONG类型变量的值。错误是:'++' 运算符的优先级高于 '*' 运算符的优先级(指针解引用)。该 "*pceltFetched++;" 行等同于下面的代码:
1
|
TMP
= pceltFetched + 1;
|
实际上它仅仅增加了指针的值。为使代码正确,我们必须添加括号:"(*pceltFetched)++;".
例 3.Chromium 项目。&
和 != 运算符的优先级
1
|
#define
FILE_ATTRIBUTE_DIRECTORY 0x00000010
|
3
|
bool GetPlatformFileInfo(PlatformFile
file, PlatformFileInfo* info) {
|
6
|
file_info.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY != 0;
|
The error was found through the V564 diagnostic:
'&' 操作被用到了bool类型的值上. 你很可能忘记加入圆括号或有意的使用'&&'操作。base platform_file_win.cc 216
程序员们很容易忘记 '!=' 的优先级高于 '&' 的优先级。这就在我们的例子中发生了。因此,我们使用下面的表达式:
2
|
file_info.dwFileAttributes
& (0x00000010 != 0);
|
让我们简化它:
1
|
info->is_directory
= file_info.dwFileAttributes & ( true );
|
再次简化它:
1
|
info->is_directory
= file_info.dwFileAttributes & 1;
|
原来, 我们是测试了第一个比特位而不是第5个比特位. 为修正它,我们需要添加圆括号。
例 4.BCmenu 项目。IF 和 ELSE 混乱。
1
|
void BCMenu::InsertSpaces( void )
|
4
|
if (!xp_space_accelerators) return ;
|
6
|
if (!original_space_accelerators) return ;
|
The error was found through the V563 diagnostic:
可能,这里的 'else' 分支必须与前一个 'if' 关联。 fire bcmenu.cpp 1853
这不是一个优先级的错误,但与它有关。程序员没有顾及 'else' 分支是于最近的 'if' 操作符关联。我们有充分的理由认为代码將以下面的算法工作:
1
|
if (IsLunaMenuStyle())
{
|
2
|
if (!xp_space_accelerators) return ;
|
4
|
if (!original_space_accelerators) return ;
|
但实际上它等同于下面的结构:
3
|
if (!xp_space_accelerators)
{
|
6
|
if (!original_space_accelerators) return ;
|
例 5.IPP
Samples 项目. ?: 和 | 的优先级。
1
|
vm_file*
vm_file_fopen(...)
|
4
|
mds[3]
= FILE_ATTRIBUTE_NORMAL |
|
5
|
(islog
== 0) ? 0 : FILE_FLAG_NO_BUFFERING;
|
The error was found through the V502 diagnostic:
可能 '?:' 操作不会像期望的那样工作。'?:' 操作具有比 '|' 操作更低的优先级。 vm vm_file_win.c 393
依赖于 'islog' 变量值,该表达式必须等于 "FILE_ATTRIBUTE_NORMAL" 或 "FILE_ATTRIBUTE_NORMAL"。但这不会发生。'?:' 的优先级比 '|' 优先级低。因此,该代码会变成下面这样:
1
|
mds[3]
= (FILE_ATTRIBUTE_NORMAL | (islog == 0)) ?
|
2
|
0
: FILE_FLAG_NO_BUFFERING;
|
让我们简化该表达式:
1
|
mds[3]
= (0x00000080 | ...) ? 0 : FILE_FLAG_NO_BUFFERING;
|
既然 FILE_ATTRIBUTE_NORMAL等于0x00000080,该条件永远为真。意味着 0 总是被写入 mds[3] 中。
例 6.Newton
Game Dynamics 项目。?: 和 * 的优先级。
1
|
dgInt32
CalculateConvexShapeIntersection (...)
|
4
|
den
= dgFloat32 (1.0e-24f) *
|
5
|
(den
> dgFloat32 (0.0f)) ?
|
6
|
dgFloat32
(1.0f) : dgFloat32 (-1.0f);
|
The error was found through the V502 diagnostic:
可能 '?:' 操作不会像期望的那样工作。'?:' 具有比 '*' 运算更低的优先级。physics dgminkowskiconv.cpp 1061
该代码的错误再一次与 '?:' 去处符相关。'?:' 运算符的条件被一个无效的表达式 "dgFloat32 (1.0e-24f) * (den > dgFloat32 (0.0f))"表示了. 添加圆括号將解决该问题.
顺便说明,程序员们常常忘记 '?:' 运算符是多么狡猾。这里有个关于该主题贴子:"How to make fewer errors at the stage of code writing. Part
N2".