关于C语言的部分BUG
2018-10-27 10:19
1156 查看
目录
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调试以及反汇编来进行说明:
- 程序在运行至main函数中的
allocateInt(i, m);
语句时,变量i和m的内存地址如下图所示,&i=0x7fffffffdaf0,&m=0x7fffffffdaec:
- 之后使用命令si对汇编语言进行单步调试,连续运行5次si命令后(主要是保留变量i和m的值),程序进入
allocateInt
函数。进入时,i=0x7ffff7ffe168, m=0,也就是说i和m还并没有被传递赋值,结果如下所示:
但此时,变量i和m的地址是不同的,&i=0x7fffffffdac8,&m=0x7fffffffdac4,如下图所示:
- 再运行5次汇编指令后,才将参数的完成传递赋值,程序的指针才开始指向
void allocateInt(int * i, int m)
中的printf("i address: %x\n", &i);
,如下图所示:
此时的i和m已经被赋值,i=(int *) 0x7fffffffdaec, m=5。 - 针对在第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。
- 关于寄存器的相关知识、gdb的调试命令可以参考下面的参考资料;
- 关于汇编指令中出现的
lea
命令可以网上查找,主要就是一种更加有效的mov方法; - 关于汇编指令中出现的
callq 0x4004a0 <printf@plt>
,意思是调用print函数。但是这里并不是直接调用print函数,而是调用类似于print函数在进程中的别名。因为这是公用库中的函数,因此不同进程中都会调用,所以只在进程中存留一个函数地址或者别名就好。具体参见stackoverflow上的一篇文章What does @plt mean here?。
题外话
- 在编写时注意局部性原理,提高性能。一般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++性能榨汁机之分支预测器
相关文章推荐
- 【原创】关于在Objective-C中使用C语言数组的使用和NSArray的差别,以及由此可能产生的一个BUG
- 关于c语言的一个小bug详解
- 关于C语言的数组部分归纳
- 关于C语言的数组部分归纳
- 关于markcode 2.3.21及之前的部分版本页面卡死bug (markdown编辑器)
- 关于计算机和C语言的部分基本概念
- 关于C语言部分面试题小测试
- 关于c语言的一个小bug(c专家编程)
- 关于部分Intel Core 2 Duo存在的一些BUG
- 关于查询的部分数据结构实现-C语言
- cocos关于部分手机截屏不全的BUG
- IOS开发之Bug--关于C语言数组的容量参数
- 关于c语言的一个小bug(c专家编程)
- 关于C语言的指针部分的一点儿小启示
- 关于c语言的算术转换引起的bug
- 关于C语言中feof的使用
- [转]关于C语言
- 关于C语言的几个细节
- 关于 c语言中的声明和定义
- js - 关于部分浏览器内置函数console详解(用开发调试的利器)