9.14 数据结构

一、知识梳理与总结

        1.内存空间划分

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

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

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

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

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

1.1栈区特点

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

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

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

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

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

6》栈空间是向下增长的

 1.2堆区特点

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

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

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

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

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

6》堆空间是向上增长的

1.3 堆和栈的区别

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

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

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

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

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

2.1malloc函数

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

头文件:#include

原型:void *malloc(size_t size);

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

          参数是size_t size 【数据类型 size_t 64位是long int——%ld 32位是int——%d】

        【size表示的是索要开辟的空间的大小】

 2.2free函数

功能:手动释放堆区空间

头文件:#include

原型:void free(void *ptr); //无返回值函数

           参数是void *ptr 【通用型指针,传参直接传指针名即可】

3.常见的内存错误 
 3.1野指针

原因:

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

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

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

解决方法:

①malloc() free()后及时NULL

②及时更新指针

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

3.2内存越界 

原因:

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

②数组下标越界访问

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

解决方法:

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

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

③使用strncat 和 strncpy可以确保不会溢出目标缓冲区

strcpy(dest , src);

strncpy(dest , src , strlen(dest))

3.3内存泄漏

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

原因:

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

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

③非法访问常量区

解决方法:

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

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

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

3.总结 

注意事项:

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

2.动态内存分配的错误处理
如果 `malloc()` 返回 `NULL`,应及时处理以避免空指针引用。

3.合理使用 `free()` 释放内存
避免在释放后继续使用已释放的内存,使用悬空指针可能会导致未定义行为。

4.优化程序的内存使用
避免频繁的内存分配和释放操作,减少内存碎片问题。

学习重点:

1.深度理解内存分配的机制
更深入地理解堆栈的使用区别,尤其在不同平台上的表现差异。

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

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

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

二、练习

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

int main()

{

    char *p = "hello,world";

    return 0;

}

解析: 

p是局部变量,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是已初始化的全局变量,存储于全局区的数据段,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是已初始化的全局变量,存储于全局区的数据段,g_iB是未初始化的全局变量,存储于全局区的BSS段,g_iC是已初始化的静态全局变量,存储于全局区的数据段,iD是已初始化的静态局部变量,存储于全局区的数据段,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接收str的地址,malloc(100)在动态内存中开辟100字节空间,并将动态空间的地址传递给p,此时p存储的地址被修改,但并不影响外部str的的值,值仍然为NULL,进行strcpy操作,会在函数内部对空指针解引用,出现段错误。

代码2:GetMemory函数中,p数组或指针都是该函数内的局部变量,将其地址传递给外部,调用结束时其空间也被释放,test函数内str虽接收了p的地址,但此时所指向的空间已被释放,成野指针,打印会出现段错误。

代码3:GetMemory函数中,接收test函数str的地址,此时p指向str的内存空间,*p=(char *)malloc(num),在动态内存中开辟100字节空间,并将地址传递给*p,此时*p即str存储着动态内存空间的地址,strcpy进行字符串拷贝,并打印字符串,所以运行结果为hello world。

代码4:malloc(100)在动态内存中开辟100字节空间,并将动态空间的地址传递给p,strcpy进行字符串拷贝,然后释放内存,此时str仍指向已被释放的内存,成为野指针,此时strcpy会出现段错误,所以释放内存后,应及时将指针置空,避免后面程序再利用该指针时出现野指针情况。

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

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

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

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

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

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

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

原因:

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

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

③非法访问常量区

解决方法:

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值