一起笨笨的学C——006怪兽struct

目录

 000——副标题

目录

前言

正文

代码:ex16

输出

    ​编辑

struct简介

 

破坏+附加题

1答答:

2答答:

3答答:

4答答:

5答答:

 

后语

        1、原始内存

        2、汇编代码

3、打印内存泄露

        4.valgrind的输出解读

5、堆和栈的区别

A:006写后感



前言


        神兽中的怪兽,struct!

        创建、使用及理解内存。

        利用上个习题的神兽,使用malloc从原始内存创建怪兽!

正文

代码:ex16

1dd41f2703324c6f83d43f5987ed87a0.png8d28936318be4edab295842fce8e55b0.png

        (从此篇开始,代码要用图片了。代码只有多敲,多输入,才会跟你亲近。也就是说,如果你是刚新学的,建议全程输入一遍。)

输出

    83c7ae8bb76f449a8d9afbe8cff24efc.png

        

struct简介

        没有struct,就需要知道数据的大小、数据类型和内存块地址,才能找到相应的数据。早期的汇编代码(甚至当今的一些)就是这样做的。

        使用struct可以将多个不同类型的变量组合成一个整体,使代码更加清晰、简洁和可读性更强。

 

破坏+附加题

如何不使用指针将结构体传递给别的函数。

1试着传NULL到Person_destroy中, 看会发生什么。如果程序不终止,Makefile的CFLAGS中缺-g。(这不是告诉我会中
2程序结尾不调用Person_destroy,看“小僵”啥反应?研究一下给“小僵”啥,它会打印内存泄露细节。(那肯定是血浆,嘿嘿……)
3忘记释放Person_destroy中的who->name,然后比较输出。使用正确的选项,让“小僵”告诉你具体那里出错。
4恩,再将NULL给Person_print,然后看看“小僵”脸色。哦哦,它很快要化成灰了!
5将程序转换成不用指针与malloc的版本
5.1如何在栈(stack)上创建struct,就和你创建任何别的变量一样。
5.2如何使用x.y(句点)而非x->y语法来初始化struct。
5.3如何不使用指针将结构体传递给别的函数

1答答:

        虽然已经知道终止,也要知道怎么终止不是?

        增加代码:

4e1f300ee7b24427a42b540e14873212.png

        输出:

d2bb8801434643d1ae569ecbf7ac5f36.png       

2答答:

        “小僵”表面看不出来反应,然而实际波涛汹涌。找了三种方法,详见后语3 

        我最终用了第一种,sudo apt-get install valgrind。第二种环境变量没反应,第三种,额,超纲,了解下吧。

        我的输出:55d76b001375483b9e92f4c0bcaeae3e.png

(有大神看懂没?反正我看得不太懂,就又检测个正常的比较下,也就稍微明白了点)

eb6e0ee51a9241e5bcdf35e4cea70297.png

3答答:

        同2(valgrind --leak-check=full  ./ex16),不同的是我又仔细看了下报错详细步骤,貌似又懂了一点工具。如果你和我一样,请参考后语4  。

4答答:

哪里有NULL,哪里就有段错误!

难度分界线


5答答:

        的确挺有难度,用了我好几个番茄时间,同时让我认识到了指针对与批量变量赋值的便利性。(以前可是做不出来的,现在用上ai工具,勉强完成了,有成就感!)

       代码:    

  1 #include <stdio.h>
  2 #include <string.h>
  3 
  4 typedef struct
  5 {
  6         char name[20];
  7         int age;
  8         int height;
  9         int weight;
 10 } Stru;
 11 
 12 void Person_print(Stru who)
 13 {
 14         printf("Name: %s\n", who.name);
 15         printf("\tAge: %d\n", who.age);
 16         printf("\tHeight: %d\n", who.height);
 17         printf("\tWeight: %d\n", who.weight);
 18 }
 19 
 20 int main(int argc, char *argv[ ])
 21 {
 22         Stru Joe, Frank;
 23         strcpy(Joe.name, "Joe Alex");
 24         Joe.age = 38;
 25         Joe.height = 138;
 26         Joe.weight = 380;
 27  
 28         printf("Joe is at memory location:%p\n", Joe.name);
 29         Person_print(Joe);
 30   
 31         strcpy(Frank.name, "Frank Blank");
 32         printf("Frank is at memory location:%p\n", Frank.name);
 33         Frank.age = 25;
 34         Frank.height = 125;
 35         Frank.weight = 250;
 36         Person_print(Frank);
 37         
 38         Joe.age += 25;
 39         Frank.age +=25;
 40 
 41         Person_print(Joe);
 42         Person_print(Frank);
 43 
 44         return 0;
 45 }

输出:

854f583e05f542958f0723cb88e14aaa.png

 

后语

        1、原始内存

      (原始内存,看到这些陌生的概念,有点难受,随后又开始喜悦!因为难受,所以喜悦,大脑又要开创新的神经元了。如果从另一个角度讲,总感觉是一种知识上的受虐狂心态,嘎嘎!)

原始内存(Raw Memory)是指计算机硬件中的一块用于存储数据和指令的物理空间。它是计算机系统中的主要存储介质,用于临时存储正在运行的程序、数据和操作系统。原始内存是由一组连续的存储单元组成,每个存储单元都有一个唯一的地址。

原始内存通常是固化在计算机主板上的芯片,也称为内存芯片。它以二进制形式存储数据和指令,并且在每个存储单元中可以存储一个固定大小的数据。

原始内存的访问速度非常快,可以迅速地读取和写入数据。它是计算机系统中内存层次结构中的第一层,也是CPU直接访问的主要内存。

        2、汇编代码

        (大学里学过,但是映像有点淡了。)

汇编代码是一种低级语言,用于直接与计算机硬件交互。它是机器语言的文本表示形式,通常由特定的CPU架构定义。汇编代码通常以助记符(如ADD,MOV,JMP等)和操作数(寄存器,内存地址等)的形式表示。

以下是一个简单的x86汇编代码示例,将两个数字相加并将结果存储在寄存器中:

section .data
    num1    db 10
    num2    db 20

section .text
    global _start

_start:
    mov al, [num1]      ; 将num1的值存储到AL寄存器
    add al, [num2]     ; 将num2的值加到AL寄存器上
    mov [result], al    ; 将结果存储到result内存地址

section .bss
    result  resb 1      ; 用于存储结果的内存单元

 

以上代码首先在.data节中定义了两个变量num1和num2,然后在.text节中使用MOV指令将num1的值存储到AL寄存器中。接下来,使用ADD指令将num2的值加到AL寄存器上。最后,使用MOV指令将结果存储在名为result的内存地址中。

请注意,以上示例代码是基于x86架构的汇编代码示例。不同的CPU架构使用不同的汇编语言和指令集。

3、打印内存泄露

要让Linux打印程序内存泄露情况,可以使用以下方法:

  1. 使用valgrind工具:Valgrind是一个强大的内存分析工具,可以检测程序的内存泄露情况。使用valgrind运行程序时,会自动检测内存泄露并输出相关信息。例如,使用以下命令运行程序:

    valgrind --leak-check=full ./your_program
    

     

    valgrind会检测程序在运行过程中的内存泄露情况,并在程序结束时输出相关信息。

  2. 使用glibc的内存泄露检测工具:glibc提供了一个内存泄露检测工具,可以通过设置环境变量来启用。例如,使用以下命令运行程序:

    MALLOC_CHECK_=1 ./your_program
    

     

    这样,程序运行时会检测内存泄露情况,并在程序结束时输出相关信息。

  3. 使用LD_PRELOAD技术:使用LD_PRELOAD技术可以在程序运行前加载一个动态链接库,从而拦截原始库函数的调用。可以编写一个自定义的LD_PRELOAD库,重定义malloc()、free()等与内存分配相关的函数,通过记录分配和释放的内存块,检查是否存在泄露。具体步骤比较复杂,需要深入了解LD_PRELOAD技术和动态链接库的使用。

以上是几种常用的方法,可以根据具体的情况选择适合的方法来检测程序的内存泄露情况。

        4.valgrind的输出解读

        Valgrind是一种用于调试和性能分析的工具,针对C、C++和Fortran程序。当该程序运行时,Valgrind会监控程序的内存使用情况,并在发现错误或潜在问题时生成报告。

Valgrind的报错信息一般包含以下几个方面的内容:

  1. 错误类型:Valgrind报告中会指明错误的类型,比如内存泄漏(Memory Leak),访问无效的内存(Invalid Read/Write),使用未初始化的变量(Uninitialized Variable)等。

  2. 出错位置:报告会告诉你代码中具体出错的位置,具体到源代码的行号,方便你后续进行修复。

  3. 源代码相关信息:报告中通常会包含与错误相关的源代码行的上下文信息,帮助你更好地理解错误产生的背景。

  4. 错误堆栈跟踪:Valgrind会提供一个错误堆栈跟踪,显示出引发问题的函数调用链,这对于定位错误非常有帮助。

  5. 其他建议:报告中可能会包含一些建议,用于修复错误或改进性能。

当你看到Valgrind的报错信息,首先需要关注错误类型和出错位置,然后根据报告中的其他信息来定位和修复问题。另外,你还可以根据错误堆栈跟踪和其他建议来优化程序的性能。

5、堆和栈的区别

        (作者在附加任务中总是有意让我们提前了解下一个课题,咱们就跟着他的思路来吧。这两个玩意名字挺怪,理解起来也有难度。栈就叫“客栈”吧,旅馆宾管啥玩意的,都是临时居住,啥都不需要自己动手,隨住随走。堆就叫“草堆”,没钱住客栈就睡草堆,睡过了要复原,不然就暴露了。恩,我脑袋里没有想古装武侠剧,嘿嘿……)

        

堆和栈是计算机内存中两种不同的数据存储方式,它们有以下区别:

  1. 分配方式:栈是编译器自动分配和释放内存,而堆是程序员手动分配和释放内存。

  2. 存储内容:栈上存储的是局部变量、函数参数和函数返回地址等,而堆上存储的是动态分配的对象和数据。

  3. 内存管理:栈的内存管理由系统自动完成,所以分配和释放速度很快;而堆的内存管理需要程序员手动操作,容易出现内存泄漏和内存碎片问题。

  4. 分配大小:栈的大小是固定的,在程序运行时无法动态调整;而堆的大小可以根据需求动态调整。

  5. 访问速度:栈的访问速度比堆快,因为栈上的数据存储紧凑,访问起来更高效;而堆上的数据存储比较分散,访问速度相对较慢。

  6. 分配效率:栈的分配效率高,因为栈上的对象分配和释放只需要移动栈指针,速度很快;而堆的分配效率相对较低,因为需要在堆内存中找到足够大小的连续空间。

总结来说,栈适合存储局部变量和函数调用等临时性数据,速度快但容量有限;堆适合存储动态分配的对象和数据,容量大但分配和释放过程比较复杂。

A:006写后感

        此篇可能目前是最长同时也是内容最全的了,我对此篇很满意,等下发出去我要给自己抢手赞,呵呵。后语的一些补充资料都是我产生的疑问然后添加上去,不知道大家还有没有疑问?欢迎踩踩啊 (脑子里蹦出“踩”后,为什么有孤忧伤!哦,那是夕阳下我逝去的青春!)

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值