C语言学习(01)——内存这个大话题

发布时间:2020-08-02 11:27:53 作者:三九感冒灵
来源:网络 阅读:5925

1.内存这个大话题

1.1.程序运行为什么需要内存

1.1.1、计算机程序运行的目的

计算机为什么需要编程?编程已经编了很多年,已经写了很多程序,为什么还需要另外写程序?计算机有这个新的程序到底为了什么?
程序的目的是为了去运行,程序运行是为了得到一定的结果。计算机就是用来计算的,所有的计算机程序其实都是在做计算。计算就是在计算数据。所以计算机程序中很重要的部分就是数据。
计算机程序 = 代码 + 数据 计算机程序运行完得到一个结果,就是说
代码 + 数据 (经过运行后) = 结果
从宏观上来理解,代码就是动作,就是加工数据的动作;数据就是数字,就是被代码所加工的东西。
那么可以得出结论:程序运行的目的不外乎2个:结果、过程
用函数来类比:函数的形参就是待加工的数据(函数内还需要一些临时数据,就是局部变量),函数本体就是代码,函数的返回值就是结果,函数体的执行过程就是过程。

    int add(int a, int b)
    {
        return a + b;
    }           // 这个函数的执行就是为了得到结果
    void add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
    }           // 这个函数的执行重在过程(重在过程中的printf),返回值不需要
    int add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
        return c;
    }           // 这个函数又重结果又重过程 

1.1.2、计算机程序运行过程

计算机程序的运行过程,其实就是程序中很多个函数相继运行的过程。程序是由很多个函数组成的,程序的本质就是函数,函数的本质是加工数据的动作,动作的操作对象是数据。

1.1.3、冯诺依曼结构和哈佛结构

从操作系统角度讲:
操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分成1个1个的页面(其实就是一块,一般是4KB),然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说,其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。
譬如:在C语言中使用malloc free这些接口来管理内存。

再从语言角度来讲:不同的语言提供了不同的操作内存的接口。
譬如:

1.3.位、字节、半字、字的概念和内存位宽

1.3.1、什么是内存?(硬件和逻辑两个角度)

C语言中,函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名的本质也是一个内存地址。

1.5.2、用指针来间接访问内存

关于类型(不管是普通变量类型int float等,还是指针类型int float 等),只要记住:
类型只是对后面数字或者符号(代表的是内存地址)所表征的内存的一种长度规定和解析方法规定而已。
C语言中的指针,全名叫指针变量,指针变量其实很普通变量没有任何区别。譬如int a和int p其实没有任何区别,a和p都代表一个内存地址(譬如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a是int型所以a的长度是4字节,解析方法是按照int的规定来的;p是int 类型,所以长度是4字节,解析方法是int *的规定来的(0x20000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)。

1.5.3、指针类型的含义

1.5.4、用数组来管理内存

数组管理内存和变量其实没有本质区别,只是符号的解析方法不同。(普通变量、数组、指针变量其实都没有本质差别,都是对内存地址的解析,只是解析方法不一样)。
int a; // 编译器分配4字节长度给a,并且把首地址和符号a绑定起来。
int b[10]; // 编译器分配40个字节长度给b,并且把首元素首地址和符号b绑定起来。
数组中第一个元素(a[0])就称为首元素;每一个元素类型都是int,所以长度都是4,其中第一个字节的地址就称为首地址;首元素a[0]的首地址就称为首元素首地址。

1.6.内存管理之结构体

1.6.1、数据结构这门学问的意义

数据结构就是研究数据如何组织(在内存中排布),如何加工的学问。

1.6.2、最简单的数据结构:数组

为什么要有数组?因为程序中有好多个类型相同、意义相关的变量需要管理,这时候如果用单独的变量来做程序看起来比较乱,用数组来管理会更好管理。
譬如 int ages[20];

1.6.3、数组的优势和缺陷

优势:数组比较简单,访问用下标,可以随机访问。
缺陷:1 数组中所有元素类型必须相同;2 数组大小必须定义时给出,而且一旦确定不能再改。

1.6.4、结构体隆重登场

结构体发明出来就是为了解决数组的第一个缺陷:数组中所有元素类型必须相同
我们要管理3个学生的年龄(int类型),怎么办?
第一种解法:用数组 int ages[3];
第二种解法:用结构体

    struct ages
    {
        int age1;
        int age2;
        int age3;
    };
    struct ages age;

分析总结:在这个示例中,数组要比结构体好。但是不能得出结论说数组就比结构体好,在包中元素类型不同时就只能用结构体而不能用数组了。

    struct people
    {
        int age;            // 人的年龄
        char name[20];      // 人的姓名
        int height;         // 人的身高
    };
因为people的各个元素类型不完全相同,所以必须用结构体,没法用数组。

1.6.5、题外话:结构体内嵌指针实现面向对象

面向过程与面向对象。

1.7、内存管理之栈

1.7.1、什么是栈

栈是一种数据结构,C语言中使用栈来保存局部变量。栈是被发明出来管理内存的。

4.7.2、栈管理内存的特点(小内存、自动化)

栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。
分析一个细节:C语言中,定义局部变量时如果未初始化,则值是随机的,为什么?
定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上,而栈内存是反复使用的(脏的,上次用完没清零的),所以说使用栈来实现的局部变量定义时如果不显式初始化,值就是脏的,是随机的。
如果你显式初始化怎么样?
C语言是通过一个小手段来实现局部变量的初始化的。
int a = 15; // 局部变量定义时初始化
C语言编译器会自动把这行转成:
int a; // 局部变量定义
a = 15; // 普通的赋值语句

4.7.4、栈的约束(预定栈大小不灵活,怕溢出)

首先,栈是有大小的。所以栈内存大小不好设置。如果太小怕溢出,太大怕浪费内存。(这个缺点有点像数组)
其次,栈的溢出危害很大,一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时 int a[10000]; 使用递归来解决问题时一定要注意递归收敛)

1.8、内存管理之堆

1.8.1、什么是堆

堆(heap)是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存,申请或者释放的内存块大小随意)。
堆这种内存管理方式特点就是自由(随时申请、释放;大小块随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存。
我们什么时候使用堆内存?需要内存容量比较大时,需要反复使用及释放时,很多数据结构(譬如链表)的实现都要使用堆内存。

1.8.2、堆管理内存的特点(大块内存、手工分配&使用&释放)

特点:容量不限(常规使用的需求容量都能满足)。
申请及释放都需要手工进行,手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free。如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),称为内存泄漏。在C/C++语言中,内存泄漏是最严重的程序bug,这也是别人认为Java/C#等语言比C/C++优秀的地方。

1.8.3、C语言操作堆内存的接口(malloc free)

堆内存释放时最简单,直接调用free释放即可。 void free(void *ptr);
堆内存申请时,有3个可选择的类似功能的函数:malloc, calloc, realloc
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);    // nmemb个单元,每个单元size字节
void *realloc(void *ptr, size_t size);      // 改变原来申请的空间的大小的

譬如要申请10个int元素的内存:
malloc(40);         malloc(10*sizeof(int));
calloc(10, 4);      calloc(10, sizeof(int));

数组定义时必须同时给出数组元素个数(数组大小),而且一旦定义再无法更改。在Java等高级语言中,有一些语法技巧可以更改数组大小,但其实这只是一种障眼法。它的工作原理是:先重新创建一个新的数组大小为要更改后的数组,然后将原数组的所有元素复制进新的数组,然后释放掉原数组,最后返回新的数组给用户;

堆内存申请时必须给定大小,然后一旦申请完成大小不变,如果要变只能通过realloc接口。realloc的实现原理类似于上面说的Java中的可变大小的数组的方式。

1.8.4、堆的优势和劣势(管理大块内存、灵活、容易内存泄漏)

推荐阅读:
  1. 计算机基础概论学习笔记01
  2. python线程与进程学习手记

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

c语言 内存

上一篇:自定制jmeter多维度报告-华山

下一篇:SQL Server查询备份日期和备份设备名

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》