C 中常见的内存错误

Wesley13
• 阅读 504

最近读了一下《深入理解计算机操作系统》第 9 章,虚拟存储器。在 9.11 书上总结了 C 中常见的与存储器有关的错误。书上下面这一句话说的很有道理(我看的是中文版,觉得引用原版会更加原汁原味),就是指出现某次内存操作错误,如果当时就立刻表现出来,那会很幸运,不幸的是有时会在错误操作之后一段时间后才显现出来。所以有时候发现 coredump 文件堆栈中某次很奇怪的调用居然导致崩溃了,查不出来原因,有可能是前文环境中某次指针操作错误造成的。
所以就把书上这节内容整篇笔记来提醒自己。

Memory-related bugs are among the most frightening because they often manifest themselves at a distance, in both time and space, from the source of the bug. Write the wrong data to the wrong location, and your program can run for hours before it finally fails in some distant part of the program.

■ Dereferencing Bad Pointers

scanf("%d", var); // => scanf("%d", &var)

解引用时,使用了错误的指针。 C 是静态类型的语言,需要类型匹配,但是遇见变长函数参数时,无法检测类型,传递了错误类型而未被编译器查出,从而导致了错误。

■ Reading Uninitialized Memory

int *y = (int*)malloc(sizeof(int));
printf("value:%d\n", *y);

使用未初始化内存,错误的认为 malloc 分配的存储空间会被初始化为 0 。可以自己测试,使用 malloc 直接分配 500MB 空间,然后调用 getchar 等待,此时在任务管理器中查看进程内存并没有 500MB ,但是在 malloc 后使用 memset 设置为 0 后再在任务管理器中查看进程内存空间,发现就有了 500 多 MB 。因为后者访问了 malloc 返回的虚拟地址空间,从而导致把此地址映射到真正的物理地址空间中,进而进程的内存占用增大了。

■ Allowing Stack Buffer Overflows

char buf[64];
gets(buf);

代码很直观,不检测缓冲区空间大小,在使用时导致栈缓冲区溢出。

■ Assuming that Pointers and the Objects They Point to Are the Same Size

int **parr = (int**)malloc(10 * sizeof(int)); // 分配 10 个指针变量,每个指针指向 5 个 int
for (int i = 0; i < 10; i++)
    parr[i] = (int*)malloc(5 * sizeof(int)); // 为指针变量赋值

在 32 位系统上 int 类型和指针类型都是 32 位,这段代码在 32 位系统上能正常运行但是移植到 64 位系统上就会出问题,因为 64 位系统上指针类型是 64 位,但是分配空间时却为这个 64 位的指针分配的是 32 位的大小。所以在 64 位系统上 for 循环中的赋值语句会超出实际可用的空间。若超出的部分覆盖了分配器的边界标记脚部(分配器的实现)或者覆盖了另一个 malloc 返回的空间中的值,所以可能不会立刻发现这个错误,等到调用 free 时或者另一个地址访问它自己的空间时,便会奇怪的出错。这就是因内存错误而导致的 action at a distance 的例子。

■ Making Off-by-One Errors

int n = 10, m = 5;
int **parr = (int**)malloc(n * sizeof(int*));
for (int i = 0; i <= n; i++)
    parr[i] = (int*)malloc(m * sizeof(int));

遍历数组时,使用了越界的索引。i 等 0 开始,那么最大值应该是 n - 1 而不是 n 。

■ Referencing a Pointer Instead of the Object It Points to

int m = 100;
int *p = &m;
*p--; // => (*p)--;

程序本意是想指针 p 指向的变量减一,但是不清楚 * 和 -- 的优先级相同,从而从右向左结合导致先执行 -- ,程序错误变成了使指针 p 移动了前一个单位,并访问这个地址处的 int 值。

■ Misunderstanding Pointer Arithmetic

int arr[] = {1, 2, 3, 4};
int *p = arr;
int *next = p + sizeof(int); // => int *next = p + 1;

另一种常见的错误是指针的算术操作是以它们指向的对象的大小为单位进行的。比如 int *p; p = p + 1; 代码中 p + 1 表示 p 移动 1 * sizeof(int) 4 个字节,而不是一个字节。

■ Referencing Nonexistent Variables

int* func() {
    int var = 100;
    return &var;
}

另一种常见的错误是函数返回了指向局部变量的指针,函数返回时局部变量已经无效了,尽管返回的指针是一个合法的地址,但是它已经不再指向一个合法的变量了。以后在程序中调用其它函数时,这段栈空间会被重新利用,再后来如果赋值给这个返回的无效指针,那么它可能正在修改另一个函数的栈,从而带来灾难性的后果。

■ Referencing Data in Free Heap Blocks

int n = 10;
int *p = (int*)malloc(n * sizeof(int));
free(p);
int *arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
    arr[i] = p[i];

一个相似的错误是引用了已经被释放的堆块中的数据。

■ Introducing Memory Leaks

void leak(size_t n) {
    int *arr = (int*)malloc(n * sizeof(int));
    return; // arr is garbage now
}

最后当然是万恶的内存泄漏了。

这里我是用内存( memory )代替了书中的虚拟存储器( virtual memory ),我们习惯上把 memory 翻译成内存,书上翻译的是存储器。
其实写这么多,主要是想提醒自己在实际项目中查找内存相关的问题时,除了要盯住出问题的地方,还要注意上下文中是否其他地方的错误指针操作留下的隐患。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
C# Aspose.Cells导出xlsx格式Excel,打开文件报“Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃”
报错信息:最近打开下载的Excel,会报如下错误。(xls格式不受影响)!(https://oscimg.oschina.net/oscnet/2b6f0c8d7f97368d095d9f0c96bcb36d410.png)!(https://oscimg.oschina.net/oscnet/fe1a8000d00cec3c
Stella981 Stella981
2年前
PhoneGap设置Icon
参考:http://cordova.apache.org/docs/en/latest/config\_ref/images.html通过config.xml中的<icon标签来设置Icon<iconsrc"res/ios/icon.png"platform"ios"width"57"height"57"densi
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这