数据结构(Day13)

一、学习内容

内存空间划分

  1. 1、一个进程启动后,计算机会给该进程分配4G的虚拟内存

    2、其中0G-3G是用户空间【程序员写代码操作部分】【应用层】

    3、3G-4G是内核空间【与底层驱动有关】

    4、所有进程共享3G-4G的内核空间,每个进程独立拥有0G-3G的用户空间

    5、内存分区的目的是:专人专项、提高效率

    栈区特点

    1. 运行时自动分配和回收: 栈是自动管理的,程序员不需要手工干预,使用起来方便简单。

    2. 反复使用: 栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。

    3. 脏内存: 栈内存由于反复使用,每次使用后程序不会去清理,因此分配到时如果没有初始化会保留原来的值。

    4. 临时性: 函数不能返回栈变量的指针,因为这个空间是临时的。

    5. 栈会溢出: 因为操作系统事先给定了栈的大小,如果在函数中无穷尽的分配栈内存总会消耗完。

    6. 栈空间是向下增长的

    堆区特点

    1. 大块内存: 堆内存管理是总量很大的操作系统内存块,各进程可以按需申请使用,使用完释放。

    2. 程序手动申请和释放: 手工意思是需要写代码去申请malloc()和释放free()。

    3. 脏内存: 堆内存也是反复使用的,而且使用者用完释放前不会清除,因此也是脏的。

    4. 临时性: 堆内存只在申请malloc()和释放free()之间属于这个进程,可以访问。在释放free()之后不能再访问,否则会有不可预料的后果。

    5. 不可直接操作: 需要通过指针操作。

    6. 堆空间是向上增长的

    堆和栈的区别

    1. 管理分配效率不同。栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。堆的效率比栈要低

    2. 空间大小不同。栈是向低地址扩展的数据结构,是一块连续的内存区域。堆是向高地址扩展的数据结构,是不连续的内存区域。

    3. 是否产生碎片。对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。

    4. 增长方向不同。堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向。

动态内存分配和回收(申请和释放)

复制代码
 #### malloc函数

 * 功能:在堆区开辟指定字节的空间

 * 头文件:#include \<stdlib.h\>

 * 原型:void \*malloc(size_t size);

 * 分析:返回值是void \*【通用型指针】------可以转换成任意类型的指针

   * 参数是size_t size 【数据类型 size_t 64位是long int------%ld 32位是int------%d】 【size表示的是索要开辟的空间的大小】
复制代码
 #### free函数

 * 功能:手动释放堆区空间

 * 头文件:#include \<stdlib.h\>

 * 原型:void free(void \*ptr); //无返回值函数 参数是void \*ptr 【通用型指针,传参直接传指针名即可】
复制代码
 #### 常见的内存错误

 *

   ##### 野指针

   * 原因

     * 指向动态分配内存的指针在释放后,没有NULL

     * 指向被删除的对象或无效对象的指针

     * 接收了函数返回的局部变量的指针

   * 解决方法

     * malloc() free()后及时NULL

     * 及时更新指针

     * 编码时明确变量的作用域,不要接收返回的局部变量的指针

 *

   ##### 内存越界

   * 原因

     * 访问到野指针指向的区域,越界访问

     * 数组下标越界访问

     * 向缓冲区写入超过其容量的数据【strcpy、strcat的例子】

   * 解决方法

     * 使用数组时确保下标的范围,使用循环计数或条件判断控制范围

     * 使用指针时,进行if(NULL==p)的检查

     * 使用strncat 和 strncpy可以确保不会溢出目标缓冲区 strcpy(dest , src); strncpy(dest , src , strlen(dest))

 *

   ##### 内存泄漏

   * 如果没有适时释放被动态分配的内存,会导致内存泄露问题。未释放的内存一直占用系统资源,使得系统变慢并最终导致崩溃。

     * 原因

       * 丢失了分配的内存的首地址,导致无法释放

       * 每循环一次,泄露一次内存

       * 非法访问常量区

     * 解决方法

       * 及时使用free()释放不再使用的内存

       * 合理设计数据结构和算法,避免内存无线增长

       * 设置合理的变量【作用域】,减少内存占用

脑图

二、作业

p 和 "hello,world"存储在内存哪个区域?( ) (鲁科安全)

int main()

{

char *p = "hello,world";

return 0;

}

解析:

字符串 "hello,world" 是一个字符串字面量,在编译时会存储在程序的 常量区(或者叫做只读数据段) 中。该区域通常是只读的,不能被修改。

变量 p 是一个指针变量,它指向字符串 "hello,world"。因为 p 是在 main() 函数中定义的局部变量,所以它存储在 栈区 中。

解答:

*p在栈区 "hello,world"在静态区中的.ro段

一个由C/C++编译的程序,会将占用的内存分为几个部分:堆、栈、代码段、数据段、BSS段。请问以下程序中的变量a、b、c、d,分别被存在内存的哪个部分?(泰华智慧)

int a = 0;

char *b;

int main()

{

int c;

static char d = 'a';

b = malloc(10);

*b = d;

return 0;

}

解析:

变量 a

变量 a 是一个全局变量并且初始化为 0

存储区域:数据段(已初始化的全局变量存储在数据段)。

变量 b

变量 b 是一个全局指针变量,但它没有初始化。

存储区域:BSS 段(未初始化的全局变量和静态变量存储在 BSS 段中)。

变量 c

变量 c 是在 main 函数内部定义的局部变量。

存储区域:栈区(局部变量存储在栈区)。

变量 d

变量 d 是一个静态局部变量并初始化为 'a'

存储区域:数据段(静态局部变量也存储在数据段中,因为它已初始化)。

解答:

变量a在数据段、b在bss段、c在栈区、d在数据段

如下代码:变量g_iA,g_iB,g_iC,iD,iE, iF, piG,iH 分别在内存中的什么区( ) (H3C)

int g_iA = 1;

int g_iB;

static int g_iC = 1;

void func1(){

static int iD=2;

iD++;

int iE=2;

iE++;

}

void func2(){

int iF=3;

int *piG = (int*) malloc(4);

}

int main(){

int iH = 100;

}

解析:

g_iA

这是一个已初始化的全局变量。

存储区域:data段(已初始化的全局变量存储在数据段中)。

g_iB

这是一个未初始化的全局变量。

存储区域:BSS 段(未初始化的全局变量存储在 BSS 段中)。

g_iC

这是一个已初始化的静态全局变量。

存储区域:data段(已初始化的静态全局变量也存储在数据段中)。

iD

这是在 func1 函数中定义的已初始化的静态局部变量。

存储区域:data段(静态局部变量存储在数据段中,无论函数是否被调用)。

iE

这是在 func1 函数中定义的局部变量。

存储区域:栈区(局部变量存储在栈中,每次函数调用时都会分配新的内存)。

iF

这是在 func2 函数中定义的局部变量。

存储区域:栈区(局部变量存储在栈中)。

piG

这是在 func2 函数中定义的指针,malloc(4) 动态分配了 4 字节的内存,piG 指向这块内存。

存储区域:栈区(指针 piG 本身) 和 堆区(malloc(4) 动态分配的内存)。

iH

这是在 main 函数中定义的局部变量。

存储区域:栈区(局部变量存储在栈中)

解答:

g_iA在静态区中的.data段、g_iB在静态区的.bss段、g_iC在静态区的.data段、iD在静态区的.data段、iE在栈区、iF在栈区、piG在栈区、iH在栈区

有关内存的思考题 (山东山大电力技术有限公司,登虹科技,昆腾微电子)

void GetMemory(char *p)

{

p =(char *)malloc(100);

}

void Test(void)

{

char *str=NULL;

GetMemory(str);

strcpy(str,"hello world");

printf(str);

}

请问运行 Test 函数会有什么样的结果?

char * GetMemory(void)

{

char pl[] = "hello world"; //char *p = "hello world"

return p1;

}

Void Test(void)

{

char *str=NULL;

str = GetMemory();

printf(str);

}

请问运行 Test 函数会有什么样的结果?

void GetMemory(char **p,int num)

{

*p = (char *)malloc(num);

}

void Test(void)

{

char *str = NULL;

GetMemory(&str, 100);

strcpy(str, "hello world");

printf(str);

}

请问运行 Test 函数会有什么样的结果?

void Test (void)

{

char *str = (char *)malloc(100);

strcpy(str,"hello");

free(str);

if(str != NULL)

{

strcpy(str, "world");

printf(str);

}

}

请问运行 Test 函数会有什么样的结果?

解析:

1、第一段

GetMemory 函数中,参数 p 是一个局部指针变量,当 malloc(100) 动态分配内存时,只是将地址赋值给局部的 p,而不会影响调用者 Test 中的 str。换句话说,str 仍然是 NULL。随后在 Test 中调用 strcpy(str, "hello world") 时,会试图向 NULL 指针中写入数据,这会导致 段错误(Segmentation Fault)。

2、第二段
GetMemory 函数中,p1[] 是一个局部数组,存储在栈区。当 GetMemory 函数返回时,栈帧将被释放,局部变量 p1 将变为无效。虽然 GetMemory 返回 p1 的地址,但是这个地址指向已经被释放的栈空间,尝试访问该地址的内容是未定义行为。

3、第三段

GetMemory 中,传递的是 str 的地址(&str),因此函数可以正确地分配内存并将其赋值给 str。随后 strcpy(str, "hello world") 将字符串拷贝到分配的内存中,并调用 printf(str) 输出字符串。

4、第四段
malloc(100) 分配了 100 字节的内存,随后 strcpy(str, "hello") 将字符串 "hello" 拷贝到这块内存。free(str) 释放了 str 指向的内存。虽然在 C 中,调用 free 并不会自动将指针置为 NULL,但在 if (str != NULL) 语句中,str 依然保持其原来的值(它指向已释放的内存)。由于内存已经被释放,再次尝试 strcpy(str, "world") 将会向一块已经释放的内存写入数据,这是未定义行为。

解答:

第一段:段错误

第二段:段错误

第三段:"hello,world"

第四段:程序崩溃

堆和栈的区别是什么? (矩阵软件)

解答:

  1. 管理分配效率不同。栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。堆的效率比栈要低

  2. 空间大小不同。栈是向低地址扩展的数据结构,是一块连续的内存区域。堆是向高地址扩展的数据结构,是不连续的内存区域。

  3. 是否产生碎片。对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。

  4. 增长方向不同。堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向。

什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?(山大华天,智洋创新)

解答:

1、如果没有适时释放被动态分配的内存,会导致内存泄露问题。未释放的内存一直占用系统资源,使得系统变慢并最终导致崩溃。

2、

  1. 及时使用free()释放不再使用的内存

  2. 合理设计数据结构和算法,避免内存无线增长

  3. 设置合理的变量【作用域】,减少内存占用

三、总结

1. 学习内容概述

内存管理

通过对堆和栈的详细讨论,讲解了内存分配的过程。栈用于自动变量分配,堆用于动态内存分配.

动态内存分配与回收

使用 `malloc`、`free` 等函数来分配和释放内存。

重点包括 `malloc()` 函数的使用方法、返回指针的类型、以及如何合理使用 `free()` 函数来释放动态内存,避免内存泄漏。

内存优化

结合了动态分配的方式和内存碎片问题,指出合理的内存使用和优化内存分配策略的重要性。

高性能与低性能

介绍了高效的内存使用方法以及可能导致低效的内存管理方式,提醒避免内存碎片和滥用动态内存分配。

2. 学习难点

指针的正确使用

在 `malloc()` 函数中分配内存时,返回的指针类型需要转换为适当的类型。初学者容易犯错的地方在于未进行正确的类型转换,或者忘记 `free()` 导致内存泄漏。

内存泄漏

在动态内存分配中,忘记释放不再使用的内存会导致内存泄漏问题,尤其是在大型程序中,内存泄漏将对程序性能产生严重影响。

内存碎片化

不合理的内存分配会导致内存的碎片化问题,影响程序的执行效率,理解并优化内存分配策略具有一定难度。

3. 主要事项

内存分配时需要注意内存对齐

在某些平台上,内存分配需要根据特定的对齐要求进行,否则可能导致性能问题或程序崩溃。

动态内存分配的错误处理

如果 `malloc()` 返回 `NULL`,应及时处理以避免空指针引用。

合理使用 `free()` 释放内存

避免在释放后继续使用已释放的内存,使用悬空指针可能会导致未定义行为。

优化程序的内存使用

避免频繁的内存分配和释放操作,减少内存碎片问题。

4. 未来学习的重点

深度理解内存分配的机制

更深入地理解堆栈的使用区别,尤其在不同平台上的表现差异。

内存调试工具的使用

掌握调试工具(如 `valgrind`)用于检测内存泄漏和内存管理错误,帮助排查问题。

进一步研究复杂数据结构中的内存管理

学习如何在链表、树、图等复杂数据结构中进行有效的动态内存分配和释放。

多线程程序中的内存管理

在并发编程中,了解内存管理的挑战,尤其是当多个线程共享内存时的同步问题。

相关推荐
迷途之人不知返13 分钟前
数据结构之,栈与队列
数据结构
MATLAB代码顾问39 分钟前
多种时间序列预测算法的MATLAB实现
开发语言·算法·matlab
高山上有一只小老虎2 小时前
字符串字符匹配
java·算法
愚润求学2 小时前
【动态规划】专题完结,题单汇总
算法·leetcode·动态规划
MOONICK3 小时前
数据结构——哈希表
数据结构·哈希算法·散列表
林太白3 小时前
跟着TRAE SOLO学习两大搜索
前端·算法
ghie90903 小时前
图像去雾算法详解与MATLAB实现
开发语言·算法·matlab
云泽8083 小时前
从三路快排到内省排序:探索工业级排序算法的演进
算法·排序算法
weixin_468466854 小时前
遗传算法求解TSP旅行商问题python代码实战
python·算法·算法优化·遗传算法·旅行商问题·智能优化·np问题
FMRbpm4 小时前
链表5--------删除
数据结构·c++·算法·链表·新手入门