scanf格式匹配引发的错误

  运行如下程序时,出现这类错误:*** stack smashing detected ***: ./test_global terminated。错误原因可能是因为scanf("%d%d", &row, &col)接收的是int型,但是我使用的是short int,长度是Int的一半。修改成int后错误消失。

#include<stdio.h>

int main(){
int row, col; scanf("%d%d", &row, &col);
printf("%d %d", row, col);
return 0;
}

  使用gcc编译时出现的警告如下:

    

  出现的错误如下:

    

局部变量被释放引发的bug

  运行如下程序时,会无终止地打印-1。原因是变量p所指向的变量k在addr()函数执行后自行销毁,k所使用的内存被分配给loop()中的变量i,从而导致p指向i。而此时对p的操作是减1,对i的操作是加1,导致i的值始终为-1,无法跳出循环。

#include<stdio.h>

void addr();
void loop(); long *p;
int main(){
addr();
loop();
} void addr(){
long k;
k = 0;
p = &k;
} void loop(){
long i, j;
j = 0;
for (i = 0; i<10;i++){
(*p)--;
j++;
printf("%d\n", i);
}
}

  程序运行输出结果如下:

    

  程序调试结果如下:

    

数组写入超出索引维度

  虽然运行下面代码不会出错,但是对数组a[10]的写操作超出了维度,导致在地址为a+10的地方也写入了数据,但是容易引发潜在bug。

#include<stdio.h>

int main()
{
int i;
int a[10];
for (i = 0; i <= 10; ++i)
{
a[i] = 0;
printf("%d\n", i);
}
exit(0);
}

指针的指针引发的思考

  对于将指针作为参数进行传递时,如果是将在子函数内赋值给一个新申请的空间,那么就要注意在传递指针时,需要传递指针的地址,即指针的指针。错误程序如下:

#include<stdio.h>

void allocateInt(int * i, int m);
void main()
{
int m = 5;
int * i = &m;
printf("i address: %x\n", &i);
allocateInt(i, m);
printf("*i = %d\n", *i);
} void allocateInt(int * i, int m)
{
printf("i address: %x\n", &i);
i = (int *) malloc(sizeof(int));
*i = 3;
}

指针的指针引发的思考——思考

  虽然对该问题的解释一般是:在传递参数时,系统为子函数的变量新申请一部分空间,因此在void allocateInt(int * i)中,i的地址和在void main()中的地址是不同的,而void allocateInt(int * i)中的i是局部变量,在子函数运行结束会被释放掉,因此void main()中的i是无法得到malloc的地址的,更不可能得到新的赋值。

  下面通过gdb调试以及反汇编来进行说明:

  1. 程序在运行至main函数中的allocateInt(i, m);语句时,变量i和m的内存地址如下图所示,&i=0x7fffffffdaf0,&m=0x7fffffffdaec:

  2. 之后使用命令si对汇编语言进行单步调试,连续运行5次si命令后(主要是保留变量i和m的值),程序进入allocateInt函数。进入时,i=0x7ffff7ffe168, m=0,也就是说i和m还并没有被传递赋值,结果如下所示:



    但此时,变量i和m的地址是不同的,&i=0x7fffffffdac8,&m=0x7fffffffdac4,如下图所示:

  3. 再运行5次汇编指令后,才将参数的完成传递赋值,程序的指针才开始指向void allocateInt(int * i, int m)中的printf("i address: %x\n", &i);,如下图所示:



    此时的i和m已经被赋值,i=(int *) 0x7fffffffdaec, m=5。

  4. 针对在第3点提到的4次汇编指令,这里进一步说明。

    • 第1条指令是push %rbp,也就是把rbp寄存器入栈;
    • 第2条指令是mov %rsp,%rbp,其中rsp是堆栈指针。也就是把堆栈指针的值赋值给rbp寄存器;
    • 第3条指令是sub $0x10,%rsp,也就是把堆栈指针所指向的地址减少16个字节。这是因为变量i和m一共占用了16个字节;
    • 第4条指令是mov %rdi,-0x8(%rbp),也就是把寄存器rdi的值(rdi=0x7fffffffdaec,如下图所示)赋值给i。因为i的地址就是rbp-0x8;
    • 第5条指令是mov %esi,-0xc(%rbp),作用类似于第4条,将寄存器esi的值(esi=0x5,如下图所示)赋值给m。

  5. 关于寄存器的相关知识、gdb的调试命令可以参考下面的参考资料;

  6. 关于汇编指令中出现的lea命令可以网上查找,主要就是一种更加有效的mov方法;

  7. 关于汇编指令中出现的callq 0x4004a0 <printf@plt>,意思是调用print函数。但是这里并不是直接调用print函数,而是调用类似于print函数在进程中的别名。因为这是公用库中的函数,因此不同进程中都会调用,所以只在进程中存留一个函数地址或者别名就好。具体参见stackoverflow上的一篇文章What does @plt mean here?


未定义赋值的变量引发的bug

  运行如下代码时,本意是用g_logger.WriteLog()将"in A()"写入文本文件中,但是结果却是将"in A()"打印在了shell里。

// file: main.cc
#include <iostream>
#include "CLLogger.h" using namespace std; extern CLLogger g_logger; class A
{
public:
A()
{
CLStatus s = g_logger.WriteLog("in A()", 0);
if(!s.IsSuccess())
cout << "g_logger.WriteLog error" << endl;
}
}; A g_a; CLLogger g_logger; int main()
{
return 0;
} // file: CLLogger.h
#include "CLStatus.h" class CLLogger
{
public:
CLLogger();
virtual ~CLLogger(); CLStatus WriteLog(const char *pstrMsg, long lErrorCode); private:
CLLogger(const CLLogger&);
CLLogger& operator=(const CLLogger&); private:
int m_Fd;
}; // file: CLStatus.h
class CLStatus
{
public:
CLStatus(long lReturnCode, long lErrorCode);
CLStatus(const CLStatus& s);
virtual ~CLStatus(); public:
bool IsSuccess(); public:
const long& m_clReturnCode;
const long& m_clErrorCode; private:
long m_lReturnCode;
long m_lErrorCode;
};

  原因是g_a是定义在g_logger之前,因此在运行到语句CLStatus s = g_logger.WriteLog("in A()", 0);时,g_logger仍未定义。但由于在文件开头声明了extern CLLogger g_logger;,因此编译器不会报错,而此时默认将声明为外部变量的g_logger中的文件操作符m_Fd赋值为0,如下:

    

题外话

  • 在编写时注意局部性原理,提高性能。一般cache会把某次访问的内存地址附近区域的内容都加载进去。如果在编写程序时相邻语句访问的数据是在内存中连续的,那么就会调高cache的命中率。
  • 在编写时注意分支预测导致的性能问题。在向下跳转的情况下,优先将最有可能执行的语句放在if分支下,减少分支预测时的开销(向下跳转在静态分支预测中一般默认不跳转;向上跳转在静态分支预测中一般默认跳转),例如:
int a = -5;
int b = 0;
................................................
if(a > 0){ if(a <= 0){
b = 1; b = 2;
} }
else{ else{
b = 2; b=1;
} }

  关于分支预测的一些预测方式可以参考一篇博客C++性能榨汁机之分支预测器

参考资料

Visual Studio文档:寄存器使用

探究Linux下参数传递及查看和修改方法

gdb 调试入门,大牛写的高质量指南

GDB的调试命令

What does @plt mean here?

C++性能榨汁机之分支预测器

关于C/C++语言的部分BUG的更多相关文章

  1. C++语言出现的bug

    输出语句不管是C语言的printf();还是cout << "" << endl; 在循环语句中会出现一个bug: 下面是不正常的两种情况: 下面是正常的: ...

  2. 自动判断应该Ajax还是return

    起因 最近回顾以前的代码,发现一个偶尔会见到的现象.一个类里面的方法可能需要Ajax返回,也有可能需要函数return.这个现象发生在网站MVC中的 逻辑层(或模型层),示例如下.IndexCtrl是 ...

  3. Swift の 函数式编程

    Swift 相比原先的 Objective-C 最重要的优点之一,就是对函数式编程提供了更好的支持. Swift 提供了更多的语法糖和一些新特性来增强函数式编程的能力,本文就在这方面进行一些讨论. S ...

  4. 个人作业2——英语学习APP案例分析

    一.个人体验 1.下载并使用,描述最简单直观的个人第一次上手体验. ①入眼界面华丽,有正能量的名言警句配上很有意境的图片,界面美观. ②内容丰富,有许多精选英文文章,同时配有中文翻译,便于理解. ③能 ...

  5. 个人作业2--英语学习APP案例分析

    1.下载APP并使用,上手体验 个人很喜欢这种风格,画面简洁,排版精细,尤其是联想词的界面,很惊喜.但是很多链接比如精选文章点进去之后的UI设计并不理想,感觉只是一个网页而已.并且我不能够保存或者收藏 ...

  6. Python开发规范

    背景 Python语言规范 Lint 导入 包 异常 全局变量 嵌套/局部/内部类或函数 列表推导(List Comprehensions) 默认迭代器和操作符 生成器 Lambda 函数 条件表达式 ...

  7. 个人作业二——英语学习APP 案例分析

    英语学习APP的案例分析 我们生活中很多时候要和软件打交道,大家上课开小差时候玩的手机游戏,买火车票的网站,互相联系用的微信.QQ,等等都是软件,都很值得分析.你为何成为它们的用户?它们的团队做对了什 ...

  8. 个人作业2-英语学习案例app分析

    第一部分 调研, 评测 (软件的bug,功能评测,黑箱测试, 第8章 用户调研, 12 章 软件的用户体验) 下载并使用,描述最简单直观的个人第一次上手体验. ①个人感觉还不错,词典的首页页面挺好看的 ...

  9. 作业2——英语学习APP的案例分析

    英语学习APP的案例分析 很多同学有误解,软件工程课是否就是理论课?或者是几个牛人拼命写代码,其他人打酱油的课?要不然就是学习一个程序语言,搞一个职业培训的课?都不对,软件工程有理论,有实践,更重要的 ...

随机推荐

  1. 沉淀,再出发:XPath的理解和使用

    沉淀,再出发:XPath的理解和使用 一.前言   在很多查找的场合之下,我们需要使用正则表达式和其他的查找工具来进行内容的匹配和查找,特别是对于xml文件,我们可以使用xpath等工具来进行查找,通 ...

  2. 美团2018年CodeM大赛-初赛B轮 B 配送(最短路)

    美团2018年CodeM大赛-初赛B轮 B 配送 题意 题解 对于每个任务,只要从上个任务的终点出发即可. 时间.地点很少,可以算出每个地点-时间的最小花费. 以题目描述的起点终点起始结束时间建图,很 ...

  3. codeforces 811E Vladik and Entertaining Flags(线段树+并查集)

    codeforces 811E Vladik and Entertaining Flags 题面 \(n*m(1<=n<=10, 1<=m<=1e5)\)的棋盘,每个格子有一个 ...

  4. java aopalliance-1.0.jar这个包是做什么用的?

    这个包是AOP联盟的API包,里面包含了针对面向切面的接口.通常Spring等其它具备动态织入功能的框架依赖此包.

  5. Gym 100633G Nano alarm-clocks

    题目,给定n个时钟,要求把他们调成一样的时间.求最小的步数 思路:肯定是有一个时钟作为标准的啦,要找到这个时钟,怎么找呢?没其他方便的方法,暴力枚举.那么枚举后,怎么能快速地算到其他时钟转到这个时钟的 ...

  6. (八)Linux之挂载命令

    挂载命令 其实挂载在Linux中可以理解为分配盘符的意思.想一下,比如一张光盘插入了Linux系统的 电脑上,要想读取其中的内容,需要做哪些操作呢?首先你要考虑的是这张外来光盘的的数据也是 外来的,如 ...

  7. 远程调用内核接口的封装类(RCKObjs)

    RCK 包括 Application, Function, Connection, Command, Response 和 Fields 六 大类, 其主要功能例如以下:     a. Applica ...

  8. mongod入门实战

    mongod-入门 摘要: 本篇文档,带你快速启动一个mongod,到搭建主从+复制集模式的入门. 内容包括:单实例安装,复制集构建,分片构建,分片及复制集整合. 软件相关信息介绍 MongoDB 是 ...

  9. 【[JSOI2007]建筑抢修】

    各种瞎写 之后也不知道为什么就过了 刚看到这道题感觉确实是不会的,因为我贪心太差了\(QAQ\) 之后就随便\(yy\)呗 发现首先我们得排一下序,以\(t2\)也就是建筑的损坏时间为第一关键字从小到 ...

  10. [Python 多线程] Concurrent (十五)

    concurrent包只有一个模块: concurrent.futures - 启动并行任务 异步并行任务编程模块,提供一个高级的异步可执行的便利接口. futures模块提供了2个池执行器 Thre ...