C之内存操作经典问题解析(三十七)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C之内存操作经典问题解析(三十七)相关的知识,希望对你有一定的参考价值。
在 C 语言中,野指针是一个常见的内存错误。那么野指针到底是什么呢?指针变量中的值是非法的内存地址,进而形成野指针;野指针不是 NULL 桌子,是指向不可用内存地址的指针;NULL 指针并无危害,很好判断也很调试;在 C 语言中无法判断一个指针所保存的地址是否合法,所以我们必须得杜绝野指针!
那么野指针到底是怎么来的呢?来源有这么几方面:1、局部指针变量没有被初始化;2、指针所指向的变量在指针之前被销毁;3、使用已经释放过的指针;4、进行了错误的指针运算;5、进行了错误的强制类型转换。
下来我们以代码为例进行分析,代码如下
#include <stdio.h> #include <malloc.h> int main() { int* p1 = (int*)malloc(40); int* p2 = (int*)1234567; int i = 0; for(i=0; i<40; i++) { *(p1 + i) = 40 - i; } free(p1); for(i=0; i<40; i++) { p1[i] = p2[i]; } return 0; }
我们看到第 7 行进行了错误的强制类型转换,在第 12 行进行指针的赋值,但是由于申请的只有40字字节的大小,但是我们进行40个 int 类型的数赋值,这个进越界啦。我们在第 15 行释放了指针 p1,但是在第 19 行又使用了指针 p1,这也会出错。我们看看编译后的结果
我们看到直接报段错误了,这便是操作了野指针带来的影响。我们在 C 语言中有这么几个基本原则:a> 绝不返回局部变量和局部数组的地址;b> 任何变量在定义后必须 0 初始化;c> 字符数组必须确认 0 结束符后才能成为字符串;d> 任何使用与内存相关的函数必须指定长度信息。
我们再看一份代码,在这份代码里野指针更是无处不在
#include <stdio.h> #include <string.h> #include <malloc.h> struct Student { char* name; int number; }; char* func() { char p[] = "D.T.Software"; return p; } void del(char* p) { printf("%s\n", p); free(p); } int main() { struct Student s; char* p = func(); strcpy(s.name, p); s.number = 99; p = (char*)malloc(5); strcpy(p, "D.T.Software"); del(p); return 0; }
我们首先在第27行定义了一个结构体变量 s,但是结构体里的指针类型的成员变量没进行初始化。接着定义指针 p 并调用 func 函数进行初始化,但是在 func 函数中返回了局部数组的地址。在第34行申请了5个char*类型大小的空间,在第36行进行字符串的复制,但是越界了。我们看到在这份代码中,野指针是无处不在的。
内存错误是实际产品开发中最常见的问题,然而绝大多数 bug 都可以通过遵循基本的编程原则和规范来避免。因此,我们在学习的时候要牢记和理解内存操作的基本原则,目的和意义,这样才能达到最小程度的减少 bug。
常见的内存错误有:a> 结构体成员指针未初始化;b> 结构体成员指针未分配足够的内存;c> 内存分配成功,但并未初始化;d> 内存操作越界。
我们再以代码为例进行分析说明
#include <stdio.h> #include <malloc.h> void test(int* p, int size) { int i = 0; for(i=0; i<size; i++) { printf("%d\n", p[i]); } free(p); } void func(unsigned int size) { int* p = (int*)malloc(size * sizeof(int)); int i = 0; if( size % 2 != 0 ) { return; } for(i=0; i<size; i++) { p[i] = i; printf("%d\n", p[i]); } free(p); } int main() { int* p = (int*)malloc(5 * sizeof(int)); test(p, 5); free(p); func(9); func(10); return 0; }
我们看到在程序的第37行申请了堆空间 ,用指针 p 指向这片堆空间。调用了 test 函数,但是在 test 函数内部进行了指针的释放。这是错误的 ,free 是用来释放堆上申请的空间的,但在这块我们释放的是栈上的内存,会造成段错误。我们在第41行继续释放 p,造成了指针的重复释放。我们在 func 函数中如果传入的是奇数便直接返回,申请的内存空间也没有释放,这样会造成内存泄漏。
编译结果如下
再来看一个程序
#include <stdio.h> #include <malloc.h> struct Demo { char* p; }; int main() { struct Demo d1; struct Demo d2; char i = 0; for(i='a'; i<'z'; i++) { d1.p[i] = 0; } d2.p = (char*)calloc(5, sizeof(char)); printf("%s\n", d2.p); for(i='a'; i<'z'; i++) { d2.p[i] = i; } free(d2.p); return 0; }
我们看到定义的两个结构体,他们的指针成员变量没有进行初始化,在第21行申请堆空间,在第27行进行赋值,但是内存越界了。我们看看编译结果
同样直接报段错误。那么我们在动态内存申请之后,应立即检查指针值是否为 NULL,防止使用 NULL 指针。free 指针之后必须立即赋值为 NULL!任何与内存操作相关的函数都必须带长度信息。malloc 操作和 free 操作必须匹配,防止内存泄漏和多次释放。
内存错误的本质来源于指针保存的地址为非法值,指针变量为初始化,保存随机值。指针运算导致内存越界。内存泄漏源于 malloc 和 free 不匹配,当 malloc 次数多于 free时,产生内存泄漏;当 malloc 次数少于 free 时,程序可能崩溃!
欢迎大家一起来学习 C 语言,可以加我QQ:243343083。
以上是关于C之内存操作经典问题解析(三十七)的主要内容,如果未能解决你的问题,请参考以下文章
Linux学习总结(三十七)lamp之禁止php解析 user_agent 访问控制 php相关配置