【动态内存错误详解和C的内存分区】

简介: 【动态内存错误详解和C的内存分区】

lso66px3532di_fa873c03d3f94857a3befa9c5fdd52e3.png

💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤

📃个人主页 阿然成长日记 👈点击可跳转

📆 个人专栏: 🔹数据结构与算法🔹C语言进阶

🚩 不能则学,不知则问,耻于问人,决无长进

🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍

💻1.动态内存错误

(1)对NULL指针的解引用操作

(2)对动态开辟空间的越界访问

(3)对非动态开辟内存使用free释放

(4)使用free释放一块动态开辟内存的一部分

(5)对同一块动态内存多次释放

(6)动态开辟内存忘记释放(内存泄漏)

📝2.经典案例分析

2.1案例一

#include<stdio.h>
#include<string.h>
void getmemory(char* p)
{
  p = (char*)malloc(100);
}
void test(void)
{
  char* str = NULL;
  getmemory(str);
  strcpy(str, "hello");
  printf("%s", str);
}
int main()
{
  test();
  return 0;
}

2.1.1问题分析

上述代码运行失败。

首先回顾一下知识;

值传递:将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同的存储单元。

地址传递:形参并不存在存储空间,编译系统不为形参数组分配内存。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。

原因是:1.因为这里是值传递,p和str分别占用不同的存储单元,malloc只是给p开辟了空间,而str仍然指向空指针。所以strcpy赋值时失败。(对NULL指针的解引用操作

2.没有free(p);造成内存泄漏。(动态开辟内存忘记释放)

注意:printf(“%s”, str);与printf(str);是一样的。

2.1.2修改错误

#include<stdio.h>
#include<string.h>
void getmemory(char** p)
{
  *p = (char*)malloc(100);
}
void test(void)
{
  char* str = NULL;
  getmemory(&str);
  strcpy(str, "hello");
  printf("%s", str);
  free(str);
  str = NULL;
}
int main()
{
  test();
  return 0;
}

运行成功

  1. 修改为地址传参
    下图是一个指向关系图:
    由图可见:str是一级指针,p是二级指针

    2.增加了free释放
free(str);
  str = NULL;

📝2.2案例二

#include<stdio.h>
#include<string.h>
char* getmemory(void)
{
  char p[] = "hello word";
  return p;
}
void test(void)
{
  char* str = NULL;
  str = getmemory();
  printf("%s", str);
  free(str);
  str = NULL;
}
int main()
{
  test();
  return 0;
}

2.2.1 原因分析

原因就出在下面代码上;

char* getmemory(void)
{
  char p[] = "hello word";
  return p;
}

数组p临时申请的那块空间,在退出这个函数的时候,就被释放掉了。

虽然我们的str可以获得p的地址,但是p指向的东西未知,p就相当于野指针。访问时,就会造成非法访问

属于:返回栈空间地址问题。

2.2.2 解决问题

#include<stdio.h>
#include<string.h>
char* getmemory(void)
{
  static char p[] = "hello word";
  return p;
}
void test(void)
{
  char* str = NULL;
  str = getmemory();
  printf("%s", str);
  free(str);
  str = NULL;
}
int main()
{
  test();
  return 0;
}

运行成功

我只是增加了static静态修饰符。

static char p[] = "hello word";

为什么这样就可以呢?首先我们来回顾static的作用

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,

所以生命周期变长。

所以,我们使用static修饰过后,当退出这个函数时,这个变量就会一直保存,我们再次调用时,仍是保存上一次的调用结果。此时,str可以获得p的地址。并且,p不再是野指针。

⚠️c/c++内存分布

内核空间,内存映射段。

1.2 内存分区简介

1.2.1 栈区(stack)

栈区编译器自动分配释放,由操作系统自动管理,无须手动管理。

栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。

1.栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。

2.栈区是先进后出原则,即先进去的被堵在屋里的最里面,后进去的在门口,释放的时候门口的先出去。

栈区存放内容



临时创建的局部变量和const定义的局部变量存放在栈区。

函数调用和返回时,其入口参数和返回值存放在栈区。

1.2.2 堆区(heap)

堆区由程序员分配内存和释放。

堆区按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。

堆区动态申请与释放内存用malloc(),free()等函数实现动态分布内存。

1.2.3 全局(静态)区

通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。

全局区有 .bss段 和 .data段组成,可读可写

1 bss段

未初始化的全局变量和未初始化的静态变量存放在.bss段。

初始化为0的全局变量和初始化为0的静态变量存放在.bss段。

.bss段不占用可执行文件空间,其内容由操作系统初始化。

2data段

已初始化的全局变量存放在.data段。

已初始化的静态变量存放在.data段。

.data段占用可执行文件空间,其内容有程序初始化。

1.2.4 常量区

字符串、数字等常量存放在常量区。 const修饰的全局变量存放在常量区。 程序运行期间,常量区的内容不可以被修改。

1.2.5 代码区

程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。 字符串常量和define定义的常量也有可能存放在代码区。

各位看官老爷,咱下回再见!

别忘了点赞关注加评论哟

💙 💜 ❤️ 💚 💔 💓 💗 💕 💞 💘 💖 ✨ ⭐️ 🌟


相关文章
|
程序员 C语言 C++
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
54 0
|
程序员 编译器 C语言
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
77 0
|
程序员 编译器 C语言
动态内存函数,内存开辟,柔性数组(超详细)
动态内存函数,内存开辟,柔性数组(超详细)
76 0
|
Unix 程序员 Linux
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
266 0
|
2月前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
53 2
|
3月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
48 0
|
4月前
|
算法 Java 开发者
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
Java面试题:Java内存探秘与多线程并发实战,Java内存模型及分区:理解Java堆、栈、方法区等内存区域的作用,垃圾收集机制:掌握常见的垃圾收集算法及其优缺点
39 0
|
5月前
|
程序员 编译器 C++
C++内存分区模型(代码区、全局区、栈区、堆区)
C++内存分区模型(代码区、全局区、栈区、堆区)
|
6月前
|
程序员 编译器 C++
内存分区模型(代码区、全局区、栈区、堆区)
内存分区模型(代码区、全局区、栈区、堆区)