• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > C++编译器无法捕捉到的8种错误实例分析

C++编译器无法捕捉到的8种错误实例分析

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了c++编译器,c++编译器下载,c++编译器哪个好,c++在线编译器,c/c++编译器等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

本文实例分析了C++编译器无法捕捉到的8种错误,分享给大家供大家参考之用。有助于深入理解C++运行原理,具体分析如下:

众所周知,C++是一种复杂的编程语言,其中充满了各种微妙的陷阱。在C++中几乎有数不清的方式能把事情搞砸。幸运的是,如今的编译器已经足够智能化了,能够检测出相当多的这类编程陷阱并通过编译错误或编译警告来通知程序员。最终,如果处理得当的话,任何编译器能检查到的错误都不会是什么大问题,因为它们在编译时会被捕捉到,并在程序真正运行前得到解决。最坏的情况下,一个编译器能够捕获到的错误只会造成程序员一些时间上的损失,因为他们会寻找解决编译错误的方法并修正。

那些编译器无法捕获到的错误才是最危险的。这类错误不太容易察觉到,但可能会导致严重的后果,比如不正确的输出、数据被破坏以及程序崩溃。随着项目的膨胀,代码逻辑的复杂度以及众多的执行路径会掩盖住这些bug,导致这些bug只是间歇性的出现,因此使得这类bug难以跟踪和调试。尽管本文的这份列表对于有经验的程序员来说大部分都只是回顾,但这类bug产生的后果往往根据项目的规模和商业性质有不同程度的增强效果。

这些示例全部都在Visual Studio 2005 Express上测试过,使用的是默认告警级别。根据你选择的编译器,你得到的结果可能会有所不同。我强烈建议所有的程序员朋友都采用最高等级的告警级别!有一些编译提示在默认告警级别下可能不会被标注为一个潜在的问题,而在最高等级的告警级别下就会被捕捉到!

1)变量未初始化

变量未初始化是C++编程中最为常见和易犯的错误之一。在C++中,为变量所分配的内存空间并不是完全“干净的”,也不会在分配空间时自动做清零处理。其结果就是,一个未初始化的变量将包含某个值,但没办法准确地知道这个值是多少。此外,每次执行这个程序的时候,该变量的值可能都会发生改变。这就有可能产生间歇性发作的问题,是特别难以追踪的。看看如下的代码片段:

if (bValue)
   // do A
else
   // do B

</div>

如果bValue是未经初始化的变量,那么if语句的判断结果就无法确定,两个分支都可能会执行。在一般情况下,编译器会对未初始化的变量给予提示。下面的代码片段在大多数编译器上都会引发一个警告信息。

int foo()
{
  int nX;
  return nX;
}

</div>

但是,还有一些简单的例子则不会产生警告:

void increment(int &nValue)
{
  ++nValue;
}
int foo()
{
  int nX;
  increment(nX);
  return nX;
}

</div>

以上的代码片段可能不会产生一个警告,因为编译器一般不会去跟踪查看函数increment()到底有没有对nValue赋值。

未初始化变量更常出现于类中,成员的初始化一般是通过构造函数的实现来完成的。

class Foo
{
private:
  int m_nValue;
public:
  Foo();
  int GetValue() { return m_bValue; }
};
 
Foo::Foo()
{
  // Oops, 我们忘记初始化m_nValue了
}
 
int main()
{
  Foo cFoo;
  if (cFoo.GetValue() > 0)
    // do something
  else
    // do something else
}

</div>

注意,m_nValue从未初始化过。结果就是,GetValue()返回的是一个垃圾值,if语句的两个分支都有可能会执行。

新手程序员通常在定义多个变量时会犯下面这种错误:

int nValue1, nValue2 = 5;
</div>

这里的本意是nValue1和nValue2都被初始化为5,但实际上只有nValue2被初始化了,nValue1从未被初始化过。

由于未初始化的变量可能是任何值,因此会导致程序每次执行时呈现出不同的行为,由未初始化变量而引发的问题是很难找到问题根源的。某次执行时,程序可能工作正常,下一次再执行时,它可能会崩溃,而再下一次则可能产生错误的输出。当你在调试器下运行程序时,定义的变量通常都被清零处理过了。这意味着你的程序在调试器下可能每次都是工作正常的,但在发布版中可能会间歇性的崩掉!如果你碰上了这种怪事,罪魁祸首常常都是未初始化的变量。

2)整数除法

C++中的大多数二元操作都要求两个操作数是同一类型。如果操作数的不同类型,其中一个操作数会提升到和另一个操作数相匹配的类型。在C++中,除法操作符可以被看做是2个不同的操作:其中一个操作于整数之上,另一个是操作于浮点数之上。如果操作数是浮点数类型,除法操作将返回一个浮点数的值:

float fX = 7;
float fY = 2;
float fValue = fX / fY; // fValue = 3.5

</div>

如果操作数是整数类型,除法操作将丢弃任何小数部分,并只返回整数部分。

int nX = 7;
int nY = 2;
int nValue = nX / nY;  // nValue = 3

</div>

如果一个操作数是整型,另一个操作数是浮点型,则整型会提升为浮点型:

float fX = 7.0;
int nY = 2;
float fValue = fX / nY;
 
// nY 提升为浮点型,除法操作将返回浮点型值
// fValue = 3.5

</div>

有很多新手程序员会尝试写下如下的代码:

int nX = 7;
int nY = 2;
float fValue = nX / nY; // fValue = 3(不是3.5哦!)

</div>

这里的本意是nX/nY将产生一个浮点型的除法操作,因为结果是赋给一个浮点型变量的。但实际上并非如此。nX/nY首先被计算,结果是一个整型值,然后才会提升为浮点型并赋值给fValue。但在赋值之前,小数部分就已经丢弃了。

要强制两个整数采用浮点型除法,其中一个操作数需要类型转换为浮点数:

int nX = 7;
int nY = 2;
float fValue = static_cast<float>(nX) / nY; // fValue = 3.5

</div>

因为nX显式的转换为float型,nY将隐式地提升为float型,因此除法操作符将执行浮点型除法,得到的结果就是3.5。

通常一眼看去很难说一个除法操作符究竟是执行整数除法还是浮点型除法:

z = x / y; // 这是整数除法还是浮点型除法?

</div>

但采用匈牙利命名法可以帮助我们消除这种疑惑,并阻止错误的发生:

int nZ = nX / nY;   // 整数除法
double dZ = dX / dY; // 浮点型除法

</div>

有关整数除法的另一个有趣的事情是,当一个操作数是负数时C++标准并未规定如何截断结果。造成的结果就是,编译器可以自由地选择向上截断或者向下截断!比如,-5/2可以既可以计算为-3也可以计算为-2,这和编译器是向下取整还是向0取整有关。大多数现代的编译器是向0取整的。

3)=  vs  ==

这是个老问题,但很有价值。许多C++新手会弄混赋值操作符(=)和相等操作符(==)的意义。但即使是知道这两种操作符差别的程序员也会犯下键盘敲击错误,这可能会导致结果是非预期的。

// 如果nValue是0,返回1,否则返回nValue
int foo(int nValue)
{
  if (nValue = 0) // 这是个键盘敲击错误 !
    return 1;
  else
    return nValue;
}
 
int main()
{
  std::cout << foo(0) << std::endl;
  std::cout << foo(1) << std::endl;
  std::cout << foo(2) << std::endl;
 
  return 0;
}

</div>

函数foo()的本意是如果nValue是0,就返回1,否则就返回nValue的值。但由于无意中使用赋值操作符代替了相等操作符,程序将产生非预期性的结果:

0
0
0

</div>

当foo()中的if语句执行时,nValue被赋值为0。if (nValue = 0)实际上就成了if (nValue)。结果就是if条件为假,导致执行else下的代码,返回nValue的值,而这个值刚好就是赋值给nValue的0!因此这个函数将永远返回0。

在编译器中将告警级别设置为最高,当发现条件语句中使用了赋值操作符时会给出一个警告信息,或者在条件判断之外,应该使用赋值操作符的地方误用成了相等性测试,此时会提示该语句没有做任何事情。只要你使用了较高的告警级别,这个问题本质上都是可修复的。也有一些程序员喜欢采用一种技巧来避免=和==的混淆。即,在条件判断中将常量写在左边,此时如果误把==写成=的话,将引发一个编译错误,因为常量不能被赋值。

4)混用有符号和无符号数

如同我们在整数除法那一节中提到的,C++中大多数的二元操作符需要两端的操作数是同一种类型。如果操作数是不同的类型,其中一个操作数将提升自己的类型以匹配另一个操作数。当混用有符号和无符号数时这会导致出现一些非预期性的结果!考虑如下的例子:

cout << 10 – 1



 
分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • linux C++ 获取文件绝对路径的实例代码
  • Linux中使用VS Code编译调试C++项目详解
  • Linux下用C++实现俄罗斯方块
  • C++实现Linux下弹出U盘的方法
  • 在Linux下编译C或C++程序的教程
  • C++编译器无法捕捉到的8种错误实例分析
  • c++利用windows函数实现计时示例
  • 基于Windows C++ 应用程序通用日志组件的使用详解

相关文章

  • 2017-05-28C语言中形参和实参详解及实例代码
  • 2017-05-28c++ 判断奇数偶数实例介绍
  • 2017-05-28用C实现PHP扩展 Image_Tool 图片常用处理工具类的使用
  • 2017-05-28C语言求Fibonacci斐波那契数列通项问题的解法总结
  • 2017-05-28sizeof()的简单介绍
  • 2017-05-28java 中ArrayList与LinkedList性能比较
  • 2017-05-28详解C语言编程中预处理器的用法
  • 2017-05-28如何在程序中判断VS的版本(实现方法详解)
  • 2017-05-28C++的sstream标准库详细介绍
  • 2017-05-28C++中自定义sleep、条件变量sleep实例

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • 用C++实现strcpy(),返回一个char*类型的深入分析
    • C++ 中"emplace_back" 与 "push_back" 的区别
    • 关于win32 gettimeofday替代方案
    • C语言 共用体(Union)详解及示例代码
    • 详解C++文件读写操作
    • C 语言基础教程(我的C之旅开始了)[五]
    • 用C语言判断字符是否为空白字符或特殊字符的方法
    • 二维指针动态分配内存连续问题深入分析
    • C++实现一维向量旋转算法
    • C语言实现用户态线程库案例

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有