c语言内存泄露示例解析

正确的内存管理的重要性
存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:“大多数计算机安全漏洞都是缓冲区溢出”,Rodney Bates 在 2004 年写道。

在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java™、Ruby、Haskell、C#、Perl、Smalltalk 等),每种语言都有众多的爱好者和各自的优点。但是,从计算角度来看,每种编程语言优于 C 或 C++ 的主要优点都与便于内存管理密切相关。与内存相关的编程是如此重要,而在实践中正确应用又是如此困难,以致于它支配着面向对象编程语言、功能性编程语言、高级编程语言、声明性编程语言和另外一些编程语言的所有其他变量或理论。

与少数其他类型的常见错误一样,内存错误还是一种隐性危害:它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见。

因此,出于所有这些原因,需要特别关注 C 和 C++ 编程的内存问题。让我们看一看如何解决这些问题,先不谈是哪种语言。

内存错误的类别
首先,不要失去信心。有很多办法可以对付内存问题。我们先列出所有可能存在的实际问题:

•内存泄漏
•错误分配,包括大量增加 free() 释放的内存和未初始化的引用
•悬空指针
•数组边界违规

这是所有类型。即使迁移到 C++ 面向对象的语言,这些类型也不会有明显变化;无论数据是简单类型还是 C 语言的 struct 或 C++ 的类,C 和 C++ 中内存管理和引用的模型在原理上都是相同的。以下内容绝大部分是“纯 C”语言,对于扩展到 C++ 主要留作练习使用。

内存泄漏
在分配资源时会发生内存泄漏,但是它从不回收。下面是一个可能出错的模型(请参见清单 1):

清单 1. 简单的潜在堆内存丢失和缓冲区覆盖


代码如下:

void f1(char *explanation) { char *p1; p1 = malloc(100); sprintf(p1,"The f1 error occurred because of '%s'.", explanation); local_log(p1); }

您看到问题了吗?除非 local_log() 对 free() 释放内存具有不寻常的响应能力,否则每次对 f1 的调用都会泄漏 100 字节。在记忆棒增量分发数兆字节内存时,一次泄漏是微不足道的,但是连续操作数小时后,即使如此小的泄漏也会削弱应用程序。

在实际的 C 和 C++ 编程中,这不足以影响您对 malloc() 或 new 的使用,本部分开头的句子提到了“资源”不是仅指“内存”,因为还有类似以下内容的示例(请参见清单 2)。FILE 句柄可能与内存块不同,但是必须对它们给予同等关注:

清单 2. 来自资源错误管理的潜在堆内存丢失


代码如下:

int getkey(char *filename) { FILE *fp; int key; fp = fopen(filename, "r"); fscanf(fp, "%d", &key); return key; }

fopen 的语义需要补充性的 fclose。在没有 fclose() 的情况下,C 标准不能指定发生的情况时,很可能是内存泄漏。其他资源(如信号量、网络句柄、数据库连接等)同样值得考虑。

内存错误分配
错误分配的管理不是很困难。下面是一个错误分配示例(请参见清单 3):

清单 3. 未初始化的指针


代码如下:

void f2(int datum) { int *p2; /* Uh-oh! No one has initialized p2. */ *p2 = datum; ... }

关于此类错误的好消息是,它们一般具有显著结果。在 AIX® 下,对未初始化指针的分配通常会立即导致segmentation fault 错误。它的好处是任何此类错误都会被快速地检测到;与花费数月时间才能确定且难以再现的错误相比,检测此类错误的代价要小得多。

在此错误类型中存在多个变种。free() 释放的内存比 malloc() 更频繁(请参见清单 4):

清单 4. 两个错误的内存释放


代码如下:

/* Allocate once, free twice. */ void f3() { char *p, *pp; p = malloc(10);
pp=p;
free(p); ... free(pp); } /* Allocate zero times, free once. */ void f4() { char *p;
...
/* Note that p remains uninitialized here. */ free(p); }

这些错误通常也不太严重。尽管 C 标准在这些情形中没有定义具体行为,但典型的实现将忽略错误,或者快速而明确地对它们进行标记;总之,这些都是安全情形。

悬空指针
悬空指针比较棘手。当程序员在内存资源释放后使用资源时会发生悬空指针(请参见清单 5):

清单 5. 悬空指针


代码如下:

void f8() { struct x *xp; xp = (struct x *) malloc(sizeof (struct x)); xp.q = 13; ... free(xp); ... /* Problem! There's no guarantee that the memory block to which xp points hasn't been overwritten. */ return xp.q; }

传统的“调试”难以隔离悬空指针。由于下面两个明显原因,它们很难再现:

•即使影响提前释放内存范围的代码已本地化,内存的使用仍然可能取决于应用程序甚至(在极端情况下)不同进程中的其他执行位置。

•悬空指针可能发生在以微妙方式使用内存的代码中。结果是,即使内存在释放后立即被覆盖,并且新指向的值不同于预期值,也很难识别出新值是错误值。

悬空指针不断威胁着 C 或 C++ 程序的运行状态。

数组边界违规
数组边界违规十分危险,它是内存错误管理的最后一个主要类别。回头看一下清单 1;如果 explanation 的长度超过 80,则会发生什么情况?回答:难以预料,但是它可能与良好情形相差甚远。特别是,C 复制一个字符串,该字符串不适于为它分配的 100 个字符。在任何常规实现中,“超过的”字符会覆盖内存中的其他数据。内存中数据分配的布局非常复杂并且难以再现,所以任何症状都不可能追溯到源代码级别的具体错误。这些错误通常会导致数百万美元的损失。

.棘手的内存泄漏


代码如下:

static char *important_pointer = NULL; void f9() { if (!important_pointer) important_pointer = malloc(IMPORTANT_SIZE); ... if (condition) /* Ooops! We just lost the reference important_pointer already held. */ important_pointer = malloc(DIFFERENT_SIZE); ... }
do not返回局部指针变量或者局部变量的指针,除非是一个static局部变量
char *f0() {     char temp[]="123456789"; //加上static 才是正确的
return temp; }

(0)

相关推荐

  • 深入探讨C语言中局部变量与全局变量在内存中的存放位置

    C语言中局部变量和全局变量变量的存储类别(static,extern,auto,register) 1.局部变量和全局变量在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放.这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了.这种变量有效性的范围称变量的作用域.不仅对于形参变量,C语言中所有的量都有自己的作用域.变量说明的方式不同,其作用域也不同.C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量.1.1局部变量局部变量也称为内部变量

  • 浅析c语言中的内存

    1.栈(stack):存局部变量.函数,调用函数时会开辟栈区,函数结束时就自动回收,遵循后进先出的原则,从高地址向低地址增长. 2.堆(heap):malloc.realloc.calloc等开辟的内存就在堆,从低地址向高地址增长,由程序员分配和释放,系统不自动回收,所以一定要记得申请了就要释放,以免溢出. 3.数据段(初始化数据段)(data):存放初始化的全局变量.static修饰的已初始化的变量. 4.未初始化数据段(bss段):存放未初始化的全局变量和static修饰的未初始化的变量.

  • 深入C语言内存区域分配(进程的各个段)详解

    C语言可执行代码结构 名称 内容 代码段  可执行代码.字符串常量 数据段  已初始化全局变量.已初始化全局静态变量.局部静态变量.常量数据 BSS段  未初始化全局变量,未初始化全局静态变量 栈  局部变量.函数参数 堆  动态内存分配 (1)代码段(text segment):存放CPU执行的机器指令.通常代码段是可共享的,这使得需要频繁被执行的程序只需要在内存中拥有一份拷贝即可.代码段也通常是只读的,这样可以防止其他程序意外地修改其指令.另外,代码段还规划了局部数据所申请的内存空间信息.

  • C语言中的内存泄露 怎样避免与检测

    有些程序并不需要管理它们的动态内存的使用.当需要内存时,它们简单地通过分配来获得,从来不用担心如何释放它.这类程序包括编译器和其他一些运行一段固定的(或有限的)时间然后终止的程序.当这种类型的程序终止时,所有内存会被自动回收.细心查验每块内存是否需要回收纯属浪费时间,因为它们不会再被使用. 其他程序的生存时间要长一点.有些工具如日历管理器.邮件工具以及操作系统本事经常需要数日及至数周连续运行,并需要管理动态内存的分配和回收.由于C语言通常并不使用垃圾回收器(自动确认并回收不再使用的内存块),这些

  • 深入解析C语言中的内存分配相关问题

    C内存分配区域 程序代码区 存放函数体的二进制代码 全局数据区 全局变量和静态变量的存储是放在一起的.初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.常量数据存放在另一个区域里.这些数据在程序结束后由系统释放.我们所说的BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称 栈区 由编译器自动分配释放,存放函数的参数值,局部变量的值等.其操作方式类似

  • 深入理解C语言内存对齐

    一.内存对齐的初步讲解 内存对齐可以用一句话来概括: "数据项只能存储在地址是数据项大小的整数倍的内存位置上" 例如int类型占用4个字节,地址只能在0,4,8等位置上. 例1: 复制代码 代码如下: #include <stdio.h>struct xx{        char b;        int a;        int c;        char d;}; int main(){        struct xx bb;        printf(&q

  • C语言中变量与其内存地址对应的入门知识简单讲解

    先来理解理解内存空间吧.请看下图: 如上图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样.电影院中的每个座位都要编号,而我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧.所以内存也要象座位一样进行编号了,这就是我们所说的内存编址.座位可以是遵循"一个座位对应一个号码"的原则,从"第1号"开始编号.而内存则是按一个字节接着一个字节的次序进行编址,如上图所示.每个字节都有个编号,我们称之为内存地址.好了,我说了这

  • C语言中多维数组的内存分配和释放(malloc与free)的方法

    如果要给二维数组(m*n)分配空间,代码可以写成下面: 复制代码 代码如下: char **a, i; // 先分配m个指针单元,注意是指针单元 // 所以每个单元的大小是sizeof(char *) a = (char **) malloc(m * sizeof(char * )); // 再分配n个字符单元, // 上面的m个指针单元指向这n个字符单元首地址 for(i = 0; i < m; i++) a[i] = (char * )malloc(n * sizeof(char )); 释

  • 浅析C语言中的内存布局

    本节注重分清几个概念:.text .data .bss   堆   栈    静态存储区    只读存储区等 从程序到a.out 把程序变成.text  .data  .bss  是编译原理完成的过程 从a.out把程序映射到对应的内存地址空间是操作系统完成的,也就是在操作系统创建进程的时候完成的,在描述进程的那个结构体中. 我们常说的堆是为了申请动态内存的时候使用的,malloc. 栈是为了在函数中切换使用的,即存放函数中的局部变量.(堆和栈是操作系统分配的,所有不在a.out中) 静态存储区

  • c语言内存泄露示例解析

    正确的内存管理的重要性存在内存错误的 C 和 C++ 程序会导致各种问题.如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行:如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击.从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:"大多数计算机安全漏洞都是缓冲区溢出",Rodney Bates 在 2004 年写道. 在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java™.

  • ThreadLocal作用原理与内存泄露示例解析

    目录 ThreadLocal作用 简单例子 局部变量.成员变量 . ThreadLocal.静态变量 共享 or 隔离 原理 源码分析 TheadLocal TheadLocalMap ThreadLocal与内存泄漏 小结 ThreadLocal作用 对于Android程序员来说,很多人都是在学习消息机制时候了解到ThreadLocal这个东西的.那它有什么作用呢?官方文档大致是这么描述的: ThreadLocal提供了线程局部变量 每个线程都拥有自己的变量副本,可以通过ThreadLocal

  • C语言内存泄露很严重的解决方案

    目录 1.前言 2.内存泄漏问题原理 2.1堆内存在C代码中的存储方式 2.2堆内存的获取方法 2.3内存泄漏三要素 2.4内存释放误区 3.内存泄漏问题检视方法 1.前言 最近部门不同产品接连出现内存泄漏导致的网上问题,具体表现为单板在现网运行数月以后,因为内存耗尽而导致单板复位现象. 一方面,内存泄漏问题属于低级错误,此类问题遗漏到现网,影响很坏:另一方面,由于内存泄漏问题很可能导致单板运行固定时间以后就复位,只能通过批量升级才能解决,实际影响也很恶劣. 同时,接连出现此类问题,尤其是其中一

  • C 语言程序结构示例解析

    C 程序结构 在我们学习 C 语言的基本构建块之前,让我们先来看看一个最小的 C 程序结构,在接下来的章节中可以以此作为参考. C Hello World 实例 C 程序主要包括以下部分: 预处理器指令 函数 变量 语句 & 表达式 注释 让我们看一段简单的代码,可以输出单词 "Hello World": #include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n&qu

  • android的GC内存泄露问题

    1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

  • 理解Java中的内存泄露及解决方法示例

    本文详细地介绍了Java内存管理的原理,以及内存泄露产生的原因,同时提供了一些列解决Java内存泄露的方案,希望对各位Java开发者有所帮助. Java内存管理机制 在C++ 语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露. Java 语言对内存管理做了自己的优化,这就是垃圾回收机制. Java 的几乎所有内存对象都是在堆内存上分配(基本数据类型

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

  • C语言结构体计算内存占用问题解析

        c语言中结构体使用是非常广泛的,但是结构体有一个问题,就是如果开头的字段属性是字符类型(char),紧跟着的是其他类型,比如整型.长整型.双精度.浮点型,这时候结构体的大小会发生改变,下面给出一个示例: #include <stdio.h> struct person{ char sex; int age; char name[8]; }; int main() { printf("sizeof(person) = %d\n",sizeof(struct perso

  • go语言数组及结构体继承和初始化示例解析

    目录 分类 数组 数组定义 结构体 结构体继承 结构体初始化 成员的操作 同名字段 其它匿名字段 非结构体类型 结构体指针类型 结构体字段实现接口 分类 类型 名称 长度 默认值 说明 pointer 指针   nil   array 数组   0   slice 切片   nil 引⽤类型 map 字典   nil 引⽤类型 struct 结构体       数组 如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储.但是,问题是班级有80个学生,那么要定义80

随机推荐