结构体中的malloc 与 free

2024-08-30 17:48
文章标签 结构 free malloc 体中

本文主要是介绍结构体中的malloc 与 free,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  1. 结构体中动态内存的管理(malloc和free)

    C语言中内存的管理主要是依据malloc和free实现的,其中malloc主要是实现内存的分配,而free则是实现内存的释放。虽然这是我们已经很熟悉的,但是还是存在一些问题。特别是当结构体中存在指针的情况下,各种问题也就会展现出来。
    其中最大的问题是:结构体中指针变量没有指向一块合法的内存空间,就对指针参数进行操作,这也是很多C语言程序员经常犯的错误。

    简单的实例如下:
    1. struct student
    2. {
    3.         char *name;
    4.         int score;
    5. }stu,*pstu;

    6. int main()
    7. {
    8.         strcpy(stu.name,"Jimy");
    9.         stu.score = 99;


    10.         strcpy(pstu->name,"Jimy");
    11.         pstu->score = 99;
    12. }


    这种代码是新手经常犯的错误,其中的主要错误是指针变量没有指向一块内存空间,其中包括ptest没有指向一块内存空间,同时结构体中的指针变量name也没有指向一块内存空间。

    这种代码一般都会编译通过,但是运行过程中会发生新手编程经常出现的段错误Segmentation fault (core dumped),我通过gdb对程序进行调试发现了存在的一些问题。其中stu.name中的内容是0x0,也就是地址0x0。这样我就知道了0x0为什么会发生段错误了,因为在Linux中进程都有一个独立的虚拟存储空间4G,但是其中在最底部的0x0是没有映射的,具体的参看进程的存储器映射关系。0x0并没有映射,这样发生段错误也就不奇怪了。

    也就是说指针变量里存储的地址值并不是一个我们需要的值,为了指向一块内存空间,因此需要采用malloc分配一块内存空间。

    改写上面的代码实现内存的分配。
    1. int main()
    2. {
    3.         /*创建一块内存空间,并让stu.name指向这块内存空间*/
    4.         stu.name = (char *)malloc(20*sizeof(char));
    5.         /*实现字符串的复制过程*/
    6.         strcpy(stu.name,"Jimy");
    7.         stu.score = 99;

    8.         /*创建一块内存空间,并让pstu指向这块内存空间*/
    9.         pstu = (struct student *)malloc(sizeof(struct student));
    10.         /*创建一块内存空间,并让pstu->name指向这块内存空间*/
    11.         pstu->name = (char *)malloc(20*sizeof(char));
    12.         /*实现字符串的复制过程*/
    13.         strcpy(pstu->name,"Jimy");
    14.         pstu->score = 99;
    15.     
    16.         return 0;
    17. }

    这样补充以后的代码就为指针变量添加了指向的对象,由于是采用malloc动态申请的存储空间,那么这段存储空间是分配在堆中,而不是在栈中,如果是在被调用函数中申请内存空间,那么在函数返回后该内存空间并不会释放。

    1. Breakpoint 1, main () at TestStructPoint.c:21
    2. 21 stu.name = (char *)malloc(20*sizeof(char));
    3. Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686
    4. (gdb) p stu     ----stu中的内容
    5. $1 = {name = 0x0, score = 0}
    6. (gdb) p stu.name  ----stu.name其中的内容是0x0,也就是指向0x0
    7. $2 = 0x0
    8. (gdb) c
    9. Continuing.
    10. Breakpoint 2, main () at TestStructPoint.c:25
    11. 25 strcpy(stu.name,"Jimy");
    12. (gdb) p stu.name   -----stu.name其中的内容不再是0x0,而是一个地址值,该地值中的内容为空
    13. $3 = 0x804a008 ""
    14. (gdb) c
    15. Continuing.
    16. Breakpoint 3, main () at TestStructPoint.c:26
    17. 26 stu.score = 99;
    18. (gdb) p stu.name    -----stu.name中存储的地址的内容发生了变化。
    19. $4 = 0x804a008 "Jimy"
    20. (gdb) c
    21. Continuing.
    22. Breakpoint 4, main () at TestStructPoint.c:29
    23. 29 pstu = (struct student *)malloc(sizeof(struct student));
    24. (gdb) p pstu        ----pstu指向的地址也是0x0
    25. $5 = (struct student *) 0x0
    26. (gdb) c
    27. Continuing.
    28. Breakpoint 5, main () at TestStructPoint.c:32
    29. 32 pstu->name = (char *)malloc(20*sizeof(char));
    30. (gdb) p pstu    ----pstu指向的内存地址发生了改变,不再是0x0
    31. $6 = (struct student *) 0x804a020
    32. (gdb) c
    33. Continuing.
    34. Breakpoint 6, main () at TestStructPoint.c:35
    35. 35 strcpy(pstu->name,"Jimy");
    36. (gdb) p pstu
    37. $7 = (struct student *) 0x804a020
    38. (gdb) p pstu->name   ----pstu->name中的地址也不再是0x0,而是一个非零的地址值
    39. $8 = 0x804a030 ""
    40. (gdb) c
    41. Continuing.
    42. Breakpoint 7, main () at TestStructPoint.c:36
    43. 36 pstu->score = 99;
    44. (gdb) p pstu->name
    45. $9 = 0x804a030 "Jimy"  ----pstu->name指向地址中的内容发生改变
    46. (gdb) c
    47. Continuing.
    48. Program exited normally.

    根据上面的调试可以知道,指针变量在定义过程中没有初始化为NULL,则指针变量指向的地址就是0x0,而在Linux中的进程虚拟存储器映射中地址0x0并没有映射,因此会出现错误。因此结构体中的指针变量一定要指向一块具体的存储空间之后才能进行相应的操作。同时其他的指针也必须指向相应的地址以后再操作。

    但是分配完地址后还需要在相应操作结束后释放分配的存储器,不然会造成浪费,以及内存的泄漏。这也是很多程序员忘记完成的工作。

    内存的释放采用free函数即可,free函数是将分配的这块内存与指针(malloc返回的指针)之间的所有关系斩断,指针变量P中存储的地址(这块内存的起始地址)值也没有发生变化,同时存储器中存储的内容也并没有发生改变,改变的只是指针对这块内存地址的所有权问题。但是该起始地址所在内存中的数据内容已经没法使用了,即时采用其他的指针也不能访问。如果下一次调用malloc函数可能会在刚才释放的区域创建一个内存空间,由于释放以后的存储空间的内容并没有改变(我是参考书上的,但我认为free后存储器中的内容是发生变化的,后面的调试可以说明这个问题,只是不知道发生什么变化,我也只是猜测,但是不要访问这个存储空间的内容是最安全的),这样可能会影响后面的结果,因此需要对创建的内存空间进行清零操作(防止前面的操作影响后面),这通常采用memset函数实现,具体参看memset函数。还有指针变量P中存储的地址值并没有改变,由于指针P没有对这个地址的访问权限,程序中对P的引用都可能导致错误的产生,造成野指针,因此最后还需要将指针P指向NULL,避免野指针的产生。当然也需要对创建是否成功需要检测,但这里我暂时不考虑这些错误的处理。




  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<string.h>

  5. struct student
  6. {
  7.         char *name;
  8.         int score;
  9. }stu,*pstu;


  10. int main()
  11. {
  12.         /*为name分配指向的一段内存空间*/
  13.         stu.name = (char *)malloc(20*sizeof(char));
  14.         memset(stu.name,0,20*sizeof(char));

  15.         strcpy(stu.name,"Jimy");
  16.         stu.score = 99;

  17.         /*为pstu分配指向的一段内存空间*/
  18.         pstu = (struct student *)malloc(sizeof(struct student));
  19.         memset(pstu,0,sizeof(struct student));

  20.         /*为name分配指向的一段内存空间*/
  21.         pstu->name = (char *)malloc(20*sizeof(char));
  22.         memset(pstu->name,0,20*sizeof(char));

  23.         strcpy(pstu->name,"Jimy");
  24.         pstu->score = 99;

  25.         /*采用另外的指针访问分配的存储空间,测试内存中内容是否改变*/
  26.         char *= stu.name;
  27.         char *p1 = (char *)0x804a008;//具体的地址值 
  28.         char *ppstu = pstu->name;
  29.         char *pp = (char *)0x804a030;//具体的地址值

  30.         /*释放的顺序要注意,pstu->name必须在pstu释放之前释放,
  31.          *如果pstu先释放,那么pstu->name就不能正确的访问。
  32.         */
  33.         free(pstu->name);
  34.         free(stu.name);
  35.         free(pstu);

  36.         /*为了防止野指针产生*/
  37.         pstu->name = NULL;
  38.         stu.name = NULL;
  39.         pstu = NULL;
  40.         p = NULL;
  41.         ppstu = NULL;

  42.         return 0;
  43. }
下面的全部是调试结果,根据调试结果说明问题:


具体的调试过程说明了其中很多的问题,根据其中的结果可以知道,free结束以后指针变量P(malloc返回)中存储的地址值并没有改变,但是通过该地值已经不能访问之前的分配的存储空间。我采用其他的指针(直接赋值地址以及指针赋值)访问得到的结果也不是正确的值,虽然这不能说明地址中的数据改变了,但说明对释放以后的存储空间再通过其他方法访问不会得到正确的值,但是内存空间中存在值,并不一定是0,因此每一次malloc都清零是必要的。防止野指针也是非常必要的,减少程序错误的概率。

最后说明一下,链表作为结构体的衍生产物,链表的结构体中就有指针变量,因此一定草采用malloc等分配好内存块以后,再对链表进行操作,不然都会导致不同问题的产生。

这篇关于结构体中的malloc 与 free的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1121511

相关文章

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据

《mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据》文章主要介绍了如何从.frm和.ibd文件恢复MySQLInnoDB表结构和数据,需要的朋友可以参... 目录一、恢复表结构二、恢复表数据补充方法一、恢复表结构(从 .frm 文件)方法 1:使用 mysq

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

结构体和联合体的区别及说明

《结构体和联合体的区别及说明》文章主要介绍了C语言中的结构体和联合体,结构体是一种自定义的复合数据类型,可以包含多个成员,每个成员可以是不同的数据类型,联合体是一种特殊的数据结构,可以在内存中共享同一... 目录结构体和联合体的区别1. 结构体(Struct)2. 联合体(Union)3. 联合体与结构体的

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s