C/C++自主分配出现double free or corruption问题解决

目录
  • 引言
  • 怎么分配和释放内存?
  • 出现 double free or corruption Error
  • 内存被释放之后会发生什么?
  • 常见的触发情形
    • 如何避免

引言

写过 C/C++ 的都知道,内存允许程序员自主分配,用完了这些资源也得释放出来,这种在系统运行过程中动态申请的内存,称为动态内存。

常言道,借东西好借好还,下次再借也不难,但是有的人有时候还真的忘了还回去。这要是发生在程序运行时,申请的内存没正常释放,没管理好,就避免不了会面对内存报错的问题。

内存都允许你自由操纵了,灵活性是真的大,恰恰这也是它的弊端。

今天就来聊聊 C/C++ 的报错 double free or corruption

怎么分配和释放内存?

C 语言提供了两个函数用于分配和释放内存 malloc 和 free,需要引用头文件 <stdlib.h>。<stdlib.h> 是 C 标准库头文件 为 C 语言程序员提供可靠、高效的函数,以实现动态内存分配、数据类型转换、伪随机数生成、过程控制、搜索和排序、数学以及多字节或宽字符函数,还包括一些常用常数,目的是促进组织和平台间的代码标准化。

#include <stdlib.h>
#include <stdio.h>
int main()
{
    int *ptr = malloc(sizeof(int));
    *ptr = 100;
    printf("%d", *ptr);
    free(ptr);
    return 0;
}

输出:

100

调用 malloc 会分配一块内存空间,并将这块内存空间的首地址返回。调用时,需要传入目标内存空间的大小,单位按照字节(Byte)算,而返回的地址数据类型是 void*,所以,根据目标空间的具体用途转换即可。

这块内存空间在分配之后还属于未初始化的状态,如果对内存空间的使用比较复杂,建议先用 memset 初始化一下。

内存空间使用完,需要使用 free 释放掉,避免闲置浪费,否则就算是内存泄漏了。内存泄露会直到程序进程结束为止。

在其它的高级语言里,比如 Java、Python 等,出于内存安全的考虑,都不会允许用户自己管理内存,而 C++ 是个例外,这可能来自于 C 语言的传承。

C++ 里同样提供了 malloc 和 free,但是引用的头文件变成了 <cstdlib>。<cstdlib>是 <stdlib.h> 增强版,而且所有内容都在命名空间内声明,所以使用前必须通过命名空间引用。

另外 C++ 还提供了两个额外的操作符用于分配和释放内存,分别是 new 和 delete。

#include <iostream>
using namespace std;
int main()
{
    int *ptr = new int;
    *ptr = 100;
    cout << *ptr << endl;
    delete ptr;
    return 0;
}

输出:

100

关键词 new 后接上一个数据类型,然后分配和数据类型 int 对应大小的内存空间,并返回首地址。对应地,new 申请的内存空间被使用完不再需要时,应该使用关键词 delete 释放,delete 直接操作内存空间首地址。

出现 double free or corruption Error

借来的钱用得可以很爽,是的,常人都这样。不过,每到要还钱的时候就特别不情愿,要么推三推四,要么直接抵赖,一不留神就忘了是否有还过这事。

比如,张三本来一直在外租房将就着过日子,随着家里人口逐渐增多,就和老婆合计着从银行贷了一笔资金准备买房嘛,贷了款之后,银行贷款经理就告诉他,“张先生,你们家以后每月就得由一名代表人来还贷款,不需要几个人同时还的,记住了哈!”

好了,这个故事给了我们什么启发呢?就是资金或者资源的借入借出需要有一个管理人,这样可以避免混乱进而出错。

同样的,在 C/C++ 的编程里边,经常会出现一些内存资源管理混乱而出现的报错甚至运行时崩溃的问题,比如 double free or corruption。

#include <iostream>
using namespace std;
int main()
{
    int *ptr = new int;
    *ptr = 100;
    cout << *ptr << endl;
    delete ptr;
    delete ptr;
    return 0;
}

执行

100
free(): double free detected in tcache 2
Aborted (core dumped)

程序执行崩溃并报错 double free,根本原因是对同一内存地址调用了多次的 free 或 delete 执行释放,这会导致应用的内存管理数据结构被损坏,甚至会允许恶意用户在内存任意区域写入数据。这类损坏会导致程序崩溃或者程序的部分执行流程被改变。如果攻击者这个时候特意覆盖特定的寄存器或者内存区域来引导执行他们的代码,进而可以产生提升权限的交互式 shell,这样就完全被破防了。

这也算是内存泄漏的一种,系统一旦检测到 double free 也会终止进程继续执行(Aborted)。

内存被释放之后会发生什么?

一块内存被释放之后,空闲的内存会被放入链表中,用于重新管理和组合不同的空闲内存碎片,便于将来用于分配更大的内存空间。这个链表属于双向链表,每个空闲的内存空间都可以往前和往后查找其它内存空间。

那么攻击者可以利用这个过程吗?

答案是肯定的。当 free 被调用时,攻击者可以让原本需要被链表管理的空闲内存取消链接,覆盖寄存器值并从缓冲区载入shell代码,最终往内存写入任意值。

常见的触发情形

上面的示例代码简单演示了 double free 的触发,平常出现这种报错的条件并不比上面的情形要复杂多少。比如,释放同一块内存的动作在相隔了几百甚至更多行的位置执行,有的还发生在不同源码文件,这就会让程序员容易多次释放。下面尝试总结一下,来看一下常见的犯错情形:

  • 释放前判断的条件错误或者其它不常见的情况
  • 内存被释放后还在使用
  • 内存释放的管理责任方混乱

如何避免

其实,细看一下上面总结的几种常见犯错情形,我们也可以很好地避免低级错误。

有个最佳实践是,分配的内存地址存储变量 ptr 在定义声明时就应该初始化为 NULL,内存被释放后应立刻将 ptr 置为 NULL,使用这块内存或者释放前应该遵循先判断内存空间是否有效的原则,简单点可以用 (ptr != NULL)。

另外,负责释放的管理责任方应该尽量单一,即使横跨多个源文件或模块。这里有个道理就是避免”多龙治水“。

中国在过去一直是个农业大国,有着重农轻商的历史,各种典故都有着农业的影子。

相传,几龙治水、几牛耕地那是对当年农业收成的预示,不妨翻一下老黄历看看?“龙”是管雨的神,以五龙治水可获风调雨顺,因东南西北中都有神龙,各施其职。龙少了当年就要发大水;龙多了当年将要天大旱。原因是管雨的龙神少了怕管不过来,就忙忙碌碌四处播雨以至大涝;管雨的龙神多了呢,就像“三个和尚无水吃”一样以至大旱。至于涝到什么程度还看治水的龙少到什么程度,龙越少涝得越严重。旱的程度亦一样。

因此就有了“龙多不下雨”的谚语。

计算机编程说到底还是程序员的思维体现,人情世故也会反映在代码的逻辑上。

以上就是C/C++出现double free or corruption问题解决的详细内容,更多关于C/C++ double free or corruption的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++20中的结构化绑定类型示例详解

    目录 C++20中新增了一个非常有用的特性 结构化绑定概念 结构化绑定类型 数组 Pair 结构体 实现一个可以被结构化绑定的类元组类型 C++20中新增了一个非常有用的特性 结构化绑定(Structured Binding).它可以让我们方便地从一个容器类型中取出元素并绑定到对应的变量中,使得代码更加简洁.易读.接下来,本文将分别介绍结构化绑定的概念.类型以及如何实现一个可以被结构化绑定的类元组类型. 结构化绑定概念 结构化绑定是C++20中的一个语言特性,允许将一个结构体或者其他类似类型的容

  • C++函数模板学习示例教程指南

    目录 C++函数模板学习指南 1. 函数模板的定义 2. 函数模板的使用 3. 函数模板的特化 4. 函数模板的偏特化 6. 非类型模板参数 7. 函数模板的局限性 总结 C++函数模板学习指南 C++函数模板是一种高效的代码复用机制,它允许我们定义一种可以用于多种类型的函数,而不必为每种类型都编写一个函数.本篇文章将介绍C++函数模板的基本使用.我们将逐步讨论函数模板的定义.使用.特化和偏特化. 1. 函数模板的定义 函数模板的定义基本语法如下: template <typename T>

  • C++类与对象及构造函数析构函数基础详解

    目录 C++类与对象 类的定义 对象的创建 构造函数和析构函数 访问修饰符 继承 多态 成员变量与成员方法 总结 C++类与对象 C++是一门面向对象的编程语言.在C++中,我们可以利用类来创建对象,并在编程时实现抽象.封装.继承和多态等面向对象的特性.下面是关于C++类和对象的学习内容及示例. 类的定义 在C++中,我们可以通过定义类来描述某种对象的属性和行为.类的定义可以分为两部分:声明和实现. 声明部分:类的声明部分通常包含类名.类成员(属性和方法)的声明.访问权限的修饰符等.下面是一个简

  • C++ 三种继承方式及好处示例详解

    目录 C++继承 公有继承 保护继承 私有继承 继承带来的好处 总结 C++继承 C++继承是面向对象编程中非常常见的一个概念,它提供了一种将一个类的特性引入另一个类的机制.在继承中,被继承的类称为基类或父类,继承它的类称为派生类或子类. 在C++中,继承通过关键字“public”.“protected”.“private”来实现不同层次的继承,其中“public”表示公有继承,“protected”表示保护继承,“private”表示私有继承.以下是C++中三种继承方式的示例: 公有继承 公有

  • C++ 测试框架GoogleTest入门介绍

    目录 引言 简单介绍 初体验 引言 开发者虽然主要负责工程里的开发任务,但是每个开发完毕的功能都是需要开发者自测通过的,所以经常会听到开发者提起单元测试的话题.那么今天我就带大伙一起来看看大名鼎鼎的谷歌 C++ 测试框架 GoogleTest. 简单介绍 来看看谷歌官方是怎么介绍这个框架的: Googletest 是由测试技术团队根据 Google 的特定要求和约束开发的测试框架.无论您是在 Linux,Windows 还是 Mac 上工作,如果您编写 C++ 代码,googletest 都可以

  • C++ Protobuf实现接口参数自动校验详解

    目录 1.背景 2.方案简介 3. 使用 4.测试 1.背景 用C++做业务发开的同学是否还在不厌其烦的编写大量if-else模块来做接口参数校验呢?当接口字段数量多大几十个,这样的参数校验代码都能多达上百行,甚至超过了接口业务逻辑的代码体量,而且随着业务迭代,接口增加了新的字段,又不得不再加几个if-else,对于有Java.python等开发经历的同学,对这种原始的参数校验方法必定是嗤之以鼻.今天,我们就模拟Java里面通过注解实现参数校验的方式来针对C++ protobuf接口实现一个更加

  • Java学习笔记:关于Java double类型相加问题

    目录 Java double类型相加问题 一.这个时候就要采用BigDecimal函数进行运算 二.double 三种加法比较 Java Double类详解 Double 类的构造方法 Double 类的常用方法 Double 类的常用常量 Java double类型相加问题 多个double类型的数直接相加的时候,可能存在精度误差.( 由于计算机算法以及硬件环境决定只能识别 0 1.计算机默认的计算结果在都在一个指定精度范围之内,想往深的了解,可以学习数值分析等) 在金融方面是绝对不允许的,好

  • C++浅拷贝与深拷贝及引用计数分析

    C++浅拷贝与深拷贝及引用计数分析 在C++开发中,经常遇到的一个问题就是与指针相关的内存管理问题,稍有不慎,就会造成内存泄露.内存破坏等严重的问题.不像Java一样,没有指针这个概念,所以也就不必担心与指针相关的一系列问题,但C++不同,从C语言沿袭下来的指针是其一大特点,我们常常要使用new/delete来动态管理内存,那么问题来了,特别是伴随着C++的继承机制,如野指针.无效指针使用.内存泄露.double free.堆碎片等等,这些问题就像地雷一样,一不小心就会踩那么几颗. 先来谈一下C

  • 详解C/C++中const关键字的用法及其与宏常量的比较

    1.const关键字的性质 简单来说:const关键字修饰的变量具有常属性. 即它所修饰的变量不能被修改. 2.修饰局部变量 const int a = 10; int const b = 20; 这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用const修饰变量时,一定要给变量初始化,否则之后就不能再进行赋值了,而且编译器也不允许不赋初值的写法: 在C++中不赋初值的表达一写出来,编译器即报错,且编译不通过. 在C中不赋初值的表达写出来时不报错,编译时只有警告,编译可以通过.而

  • C/C++ 浅拷贝和深拷贝的实例详解

    C/C++ 浅拷贝和深拷贝的实例详解 深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉. 浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间. 浅拷贝只是对对象的简单拷贝,让几个对象共用一片内存,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针. 在iOS开发中也会涉及到浅拷贝和深拷贝,简而言之: 浅拷贝:拷贝指针变量的值 深拷贝:拷贝指针所指向内存

  • Windows Powershell 变量的类型和强类型

    变量可以自动存储任何Powershell能够识别的类型信息,可以通过$variable的GetType().Name查看和验证Powershell分配给变量的数据类型. PS> (10).gettype().name Int32 PS> (9999999999999999).gettype().name Int64 PS> (3.14).gettype().name Double PS> (3.14d).gettype().name Decimal PS> ("WW

  • C++中const的实现细节介绍(C,C#同理)

    1.什么是const?  常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的.(当然,我们可以偷梁换柱进行更新:) 2.为什么引入const?  const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点. 3.cons有什么主要的作用? (1)可以定义const常量,具有不可变性. 例如:  const int Max=100; int Array[Max]; (2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患.例如:

  • C语言商品销售系统源码分享

    本文实例为大家分享了C语言商品销售系统的具体代码,供大家参考,具体内容如下 #include<stdio.h> //头文件 #include<string.h> //头文件 #include<stdlib.h> //头文件 #define M 100 //货物种类 #define N 100 //顾客数目 struct goods //单个货物信息格式 { int number; //产品编号 char name[20]; //产品名称 int price1; //进价

  • java GUI编程之布局控制器(Layout)实例分析

    本文实例讲述了java GUI编程之布局控制器(Layout).分享给大家供大家参考,具体如下: 布局控制器,是用来系统自动分配各个component在window内部是怎么排布的:默认为FlowLayout,即挨个排序.FlowLayout是Panel的 instance 1:FlowLayout import java.awt.*; public class TestLayout { public static void main(String[] args) { Frame f = new

  • C/C++ 中const关键字的用法小结

    C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性. Const作用 NO. 作用 说明 参考 1 可以定义const常量 const int Max = 100; 2 便于进行类型检查 const常量有数据类型,而宏常量没有数据类型.编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误 void f(const int i) { ---} //对传入的参数进行类型检查,不匹配进行提示 3 可以保护被修

  • 实例代码分析c++动态分配

    1. c语言中动态分配和释放 在c中,申请动态内存是使用malloc和free,这两个函数是c的标准库函数,分配内存使用的是系统调用,使用它们必须包含stdlib.h,才能编译通过. malloc后需要检查内存是否分配成功,free则要在指针不为空的情况下才能进行. 示例代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p = (char*)malloc

随机推荐