100个开源C/C++项目中的bugs(二)未定义行为、与运算优先级相关的错误

简介: from:http://www.oschina.net/question/1579_45444 未定义行为 首先,一小段理论知识 未定义行为是某些编程语言的特性(尤其在C和C++中),在某些情形下产生的结果將依赖于编译器的实现或指定的优化选项。

from:http://www.oschina.net/question/1579_45444

未定义行为

首先,一小段理论知识

未定义行为是某些编程语言的特性(尤其在C和C++中),在某些情形下产生的结果將依赖于编译器的实现或指定的优化选项。换句话说,规范并没有定义 某情况下该语言的行为,仅仅是说:“在 A 条件下,B 结果是未定义的”。在这种情况下错误在你的程序中被认为是允许的,甚至在一些特别的编译器中执行良好。这样的程序不能跨平台,并有可能在不同的电脑,不同 的操作系统甚至不同的编译器设置中导致失败。

 一个时序点可以是程序中的任意点,它保证在它之前所有运算的副作用已完成,而后继运算的副作用未开始.学习更多关于时序和跟时序点相关的未定义行为,查看该贴:http://www.viva64.com/en/t/0065/.

例 1. Chromium项目。不正确的使用智能指针。

1 void AccessibleContainsAccessible(...)
2 {
3   ...
4   auto_ptr<VARIANT> child_array(new VARIANT[child_count]);
5   ...
6 }

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 操作释放:

1 ~auto_ptr() {
2   delete _Myptr;
3 }

为修复这类问题,你应为实例使用一个更恰当的类,boost::scoped_array.

例 2. IPP Samples 项目。经典的未定义行为。

01 template<typename T, Ipp32s size> void HadamardFwdFast(...)
02 {
03   Ipp32s *pTemp;
04   ...
05   for(j=0;j<4;j++) {
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];
10     pTemp = pTemp++;
11     ...
12   }
13   ...
14 }

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)
2 {
3   ...
4   while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
5     Powers_of_Two_Reversed[m_nCurrentBitIndex++ & 31])) {}
6   ...
7 }

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,
2   char *s, size_t len)
3 {
4   ...
5   while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') {
6   ...
7 }

The error was found through the V567 diagnostic: 未定义行为. 's' 变量被修改且在时序点间使用了两次. msne zxml.c

这里使用了前缀++. 但不代表什么:无法保证 's' 变量值会在 strspn() 函数调用前增长。 

与运算优先级相关的错误

为了更容易理解例子,让我们先回顾运算符优先级列表。

例 1.MySQL 项目。! 和 & 运算的优先级

1 int ha_innobase::create(...)
2 {
3   ...
4   if (srv_file_per_table
5       && !mysqld_embedded
6       && (!create_info->options & HA_LEX_CREATE_TMP_TABLE)) {
7   ...
8 }

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 项目. * 和 ++ 优先级.

1 STDMETHODIMP
2 CCustomAutoComplete::Next(..., ULONG *pceltFetched)
3 {
4   ...
5   if (pceltFetched != NULL)
6     *pceltFetched++;
7   ...
8 }

The error was found through the V532 diagnostic:考虑审查 '*pointer++' 部分. 可能的意思是:'(*pointer)++'. emule customautocomplete.cpp 277

如果 'pceltFetched' 不是一个空指针,该函数必须增加该指针指向的ULONG类型变量的值。错误是:'++' 运算符的优先级高于 '*' 运算符的优先级(指针解引用)。该 "*pceltFetched++;" 行等同于下面的代码:

1 TMP = pceltFetched + 1;
2 *pceltFetched;
3 pceltFetched = TMP;

实际上它仅仅增加了指针的值。为使代码正确,我们必须添加括号:"(*pceltFetched)++;".

例 3.Chromium 项目。& 和 != 运算符的优先级

1 #define FILE_ATTRIBUTE_DIRECTORY 0x00000010
2  
3 bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {
4   ...
5   info->is_directory =
6     file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
7   ...
8 }

The error was found through the V564 diagnostic: '&' 操作被用到了bool类型的值上. 你很可能忘记加入圆括号或有意的使用'&&'操作。base platform_file_win.cc 216

程序员们很容易忘记 '!=' 的优先级高于 '&' 的优先级。这就在我们的例子中发生了。因此,我们使用下面的表达式:

1 info->is_directory =
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)
2 {
3   if(IsLunaMenuStyle())
4     if(!xp_space_accelerators) return;
5   else
6     if(!original_space_accelerators) return;
7   ...
8 }

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;
3 else {
4   if(!original_space_accelerators) return;
5 }

但实际上它等同于下面的结构:

1 if(IsLunaMenuStyle())
2 {
3    if(!xp_space_accelerators) {
4      return;
5    else {
6      if(!original_space_accelerators) return;
7    }
8 }

例 5.IPP Samples 项目. ?: 和 | 的优先级。

1 vm_file* vm_file_fopen(...)
2 {
3   ...
4   mds[3] = FILE_ATTRIBUTE_NORMAL |
5            (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;
6   ...
7 }

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 (...)
2 {
3   ...
4   den = dgFloat32 (1.0e-24f) *
5         (den > dgFloat32 (0.0f)) ?
6           dgFloat32 (1.0f) : dgFloat32 (-1.0f);
7   ...
8 }

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".


相关文章
|
1月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
37 0
|
6月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
301 77
|
5月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
217 1
|
6月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
222 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
6月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
156 12
|
6月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
115 9
|
6月前
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
180 7
|
6月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
275 5
|
6月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
163 5
|
7月前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。