c语言精通学习「1」: 内存
1.为什么需要内存
(1)计算机为什么需要编程?为什么要写新的程序?
计算机编程主要注重两点,结果和过程。比如下面的代码看重的是结果
int add(int a,int b)
) { return a+b; }
下面的代码看重的是过程
void add(int a,int b) { printf("%d",a+b); }
而下面的代码既看重过程也看重结果
int add (int a,int b) { printf("%d",a+b); return a+b; }
(2)计算机程序运行过程
计算机运行过程其实就是多个函数相继运行的过程,程序的本质就是函数,函数的本质是数据的加工。
(3)冯诺依曼结构和哈佛结构
冯诺依曼结构:数据和代码放在一起
哈佛结构:数据和代码分开存放
例如在常见的单片机中比如51或32他们都是通过烧录将程序烧录进ROM中而程序执行后的产生的数据存储在RAM中故其输入哈佛结构。而在linux系统中代码和数据都存储在同一个存储器中故其属于冯诺依曼结构。
(4)DRAM和SRAM
对于RAM ROM DRAM SRAM不熟悉的同学可以看 这里
(5)总结
程序运行过程中可变数据是必不可少的,而可变数据(局部变量、全局变量等)都存储在内存中。故在计算机中内存也是必不可少的。
(6)如何管理内存
操作系统中:操作系统控制所有的硬件内存,因为内存很大所以操作系统会通过将其分为页面进行管理,页面内用更细小的方式进行管理。(在此省略)操作系统提供了很多API,我们调用即可。
无操作系统时:裸机程序中,需编程者自己计算、使用和安排
2.位、字节、半字、字的概念和内存位宽
(1)什么是内存
硬件角度:内存是电脑的一个硬件比如内存条
逻辑角度:内存可以随机访问并且可以读写。在编程中用来存放变量
(2)内存位宽
硬件角度:硬件内存的实现本身就是有宽度的,也就是说有些是8位、16位的,有些是32位的
逻辑角度:内存位宽是任意的,
(3)位和字节
内存单元的大小大单位,位(1)->字节(8)->半字(一般为16)->字(一般为32)
所有计算机中位都是1bit 字节为8bit
(4)字和半字
字和半字根据不同平台有所不同。编程时一般用不到这几个概念,但文档中会用到。
3.内存的编址和寻址、内存对齐
(1)内存编制方法
内存在逻辑上就是一个个的格子,这些格子可以用来储存数据,每个格子都有一个编号,这个编号就是内存地址。
程序运行时,计算机cpu只认识内存地址,并不关心这个地址所代表的空间在哪里,由硬件来保证按照这个地址一定能找到这个空间
(2)内存编址是以字节(8bit)为单位的
(3)内存和数据类型的关系
c语言中数据类型有:char short int long float double
int 整型(整数类型)如32机中int为32位
数据类型是用来定义变量的,而变量需要储存、运算在内存中。数据类型必须和内存相匹配才能获得最好的性能。
在32位系统中变量最好用int,因为这样效率高。(其实应该是使用unsigned int效率会更高,少了符号判断的一个过程)
(4)内存对齐
内存分配策略:
第一种:0 1 2 3 对齐访问
第二章:2 3 4 5 非对齐访问
内存的对齐访问不是逻辑问题,是硬件问题。对齐访问很配合硬件效率很高,非对齐访问和硬件不搭配因此效率不高。有时为了节省空间会采用非对齐的方式。
(5)从内存编址看数组
数组b[10],b即指的是b[0]字节的的首地址
4.C语言如何操作内存
(1)C语言中对内存地址的封装
如 int a; a=5;编译器帮我们申请了1个int类型的内存格子,并将符号a和之和内存格子绑定。a的地址只有编译器知道,我们不需要知道。
函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名本质也是一个内存地址
(2)用指针来间接访问内存
int a和int *p 本质上没有任何区别,int a是内存中某一个格子,而int *p也是内存中某一个格子,不过int *p这个格子内存的是内存地址。也就是说解析方式不同
(3)数组管理内存
数组管理内存本质上和变量没有区别,只是符号的解析方式不同。
数组的第一个元素a[0]成为首元素;每一个元素类型都是int,长度都为4,其中第一个字节的地址就是首地址;首元素a[0]的地址也就是首元素首地址。
举例:
int a; int *p; int *q; int b[4]; //以上都是申请内存地址用与储存变量 a=5; //a所关联的地址的数值写为5 b=[1,2,3,4];//b所关联的4个字节的内存地址每个字节分别写为1 2 3 4 p=&a;//p所关联的内存地址写为a的内存地址 q=b;//q所关联的内存地址写为b的内存地址 printf("%d",*p) //p指向a的地址,*p即为a的内存即输出 5 printf("%s",*q) //q指向的为b的首地址,输出为[1,2,3,4] printf("%d"**q); //*q为b的首地址,**q即为*b即输出 1
5.内存管理之结构体
(1)数据结构这门学问的意义
数据结构就是研究数据如何组织(在内存中排布),如何加工的学问。
(2)最简单的数据结构:数组
为什么要有数组?
因为程序中有好多类型相同、意义相关的变量需要管理。单独变量的话会比较乱,数组可以更好管理
(3)数组的优势和缺陷
优势:数组比较简单,访问用下标,可以随机访问
缺陷:1.数组中所有类型必须相同;2.数组的大小必须定义时给出,并且一旦确认就不能更改,不具有伸缩性
(4)结构体
数组的升级版,用来解决第一个缺陷
举例,管理三个学生的年龄
方法1,:用数组 int ages[3];
方法2:用结构体
struct ages { int age1; int age2; int age3; } struct ages age;
在此处结构体和数组相等,但结构体较为麻烦
举例2,管理三个学生的年龄,姓名。此处就只能通过结构体进行管理而数组无法管理
struct students { int age; char name[20]; } struct student a; struct student b; struct student c;
(5)结构体内嵌指针实现面向对象
C语言是面向过程的,但是C语言写出的linux是面向对象的,非面向对象的语音也可以实现面向对象的代码。只能说,用面向对象的语言实现面向对象的过程要简单一些。
struct s { int age; void (*pFunc)(void)//函数指针,指向void Func(void)这类函数 }; 使用这样的结构体就可以实现面向对象。
这样包含了函数指针的结构体,就类似与面向对象的class,结构体中的变量类似于class中的成员变量,函数指针类似于class中的成员方法。
6.内存管理之栈
(1)什么是栈
栈是一种数据结构,C语言中使用栈来保存局部变量。栈是被发明出来管理内存的
(2)栈管理内存的特点(小内存、自动化)
栈包括两个指针 botton 指针和 top指针,栈为空时两个指针都指向底部,加入有数据进来,top指针上移一位。top指针永远指向数据的上一格,每进来一个数据top指针上移一位知道栈满。
先进后出特性 FILO (first in last out)栈的特点事入口即出口,只有一个口另一个口是堵死的,要想改变先进入的数据需先修改上层的数据
先进先出(FIFO)是队列,队列的特点是入口和出口都有,必须从入口进出口出
栈和对列的实现 请见 这里
(3)栈的应用举例:局部变量
分析一个细节: c语言中,定义局部变量时如果未初始化,则值是随机的,为什么?
定义局部变量.其实就是在栈虫通过移动栈指钛来给程序提供一个内存空间和这个局部变量名绑定因为这段内存空间在栈上而栈内存是反复使用的脏的上次用完没清零的所以说使用栈来实现的局部变量定义时如果不显式初始化值就是脏的。
(4)栈的缺点
首先栈是有太小的所以栈内存大小丕好设置如果太小怕溢出太大怕浪费内存(这个缺点有点像数组)其次,栈的溢出危害很大,一定要避免。所以我们在c语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时int a [10000];使用递归来解决问题时一定要注意递归收敛)
7.内存管理之堆
(1)什么是堆?
堆(heap)是一种内存管理方式。如今内存容量特别、内存需求在时间和大小上没有规律,因此采用堆来进行管理。堆内存是操作系统划给堆管理器来管理的,然后向使用者提供api来使用堆内存。
什么时候使用堆内存?需要内存容量比较大时,需要反复使用以及释放时,很多数据结构的实现都要使用堆内存
(2)堆管理内存的特点
特点一:容量不限
特点二:申请以及释放都需要手工进行,如果申请了但是没释放则会发生内存泄漏,在C/C++内存泄露是最严重的程序BUG
(3)堆管理的接口
堆内存释放时最简单,free释放即可
堆内存申请时有三个函数可供选择 malloc calloc relloc
加入释放10个int元素的内存
malloc(40) malloc(10*sizeof(int))
calloc(10,4) calloc(10,sizeof(int))
(4)堆的劣势和优势
优势:灵活
劣势:需要程序员去处理各种细节
8.复杂数据结构
(1)链表、哈希表、二叉树、图...
链表最简单也是最重要的,在linux内核中使用非常多,需掌握到会自己定义结构体来实现链表以及增删改查。
剩下的复杂数据结构嵌入式里用到的很少
(2)为什么需要复杂的数据结构
解决某些特定问题
(3)数据结构和算法的关系
相辅相成
(4)如何学习?
嵌入式学习不必太过深入花费过多精力,遇到时再去深究