与内存有关的错误,属于那种最令人惊恐的错误。在时间和空间上,经常在距离错误源一段距离之后才表现出来。将错误的数据写到错误的位置,你的程序可能在最终失败之前运行了一段时间。
下面列举并分析了与内存有关的几种错误:
1、间接引用坏指针
如果间接引用一个指向没有任何意义的数据的指针,那么操作系统会以段异常终止程序。如果向只读区域中写入数据,这些区域会以保护异常终止这个程序。
一个常见的经典示例是scanf错误。这个函数用处是从标准输入读入一个整数到一个变量,正确的写法是传递给scanf一个格式串和变量的地址:
scanf(%d, &value);
然而,常见的书写错误如下:
scanf(%d, value);
这种情况下,scanf将把value内容解释为一个地址,并试图将一个字写到这个位置。这会导致程序出现异常,有时会立即终止;有时会在相当长的时间后造成灾难性、令人困惑的后果。
2、读未初始化的内存
常见的错误是假设堆内存被初始化为零:
int *matvec(int **a, int *x, int n){ int i, j; int *y = (int *)malloc(n * sizeof(int)); for(i = 0; i < n; i++) { for(j = 0; j < n; j++) { y[i] += a[i][j] * x[j] } } return y;}示例中不应该假设新申请的内存地址(y指向的地址)被初始化为零;正确的做法是显式地将y[i]设置为零,或者使用calloc申请内存。 3、栈缓冲区溢出
如果一个程序不检查输入字符串的大小就写入栈中目标缓冲区,那么这个程序就会出现缓冲区溢出的错误,如下程序:
void buff(){ char buf[64]; gets(buf); return;}这个函数会出现缓冲区溢出错误,因为gets函数只是简单复制一个任意长度的字符串到缓冲区,不限制输入串的大小。解决这个问题的方法是,可以用限制了输入串大小的fgets函数。 4、假设指针和它们指向的对象大小相同
常见的错误是,假设指向对象的指针和它们所指向的对象是相同大小的,示例程序:
int **makearray(int n, int m){ int i; int **a = (int **)malloc(n * sizeof(int)); /* 注意此处语句,存在问题 */ for(i = 0; i < n; i++) { a[j] = (int *)malloc(m * sizeof(int)); } return a;}此程序的目的是创建一个由n个指针组成的数组,每个指针都指向一个包含m个int的数组。然而,第4行程序代码将sizeof(int *)写成了sizeof(int),代码实际上创建的是一个int的数组。
这段代码只有在int和指向int的指针大小相同的机器上运行良好,否则就会出现错误。 5、内存越界
这种错误会越界覆盖原有内存的数据,导致出错:
int **makearray(int n, int m){ int i; int **a = (int **)malloc(n * sizeof(int)); /* 注意此处语句,存在问题 */ for(i = 0; i <= n; i++) /* 注意循环终止条件 */ { a[j] = (int *)malloc(m * sizeof(int)); } return a;}程序在第6行和第8行试图初始化这个数组的n+1个元素,这个过程会覆盖a数组后面的某个内存位置。 6、引用指针,而不是它所指向的对象
如果不太注意c操作符的优先级和结合性,我们就会错误地操作指针,而不是指针所指向的对象。如果想要减少某个指针指向的整数的值,代码书写如下:
*ptr--;
然而,因为一元运算符“--”和“*”的优先级相同,且从右向左结合。那么上述代码实际的效果为*(ptr--),即减少的是指针自己的值,而不是它所指向的整数的值。
如果对优先级和结合性有疑问的时候,就用括号。修正后的代码如下:
(*ptr)--; 7、误解指针运算
这类错误是忘记指针的算术运算操作是如何进行,是以指针指向的对象的大小为单位进行的,而这种大小单位并不一定是字节。 例如,扫描一个int的数组,并返回一个指向val首次出现的指针:
int *search(int *p, int val){ while(*p && *p != val) { p += sizeof(int); } return p;}每次循环时,第5行都把指针加了4(一个整数的字节数),函数就不正确地扫描了数组中每4个整数。 8、引用不存在的变量
有的c程序员不太理解栈的规则,有时会引用不再合法的局部变量,如下所示:
int *stackref(){ int val; return &val;}
这个函数返回一个指针(假设为ptr),指向栈里的一个局部变量,然后弹出它的栈帧。尽管ptr仍然指向一个合法的内存地址,但它已经不再指向一个合法的变量了。
以后在程序中调用其他函数时,内存将重用它们的栈帧。如果程序赋值给*ptr,那么它可能实际上正在修改另一个含的栈帧中的数据,从而潜在地带来灾难性的后果。
9、引用空闲堆块中的数据
引用已经被释放了的堆块中的数据会导致出错。例如:
int *heapref(int n, int m){ int i; int *x, *y; x = (int *)malloc(n * sizeof(int)); /* 申请内存 */ ... free(x); /* 释放内存 */ y = (int *)malloc(m * sizeof(int)); for(i = 0; i < m; i++) { y[i] = x[i]++; } return y;}
当程序在第15行引用x[i]时,数组x可能已经是某个其他已分配堆块的一部分了,其内容也许被重写了。导致程序运行结果与预期不符合,出现错误。
10、引起内存泄漏
内存泄漏是缓慢、隐形的杀手,当程序员不小心忘记释放已分配的内存块,而在堆里创建了垃圾时,会发生这种问题。如下:
void leak(int n){ int *x = (int *)malloc(n * sizeof(int)); return;}如果经常调用这个函数,渐渐地堆里会充满了垃圾,造成内存泄漏。另外,有时也会引起程序终止或其他问题。 小结
以上总结了c程序中,管理和使用内存常见的错误类型,并举例进行了说明。在实际的编程中,应该避免出现这些错误,否则会出现意想不到的后果。
SAP S/4 HANA 与R3(ECC) 的区别
特斯拉:上海超级工厂Model 3周产5000辆 已达规划产能速度
华为摄像头传感器
谷歌Nexus 6P开启无限重启模式:用户简直不能忍
现有AC/DC转换器的课题是效率与尺寸
关于C程序中10个与内存有关的常见错误
E现场:唤醒创造力的Xperia 1
荣耀Earbuds 2 SE获首销全天京东销量冠军,赵明称未来重量级产品将逐一亮相
ME300实例-DS18B20温度控制实验(汇编语言源程序)
OpenHarmony开发者大会召开 携手共建使能千行百业的数字底座
华为mate10什么时候上市?华为mate10价格确认:国产超强旗舰,秒杀三星note8
UPS逆变器的重复控制器参数的仿真分析
苹果三款新iPhone曝光 将使用新一代A系列处理器FaceID也可能会升级
如何选择适合你的中小户型的迷你净水器
充电接口另一种形式无线充电会有怎样的未来
黑芝麻华山二号芯片发布,成为唯一可支持L3自动驾驶的国产芯片
抖音为什么要做支付?移动支付会变天吗?
电烙铁发光提示和调温电路
联想Z5ProGT855版将推迟到2019年1月29日发售
三种具有代表性的色度信息处理方法