常见C语言内存错误
前言
C语言强大的原因之一在于几乎能掌控所有的细节,包括对内存的处理,什么时候使用内存,使用了多少内存,什么时候该释放内存,这都在程序员的掌控之中。而不像Java中,程序员是不需要花太多精力去处理垃圾回收的事情,因为有JVM在背后做着这一切。但是同样地,能力越大,责任越大。不恰当地操作内存,经常会引起难以定位的灾难性问题。今天我们就来看看有哪些常见的内存问题。
初始化堆栈中的数据
对申请的内存或自动变量进行初始化是一个好习惯,例如:
int test()
{
int *a = (int*) malloc(10);
/*判断是否申请成功*/
if(NULL == a)
{
return -1;
}
/*将其初始化为0*/
memset(a,0,10);
/*do something*/
return 0;
}
我们经常需要在使用前将其初始化为0或使用calloc申请内存。关于初始化,在《C语言入坑指南-被遗忘的初始化》一文中,有更详细的阐述。
缓冲区溢出
缓冲区溢出通常指的是向缓冲区写入了超过缓冲区所能保存的最大数据量的数据。同样的,缓冲区溢出通常也伴随着难以定位的问题。例如下面的代码就存在缓冲区溢出的可能:
/*bad code*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char buff[8] = {0};
char *p = "0123456789";
strcpy(buff,p);
printf("%s\n",buff);
return 0;
}
关于缓冲区溢出,可以通过《C语言入坑指南-缓冲区溢出》一文了解更多。
指针不等同于其指向的对象
我们可能常常错误性地认为指针对象的大小就是数据本身的大小,最常错误使用的就是下面的情况:
/*bad code*/
int test(int a[])
{
size_t len = sizeof(a)/sizeof(int);
/*do something*/
}
这里计算数组a的长度偶尔能够如愿,但实际上是错误的,因为数组名作为参数时,是指向该数组下标为0的元素的指针。因此sizeof(a)的值会是4或者8(取决于程序的位数)。
指针运算以指向对象大小为单位
对于下面的代码,ptr1 + 1之后,到底移动了多少个字节?ptr2 + 1呢?
int arr[] = {1,2,3};
int *ptr1 = arr;
char *ptr2 = (char*)arr;
实际上,它们移动的字节数,是以其指向对象大小为单位的。即ptr1 + 1会移动4字节(int类型),而ptr2 + 1 会移动1字节(char类型)。
下面的代码运行结果是什么?
#include<stdio.h>
int main(void)
{
int a[5] = {1,2,3,4,5};
int *p = (int*)(&a+1);
printf("%d,%d",*(a+1),*(p-1));
return 0;
}
问题的答案也可在《C语言入坑指南-数组之谜》中找到。
不可引用已释放的内存
对于下面的代码:
/*bad code*/
char *getHelloString()
{
char string[] = "hello";
return string;
}
在其他地方调用getHelloString之后,如果再使用printf打印string,显然是不可取的。因为在调用返回之后,string所指向的内存已经释放了。有人可能会问了,为什么返回int类型就可以使用呢?比如:
int getInt()
{
int a = 10;
return a;
}
调用getInt显然能够得到a的值,这是为什么呢?因为你实际上返回的就是值10,而前面返回的是string的地址,这个值你也能获取,但是要获取这个地址值指向的内存,已经不可行了。
下面的情况也是应该避免的:
/*bad code*/
int *a = (int*)malloc(10);
/*do something*/
free(a);
a[0] = 10; //内存已经被释放,不可再引用
在这个例子中可能很容易发现问题,但是在大型程序中,这样的问题可能很难发现,一个建议就是在释放a的内存后,显式地将a置为NULL。即:
free(a);
a = NULL;
避免对NULL解引用
对于上面的例子,a置NULL之后还不够,我们需要经常对入参进行检查,避免对NULL解引用。这样就避免引用已经释放的内存。例如:
int calcSum(int *arr,int len)
{
/*入惨检查,避免引用空指针*/
if(NULL == arr || 0 == len)
{
return -1;
}
/*do something*/
return 0;
}
当然了,在C++中可以传引用,而避免这种重复的检查性代码。
下面的代码,同样也是有问题的:
char *str = NULL;
printf("%s",str);
这里str为NULL,却将其作为字符串打印,后果将是灾难性的。
申请的内存不使用时需要释放
使用malloc等申请的内存如果不使用free进行释放,将会引起内存泄露。长期运行将会导致可用内存越来越少,程序也将会变得越来越卡顿。
/*bad code*/
int doSomething(void *data,size_t len)
{
if(NULL == data)
{
return -1;
}
int *p = (int*)malloc(len);
/*do something*/
return 0;
}
在这里,doSomething中申请了内存却没有释放,多次调用之后,将导致内存泄露。也就是说,malloc或calloc与free经常是成对出现的。
总结
如果控制不当,强大的同时,也会造成更多的危害。上面所列出的仅仅是一些比较常见的内存相关问题,总结如下:
- 自动变量或申请的内存需要初始化
- 避免缓冲区溢出
- 指针不等同于指向的对象
- 指针运算以指向大小为单位
- 避免对NULL或已释放的内存进行引用
- 申请的内存不使用时及时释放
- 使用printf打印字符串时避免使用空指针
你踩过哪些坑?欢迎留言评论。
思考
下面的代码有什么问题?
int *arr = (int*)malloc(10);
/*do something*/
arr++;
free(arr);
公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,Python,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。
常见C语言内存错误的更多相关文章
- 常见C语言编译错误解析【转】
C语言编译错误信息及说明1. 在函数 ‘transform’ 中:7: 错误:expected ‘;’ before ‘{’ token 解释:‘{’之前的某个语句缺少分号‘;’: 2. 在函数 ...
- 常见的C语言内存错误及对策(转)
http://see.xidian.edu.cn/cpp/html/483.html 一.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存.浅显的例子就 ...
- [转]C++常见内存错误汇总
在系统开发过程中出现的bug相对而言是比较好解决的,花费在这个上面的调试代价不是很大,但是在系统集成后的bug往往是难以定位的bug(最好方式是打桩,通过打桩可以初步锁定出错的位置,如:进入函数前打印 ...
- C语言内存管理(转)
伟大的Bill Gates 曾经失言: 640K ought to be enough for everybody — Bill Gates 1981 程序员们经常编写内存管理程序,往往提心吊胆.如果 ...
- C语言内存对齐详解
一.字节对齐基本概念 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型 ...
- C语言内存对齐原理
一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这 ...
- C语言内存调试技巧—C语言最大难点揭秘
本文将带您了解一些良好的和内存相关的编码实践,以将内存错误保持在控制范围内.内存错误是 C 和 C++ 编程的祸根:它们很普遍,认识其严重性已有二十多年,但始终没有彻底解决,它们可能严重影响应用程序, ...
- 如何处理C++构造函数中的错误——兼谈不同语言的错误处理
用C++写代码的时候总是避免不了处理错误,一般来说有两种方式,通过函数的返回值或者抛出异常.C语言的错误处理一律是通过函数的返回值来判断的,一般是返回0.NULL或者-1表示错误,或者直接返回错误代码 ...
- mysql 常见的几个错误问题
Mysql常见的几个错误问题及解决方法: 1.问题: mysql DNS反解:skip-name-resolve 错误日志有类似警告: 点击(此处)折叠或打开 120119 16:26:04 [War ...
随机推荐
- YARN集群的mapreduce测试(五)
将user表计算后的结果分区存储 测试准备: 首先同步时间,然后master先开启hdfs集群,再开启yarn集群:用jps查看: master上: 先有NameNode.SecondaryNameN ...
- RNN入门(一)识别MNIST数据集
RNN介绍 在读本文之前,读者应该对全连接神经网络(Fully Connected Neural Network, FCNN)和卷积神经网络( Convolutional Neural Netwo ...
- C#文件操作。
文件操作常用相关类: File: 操作文件,静态类,对文件整体操作. Directory:操作目录(文件夹),静态类. FileInfo:文件类,用来描述一个文件对象,获取指定目录下的所有文件时,返回 ...
- https创建请求UrL报错: 未能为 SSL/TLS 安全通道建立信任关系
1.项目中异常报错如下: 2.百度结果:原来是 网站没有使用SSL证书或者是SSl证书失效了的缘故. 3.具体解决方案如下: )导入命名空间 using System.Net.Security; us ...
- 22.QT-QXmlStreamReader解析,QXmlStreamWriter写入
XML介绍 XML 用于存储数据,数据的形式类似于树结构(参考: http://www.runoob.com/xml/) 示例如下 <?xml version="1.0" e ...
- Could not get JDBC connection
想学习下JavaWeb,手头有2017年有活动的时候买的一本书,还是全彩的,应该很适合我这种菜鸟技术渣. 只可惜照着书搭建了一套Web环境,代码和db脚本都是拷贝的光盘里的,也反复检查了数据库的连接情 ...
- js发送邮件确定email地址
<a href="mailto:wjl@tom.com?subject=aaa&body=11111">test</a>
- react children
children react 中,属性名是一一对应的,除了children. 对于一个组件来说,其this.props.children拿到的是什么呢???举个
- JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )
ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true. 相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性. 在转换不同的数据类型 ...
- 基于Log4j完成定时创建和删除日志的方法
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 Log4j作为常用的日志生成工具,其清除日志的策略却十分有限. ...