寻找“野指针”
- 格式:doc
- 大小:26.00 KB
- 文档页数:5
【C】Re05指针⼀、变量 & 指针变量 = 内存地址 + 存储值指针变量 = 内存地址 + 存储值【变量的内存地址】作⽤:间接访问内存地址内存地址 = 地址编号地址编号:内存中的每个字节唯⼀的编号,从0开始记录,使⽤⼗六进制显⽰可以使⽤指针变量存储变量的地址不同数据类型就有对应的指针的数据类型⼆、使⽤#define _CRT_SECURE_NO_WARNINGS#include <stdlib.h>#include <stdio.h>void pointer () {// 指针变量int * pointer;// 变量int varA = 100;printf("pointer -> x16 %x, x10 %d, x8 %o\n", pointer, pointer, pointer);printf("varA -> %d\n", varA);printf("- - - - - - - - - - - - - - -\n");// 把varA的地址赋值给指针变量pointerpointer = &varA;// 通过指针变量pointer取地址访问varA变量*pointer = 20;printf("pointer -> x16 %x, x10 %d, x8 %o\n", pointer, pointer, pointer);printf("varA -> %d\n", varA);}int main() {pointer();return EXIT_SUCCESS;}输出格式注意:// %p显⽰完整⼗六进制位数, %x只显⽰进制数语法递进:int tf = (*pointer == *&varA); // 0 false, 1 trueint tf2 = (pointer == &varA);int tf3 = (*pointer == varA);printf(" %d\n",tf);printf(" %d\n",tf2);printf(" %d\n",tf3);三、空指针& 野指针空指针定义void nullPointerAndWildPointer () {// 定义⼀个空指针int * nullPointer = NULL;}指向的NULL常量来⾃于STDLIB标准库头⽂件的这⼀段#else#define NULL ((void *)0)#endif#endif最后指向的还是⼀个0⽽已void nullPointerAndWildPointer () {// 定义⼀个空指针int * nullPointer = 0;}实际上不建议直接写0,容易混淆指针变量与变量空指针不能被访问到:void nullPointerAndWildPointer () {// 定义⼀个空指针int * nullPointer = NULL;printf("nullPointer -> %p", *nullPointer);}int main() {nullPointerAndWildPointer();return EXIT_SUCCESS;}因为内存地址编号0 - 255已经被操作系统占⽤了空指针的作⽤:不知道应该对指针定义多少合适时使⽤野指针定义:void nullPointerAndWildPointer () {int * wildPointer = 0xffff;printf("wildPointer -> %p\n", *wildPointer);}指针变量存储了⾮法的、未知的⼀个内存地址,该地址存储的内容将⽆法访问但是允许查看地址void nullPointerAndWildPointer () {// 定义⼀个空指针// int * nullPointer = NULL;// printf("nullPointer -> %p\n", *nullPointer);// 定义⼀个野指针// int * wildPointer = 0xffff;// printf("wildPointer -> %p\n", *wildPointer);int * nullPointer = NULL;printf("nullPointer -> %p\n", nullPointer);int * wildPointer = 0xffff;printf("wildPointer -> %p\n", wildPointer);}int main() {nullPointerAndWildPointer();return EXIT_SUCCESS;}野指针的第⼆种情况:也是⼀样,地址可以访问,但是内部存储的值⽆法访问// 野指针的第⼆种情况int * wildPointer2;printf("wildPointer2 -> %p\n", wildPointer2);// printf("wildPointer2 value -> %d\n", *wildPointer2);四、⽆类型指针和万能指针1、Void类型概述void voidUsage() {// void 是⼀个数据类型,所以也具备对于的指针类型 void *// void 的⽤途是修饰函数返回类型和形参类型}// 形参修饰了void 表⽰该函数不需要参数void noNeedParam( void ) {}2、函数返回类型省略当函数的返回值类型声明的是void时,我们可以省略,不需要return不过不建议这样书写,C++并不⽀持这样的语法aaa () {printf("void返回类型省略的函数调⽤");}int main() {aaa();return EXIT_SUCCESS;}如果函数不需要注⼊任何类型的参数,编写时是可以明确标注void 数据类型即可3、⽆类型指针与万能指针:⽆类型指针可以强转任意类型接收对于的类型的变量地址void noTypePointer() {void * p = NULL;printf("sizeof p = %d\n", sizeof(p)); // 64位 sizeof p = 8 32位 sizeof p = 4int num = 10;p = #// printf("p = %d\n", *p); int指针类型赋值给void指针类型,类型不匹配错误// 使⽤强转来转换指针类型printf("p = %d\n", *(int *)p);}另外可以作为万能指针使⽤:void anyTypePointer() {void * pointer = NULL;int * varA = NULL;char * varB = NULL;// ⼀个是int指针类型⼀个是char指针类型,直接这样赋值不会有语法错误提⽰// 但是在编译执⾏时会有警告提⽰,另外,如果指针调⽤了就会报错。
c语言指针类面试题C语言指针是面试中常见的话题之一,下面我将从多个角度回答与C语言指针相关的面试题。
1. 什么是指针?指针是一个变量,用于存储内存地址。
它可以指向其他变量或数据,通过指针可以直接访问或修改这些数据。
2. 指针和变量的区别是什么?变量是一个具体的数据存储单元,而指针是存储变量地址的变量。
变量有自己的值,而指针存储的是另一个变量的地址。
3. 如何声明和定义指针?在C语言中,可以使用以下语法声明和定义指针:c.数据类型指针变量名;例如:c.int ptr;这声明了一个指向整型数据的指针变量ptr。
4. 如何使用指针访问变量的值?可以使用解引用运算符()来访问指针所指向的变量的值。
例如,如果有一个整型指针ptr,可以使用`ptr`来获取ptr所指向的整型变量的值。
5. 指针与数组的关系是什么?数组名本身就是一个指针,它存储了数组的首地址。
可以通过指针算术运算来访问数组中的元素,例如`(array + i)`可以访问数组中的第i个元素。
6. 什么是指针的运算?指针的运算包括指针的加法、减法、比较等操作。
指针加法可以用于在指针上进行偏移,指针减法可以计算两个指针之间的距离,指针比较可以判断两个指针是否相等或者大小关系。
7. 什么是空指针和野指针?空指针是指未指向任何有效地址的指针,可以用NULL来表示。
野指针是指指向未知或无效地址的指针,使用野指针可能导致程序崩溃或产生不可预测的结果。
8. 如何避免野指针?避免野指针的方法包括及时初始化指针、在指针使用完毕后将其置为NULL、避免对未分配内存的指针进行解引用操作等。
9. 什么是指针的指针?指针的指针是指一个指针变量存储了另一个指针变量的地址。
通过指针的指针可以实现对指针的间接访问和修改。
10. 什么是指针数组和数组指针?指针数组是指一个数组中的元素都是指针类型。
数组指针是指一个指针,它指向一个数组的首地址。
以上是对C语言指针类面试题的回答,希望能对你有所帮助。
c语言面试题目及最佳答案1、描述⼀下gcc的编译过程?gcc编译过程分为4个阶段:预处理、编译、汇编、链接。
预处理:头⼀件包含、宏替换、条件编译、删除注释编译:主要进⼀词法、语法、语义分析等,检查⼀误后将预处理好的⼀件编译成汇编⼀件。
汇编:将汇编⼀件转换成⼀进制⼀标⼀件链接:将项⼀中的各个⼀进制⼀件+所需的库+启动代码链接成可执⼀⼀件2、内存的最⼀存储单位以及内存的最⼀计量单位分别是?内存的最⼀存储单位为⼀进制位,内存的最⼀计量单位字节3、#include<> 与#include ""的区别?include<>到系统指定⼀录寻找头⼀件,#include ""先到项⼀所在⼀录寻找头⼀件,如果没有找再到系统指定的⼀录下寻找4、描述⼀下变量的命名规则变量名有字⼀、数值、下划线组成,但不能以数值开头5、变量的声明与定义有啥区别?声明变量不需要建⼀存储空间,变量的定义需要建⼀存储空间6、谈谈c语⼀中有符号和⼀符号的区别?有符号:数据的最⼀位为符号位,0表示正数,1表示负数⼀符号:数据的最⼀位不是符号位,⼀是数据的⼀部分7、谈谈计算机中补码的意义统⼀了零的编码将符号位与其他位统⼀处理将减法运算转换成加法运算8、谈谈数组的特点同⼀个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的9、数组的分类数组的分类主要是:静态数组、动态数组两类。
静态数组:类似int arr[5];在程序运⼀就确定了数组的⼀⼀,运⼀过程不能更改数组的⼀⼀。
动态数组:主要是在堆区申请的空间,数组的⼀⼀是在程序运⼀过程中确定,可以更改数组的⼀⼀。
10、描述⼀下⼀维数组的不初始化、部分初始化、完全初始化的不同点不初始化:如果是局部数组数组元素的内容随机如果是全局数组,数组的元素内容为0部分初始化:未被初始化的部分⼀动补0完全初始化:如果⼀个数组全部初始化可以省略元素的个数数组的⼀⼀由初始化的个数确定11、谈谈数组名作为类型、作为地址、对数组名取地址的区别?数组名作为类型:代表的是整个数组的⼀⼀数组名作为地址:代表的是数组⼀元素的地址对数组名取地址:代表的是数组的⼀地址12、谈谈你对⼀维数组在物理上以及逻辑上的数组维度理解⼀维数组在逻辑上是⼀维的,在物理上是⼀维的13、描述⼀下函数的定义与函数的声明的区别函数定义:是指对函数功能的确⼀,包括指定函数名、函数类型、形参及其类型、函数体等,它是⼀个完整的、独⼀的函数单位。
内存分配的三种⽅式⼀、内存基本分配可编程内存在基本上分为这样的⼏⼤部分:静态存储区、堆区和栈区。
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运⾏期间都存在。
它主要存放静态数据、全局数据和常量。
栈区:在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。
堆区:亦称动态内存分配。
程序在运⾏的时候⽤malloc或new申请任意⼤⼩的内存,程序员⾃⼰负责在适当的时候⽤free或delete释放内存。
动态内存的⽣存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。
但是,良好的编程习惯是:如果某动态内存不再使⽤,需要将其释放掉,否则,我们认为发⽣了内存泄漏现象。
⼆、三者之间的区别我们通过代码段来看看对这样的三部分内存需要怎样的操作和不同,以及应该注意怎样的地⽅。
例⼀:静态存储区与栈区char* p = “Hello World1”;char a[] = “Hello World2”;p[2] = ‘A’;a[2] = ‘A’;char* p1 = “Hello World1;”这个程序是有错误的,错误发⽣在p[2] = ‘A’这⾏代码处,为什么呢?是指针变量p和变量数组a都存在于栈区的(任何临时变量都是处于栈区的,包括在main()函数中定义的变量)。
但是,数据“Hello World1”和数据“Hello World2”是存储于不同的区域的。
因为数据“Hello World2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任何问题的。
因为指针变量p仅仅能够存储某个存储空间的地址,数据“Hello World1”为字符串常量,所以存储在静态存储区。
虽然通过p[2]可以访问到静态存储区中的第三个数据单元,即字符‘l’所在的存储的单元。
但是因为数据“Hello World1”为字符串常量,不可以改变,所以在程序运⾏时,会报告内存错误。
linux下编程遇到的一个疑难杂症——野指针所带来的害处分类:LINUX网络编程2012-04-12 14:15 653人阅读评论(0) 收藏举报场景:一段代码,单进程多线程模式,除了main线程还有很多个子线程,里面大量地使用了指针,代码编译没有任何的warning和error;程序跑起来后一切正常,并和另外一个程序通过socket 网络通信,也一切正常;所有的子线程退出也正常;所有类的析构函数析构的时候也正常。
但是:就在main进程销毁(或者通过exit(0))退出的时候,操作系统就提示以下错误:*** glibc detected *** ./fsvspsiu: corrupted double-linked list: 0x08484100 ***======= Backtrace: =========/lib/libc.so.6[0x3bce1b]/lib/libc.so.6[0x3be7fb]/lib/libc.so.6(cfree+0x90)[0x3c20f0]./fsvspsiu[0x804d4c5]./fsvspsiu[0x804c411]./fsvspsiu[0x804a134]/lib/libc.so.6(exit+0xee)[0x38163e]/lib/libc.so.6(__libc_start_main+0xe8)[0x36b398]./fsvspsiu(__gxx_personality_v0+0x69)[0x8049211]======= Memory map: ========00110000-00111000 r-xp 00110000 00:00 0 [vdso]00336000-00351000 r-xp 00000000 fd:00 332652 /lib/ld-2.7.so00351000-00352000 r-xp 0001a000 fd:00 332652 /lib/ld-2.7.so00352000-00353000 rwxp 0001b000 fd:00 332652 /lib/ld-2.7.so00355000-004a8000 r-xp 00000000 fd:00 332653 /lib/libc-2.7.so004a8000-004aa000 r-xp 00153000 fd:00 332653 /lib/libc-2.7.so004aa000-004ab000 rwxp 00155000 fd:00 332653 /lib/libc-2.7.so004ab000-004ae000 rwxp 004ab000 00:00 0004b0000-004d7000 r-xp 00000000 fd:00 332657 /lib/libm-2.7.so004d7000-004d8000 r-xp 00026000 fd:00 332657 /lib/libm-2.7.so004d8000-004d9000 rwxp 00027000 fd:00 332657 /lib/libm-2.7.so004e2000-004f7000 r-xp 00000000 fd:00 332655 /lib/libpthread-2.7.so004f7000-004f8000 r-xp 00014000 fd:00 332655 /lib/libpthread-2.7.so004f8000-004f9000 rwxp 00015000 fd:00 332655 /lib/libpthread-2.7.so004f9000-004fb000 rwxp 004f9000 00:00 000cab000-00cb6000 r-xp 00000000 fd:00 332676 /lib/libgcc_s-4.1.2-20070925.so.100cb6000-00cb7000 rwxp 0000a000 fd:00 332676 /lib/libgcc_s-4.1.2-20070925.so.103694000-03774000 r-xp 00000000 fd:00 415472 /usr/lib/libstdc++.so.6.0.803774000-03778000 r-xp 000df000 fd:00 415472 /usr/lib/libstdc++.so.6.0.803778000-03779000 rwxp 000e3000 fd:00 415472 /usr/lib/libstdc++.so.6.0.803779000-0377f000 rwxp 03779000 00:00 008048000-08052000 r-xp 00000000 00:14 85112 /mnt/hgfs/d/fsvsp/src/pf/siu/fsvspsiu08052000-08053000 rw-p 00009000 00:14 85112 /mnt/hgfs/d/fsvsp/src/pf/siu/fsvspsiu08053000-08057000 rw-p 08053000 00:00 008452000-08486000 rw-p 08452000 00:00 0b5600000-b5621000 rw-p b5600000 00:00 0b5621000-b5700000 ---p b5621000 00:00 0b5718000-b5719000 ---p b5718000 00:00 0b5719000-b6119000 rw-p b5719000 00:00 0b6119000-b611a000 ---p b6119000 00:00 0b611a000-b6b1a000 rw-p b611a000 00:00 0b6b1a000-b6b1b000 ---p b6b1a000 00:00 0b6b1b000-b751b000 rw-p b6b1b000 00:00 0b751b000-b751c000 ---p b751b000 00:00 0b751c000-b7f1f000 rw-p b751c000 00:00 0bfe86000-bfe9b000 rw-p bffea000 00:00 0 [stack]Aborted1. 通过一翻周折,终于找到了问题的根源:看上面操作系统提示信息的第五行( /lib/libc.so.6(cfree+0x90)[0x3c20f0] ),可以估计应该是堆内存被free的时候出了问题,那么free()函数最有可能会出现问题的情况是什么呢——堆被多次释放了。
C语⾔指针详解之野指针⽬录指针是什么?怎么表⽰?什么是指针变量?指针类型⼜是什么?指针类型存在的意义野指针是什么?野指针产⽣的原因⼀、指针未初始化⼆、指针越界访问如何避免野指针(野狗)的出现呢?指针运算总结指针是什么?指针只是⼀个名词⽽已,指针就是地址。
我们平时说指针,也可以指指针变量。
怎么表⽰?类型名指针变量 = 地址;例如:int* pa = &a;//我们这⾥的指针类型叫做int*,我读做(yin te 星号)。
//pa是指针变量这段表达式的意思是:定义了⼀个指针变量pa,⾥⾯存放的是a的地址。
⽽这个指针变量的类型为int*。
那下⾯就有同学疑惑了,那什么是指针变量?什么是指针变量?很简单,在之前我们学习了怎么定义整型变量a。
⽐如定义⼀个《整型》《变量a》,然后把a初始化为10。
int a = 10;不过现在变了,我们现在学习了指针。
可以定义⼀个《int*》《变量pa》,然后把pa初识化为&a。
注意:int* 是⼀个类型。
叫做指针类型pa就叫做指针变量int* pa = &a;指针类型⼜是什么?既然变量有不同的类型,⽐如整型,浮点型等。
那么指针也有也有不同的类型。
char *pc = NULL;int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;//NULL为空指针。
这⾥可以看到,指针的定义⽅式是:类型 + * 。
其实:char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
指针类型存在的意义那有这么多的指针的类型,指针类型的意义是什么?我们在这⾥先说两个重要结论:指针的类型决定了指针向前或者向后⾛⼀步(也就是地址+1)有多⼤(能⾛多少个字节)指针的类型决定了,对指针解引⽤的时候有多⼤的权限(能操作⼏个字节)。
CC++野指针野指针:野指针不同于空指针,空指针是指⼀个指针的值为null,⽽野指针的值并不为null,野指针会指向⼀段实际的内存,只是它指向哪⾥我们并不知情,或者是它所指向的内存空间已经被释放,所以在实际使⽤的过程中,我们并不能通过指针判空去识别⼀个指针是否为野指针。
避免野指针只能靠我们⾃⼰养成良好的编程习惯,下⾯说说哪些情况下会产⽣野指针,以及怎样避免。
1. 指针变量的值未被初始化:声明⼀个指针的时候,没有显⽰的对其进⾏初始化,那么该指针所指向的地址空间是乱指⼀⽓的。
如果指针声明在全局数据区,那么未初始化的指针缺省为空,如果指针声明在栈区,那么该指针会随意指向⼀个地址空间。
所以良好的编程习惯就是在声明指针的时候就对其进⾏初始化,如果暂时不知道该初始化成什么值,就先把指针置空。
void func(){int *ptr; // 野指针if (ptr != nullptr) {……do_somthing}}2.指针所指向的地址空间已经被free或delete:在堆上malloc或者new出来的地址空间,如果已经free或delete,那么此时堆上的内存已经被释放,但是指向该内存的指针如果没有⼈为的修改过,那么指针还会继续指向这段堆上已经被释放的内存,这时还通过该指针去访问堆上的内存,就会造成不可预知的结果,给程序带来隐患,所以良好的编程习惯是:内存被free或delete后,指向该内存的指针马上置空。
void func(){int *ptr = new int[5];delete []ptr;// 执⾏完delete后,ptr野指针}3.指针操作超越了作⽤域:void func(){int *ptr = nullptr;{int a = 10;ptr = &a;} // a的作⽤域到此结束int b = *ptr; // ptr指向a,a已经被回收,ptr野指针}。
什么是野指针? 今天偶然看到了C++中有关野指针的概念,就到百度和博客园查了⼀下,还是挺有收获的。
野指针的定义:“野指针”不是NULL指针,是指向“垃圾”内存(不可⽤内存)的指针。
⼈们⼀般不会错⽤NULL指针,因为⽤if语句很容易判断。
但是“野指针”是很危险的,if⽆法判断⼀个指针是正常指针还是“野指针”。
有个良好的编程习惯是避免“野指针”的唯⼀⽅法。
野指针形成的成因:⼀、没有被初始化。
任何刚被创建时不会⾃动成为NULL指针,它的缺省值是随机的,它会乱指⼀⽓。
所以,在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
⼆、指针p被free或者delete之后,没有置为NULL,让⼈误以为p是个合法的指针。
别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本⾝⼲掉。
通常会⽤语句if (p != NULL)进⾏防错处理。
很遗憾,此时if语句起不到防错作⽤,因为即便p不是NULL指针,它也不指向合法的内存块。
例: #include <stdio.h> #include <string.h> #include <malloc.h> int main(void) { int i; char *p = (char *) malloc(100); strcpy(p, "hello"); free(p); // p 所指的内存被释放,但是p所指的地址仍然不变,原来的内存变为“垃圾”内存(不可⽤内存 if(p != NULL) // 没有起到防错作⽤ strcpy(p, "world"); for(i=0;i<5;i++) //i=5后为乱码 printf("%c",*(p+i)); printf("\n"); } 另外⼀个要注意的问题:不要返回指向栈内存的指针或引⽤,因为栈内存在函数结束时会被释放。
中国高新技术企业文/彭程杨春生C语言指针操作技巧探讨【摘要】指针增加了我们控制程序的灵活性,但是指针使用不当就会出现野指针,危害整个程序的运行,所以在程序中使用指针时应十分小心,养成良好的编码习惯,避免出现野指针。
【关键词】C语言指针技巧野指针1.指针概述C语言中,声明一个变量即表示在计算机中为该变量开辟一块内存空间,变量的值即该内存中所保存的值,可以使用变量名对该内存的内容进行访问或修改,或者通过先求出该变量所占内存的地址,然后直接对该内存进行访问或修改。
指针是一个变量,其值为所指向变量的内存地址。
指针变量的声明为:类型标识符*标识符;其中“类型标识符”表示该指针变量所指向变量的类型,标识符是该指针变量的名字。
如:inti=10;int*p=&i;此时,p即为一个指向int类型的指针,其值为一个整数,表示变量i所占内存的地址,而*p即为该内存所保留的值,此时为10。
该例中,运算符&执行取地址操作,即&i表示变量i的地址。
此时可以通过指针变量p对变量i的值进行访问或修改,如:(*p)=(*p)+5;执行该语句后,变量i的值变为15。
与一般变量一样,对于外部或静态指针变量若在定义中未初始化,指针变量将被自动初始化为NULL,即空指针。
2.野指针2.1野指针出现的原因及后果野指针即没有保存一个当时合法存在的变量地址的指针。
NULL指针很容易通过if语句进行判断以避免非法的间接访问,但是野指针却没有简单的方法可以判断。
访问野指针将会导致错误的程序结果,甚至使程序崩溃。
所以在程序编码时应避免野指针以保证程序的正常运行。
下面从四个方面说明了野指针产生的原因,并就如何避免野指针进行了探讨。
2.2如何避免野指针2.2.1指针在使用前未初始化指针变量在被创建时若未进行初始化,外部指针变量和局部静态指针变量将被自动地初始化维NULL指针,而局部自动指针变量、寄存器指针变量将不会自动初始化,这些变量的值是随机的。
此时的指针变量就是野指针,此时访问该指针变量可能导致程序崩溃。
什么是野指针和段错误1、野指针与段错误问题1.1、什么是野指针?所谓野指针就是,指针指向一个不确定的地址空间,或者指向的是一个确定的地址空间的,但引用空间的结果却是不可预知的,这样的指针就称为野指针。
例子1:int main(void) {int *p;*p = 10;return 0;}本例子中,p是自动局部变量,由于p没有被初始化,也没有被后续赋值,那么p中存放的是一个随机值,所以p指向的内存空间是不确定的,访问一个不确定的地址空间,结果显然是不可预知的。
例子1:int main(void) {int *p=0x13542354;*p = 10;return 0;}本例子中,p虽然是指向了一个确定地址“0x43542354”的空间,但是它对应的空间是否存在,其读写权限是否满足程序的访问要求,都是未知数,所以导致的结果也是未知的。
通过以上两个例子了解到,在给指针变量绑定地址,指向某个空间时,一定要是确定的,不能出现不可预知性,一旦出现未知性它就是一个野指针,即使某一次没有产生严重后果,但埋下了这颗地雷后,就留下了不可预知的隐患,对于程序来说这是不可接受的。
1.2、野指针可能引发的危害(1)引发段错误段错误就是地址错误,其实是一种对程序和系统的保护性措施,一旦产生段错误,程序会立即终止,防止错误循环叠加,产生雪崩式的错误。
(2)未产生任何结果有的时候,可能使用了一个野指针,虽然指向了一个未知的地址空间,但是这空间可以使用,而且该空间和程序中的其它变量空间没有交集,对野指针指向的空间进行了读写访问后,也不会对程序产生任何影响,虽然如此,这种野指针也是必须要极力避免的,因为这个隐患很可能在后面的程序运行中,导致严重的错误,而且这种错误很难排查。
(3)引发程序连环式错误访问野指针产生的错误循环叠加,轻者程序结果严重扭曲,重者直接导致程序或者系统崩溃。
1.3、野指针产生的原因野指针产生的原因大概有如下三种:(1)在使用指针前,忘记了给指针变量初始化或者赋值一个有效的空间地址,导致指向的不确定。
寻找“野指针”
本文介绍了一种在调试过程中寻找悬挂指针(野指针)的方法,这种方法是通过对new和delete运算符的重载来实现的。
这种方法不是完美的,它是以调试期的内存泄露为代价来实现的,因为文中出现的代码是绝不能出现在一个最终发布的软件产品中的,只能在调试时使用。
在VC中,在调试环境下,可以简单的通过把new替换成DEBUG_NEW来实现功能更强更方便的指针检测,详情可参考MSDN。
DEBUG_NEW的实现思路与本文有相通的地方,因此文章中介绍的方法虽然不是最佳的,但还算实用,更重要的是,它提供给我们一种新的思路。
简介:
前几天发生了这样一件事,我正在调试一个程序,这个程序用了一大堆乱七八糟的指针来处理一个链表,最终在一个指向链表结点的指针上出了问题。
我们预计它应当指向的是一个虚基类的对象。
我想到第一个问题是:指针所指的地方真的有一个对象吗?出问题的指针值可以被4整除,并且不是NULL的,所以可以断定它曾经是一个有效的指针。
通过使用Visual Studio的内存查看窗口(View->Debug Windows->Memory)我们发现这个指针所指的数据是FE EE FE EE FE EE ...这通常意味着内存是曾经是被分配了的,但现在却处于一种未分配的状态。
不知是谁、在什么地方把我的指针所指的内存区域给释放掉了。
我想要找出一种方案来查出我的数据到底是怎么会被释放的。
背景:
我最终通过重载了new和delete运算符找到了我丢失的数据。
当一个函数被调用时,参数会首先被压到栈上后,然后返回地址也会被压到栈上。
我们可以在new和delete运算符的函数中把这些信息从栈上提取出来,帮助我们调试程序。
代码:
在经历了几次错误的猜测后,我决定求助于重载new和delete运算符来
帮我找到我的指针所指向的数据。
下面的new运算符的实现把返回地址从栈上提了出来。
这个返回地址位于传递过来的参数和第一个局部变量的地址之间。
编译器的设置、调用函数的方法、计算机的体系结构都会引响到这个返回地址的实际位置,所以您在使用下面代码的时候,要根据您的实际情况做一些调整。
一旦new运算符获得了返回地址,它就在将要实际分配的内存前面分配额外的16个字节的空间来存放这个返回地址和实际的分配的内存大小,并且把实际要分配的内存块首地址返回。
对于delete运算符,你可以看到,它不再释放空间。
它用与new同样的方法把返回地址提取出来,写到实际分配空间大小的后面(译者注:就是上面分配的16个字节的第9到第12个字节),在最后四个字节中填上DE AD BE EF(译者注:四个十六进制数,当成单词来看正好是dead beef,用来表示内存已释放真是很形象!),并且把剩余的空间(译者注:就是原本实际应该分配而现在应该要释放掉的空间)都填上一个重复的值。
现在,如果程序由于一个错误的指针而出错,我只需打开内存查看窗口,找到出错的指针所指的地方,再往前找16个字节。
这里的值就是调用new运算符的地址,接下来四个字节就是实际分配的内存大小,第三个四个字节是调用delete运算符的地址,最后四个字节应该是DE AD BE EF。
接下的实际分配过的内存内容应该是77 77 77 77。
要通过这两个返回地址在源程序中分别找到对应的new和delete,可以这样做:首先把表示地址的四个字节的内容倒序排一下,这样才能得到真正的地址,这里因为在Intel平台上字节序是低位在前的。
下一步,在源代码上右击点击,选“Go To Diassembly”。
在反汇编的窗口上的左边一栏就是机器代码对应的内存地址。
按Ctrl + G或选择Edit->Go To...并输入你找到的地址之一。
反汇编的窗口就将滚动到对应的new或delete的函数调用位置。
要回到源程序只需再次右键单击,选择“Go To Source”。
您就可以看到相应的new或delete 的调用了。
现在您就可以很方便的找出您的数据是何时丢失的了。
至于要找出为什么
delete会被调用,就要靠您自己了。
#include <MALLOC.H>
void * ::operator new(size_t size)
{
int stackVar;
unsigned long stackVarAddr = (unsigned long)&stackVar;
unsigned long argAddr = (unsigned long)&size;
void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);
void * retAddr = * retAddrAddr;
unsigned char *retBuffer = (unsigned
char*)malloc(size + 16);
memset(retBuffer, 0, 16);
memcpy(retBuffer, &retAddr, sizeof(retAddr));
memcpy(retBuffer + 4, &size, sizeof(size));
return retBuffer + 16;
}
void ::operator delete(void *buf)
{
int stackVar;
if(!buf)
return;
unsigned long stackVarAddr = (unsigned long)&stackVar;
unsigned long argAddr = (unsigned long)&buf;
void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);
void * retAddr = * retAddrAddr;
unsigned char* buf2 = (unsigned char*)buf;
buf2 -= 8;
memcpy(buf2, &retAddr, sizeof(retAddr));
size_t size;
buf2 -= 4;
memcpy(&size, buf2, sizeof(buf2));
buf2 += 8;
buf2[0] = 0xde;
buf2[1] = 0xad;
buf2[2] = 0xbe;
buf2[3] = 0xef;
buf2 += 4;
memset(buf2, 0x7777, size);
// deallocating destroys saved addresses, so don't
// buf -= 16;
// free(buf);
}
其它值得关注的地方:
这段代码同样可以用于内存泄露的检测。
只需修改delete运算符使它真正
的去释放内存,并且在程序退出前,用__heapwalk遍历所有已分配的内存块并把调用new的地址提取出来,这就将得到一份没有被delete匹配的new调用列表。
还要注意的是:这里列出的代码只能在调试的时候去使用,如果你把它段代码放到最终的产品中,会导致程序运行时内存被大量的消耗。