记笔记的过程,写出自己的问题,感想,边看边总结.
在看视频时,或者书籍学习时,有什么感想,疑问,可以停下来,记录好,或者有什么理解,什么启示,收获都可以记下来,笔记看不看不重要,重要的是记笔记的过程,眼过千遍不如手过一遍.
让学习以理解为主,而不是以记忆为主!
程序运行的目的是
的到一定结果,这个结果可以解决实际需求问题,新问题不断产生,程序也需要不断重新编写.得到不同结果.
计算机就是在计算数据, 那么 数据 的重要性不言而喻.
计算机程序 = 代码 + 数据
代码用来加工数据,改变数据得到我们想要的结果.
程序 运行的目的: 结果 过程(不在乎结果,主要是过程是否运行,凡是C语言函数返回值是Void的都不在乎结果)
函数:
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),返回值不重要。
函数的返回问题
计算机程序运行其实就是很多函数的运行,程序的本质就是函数,函数的本质就是加工数据的动作。
冯诺依曼机构是数据和代码放在一起、
哈佛结构是数据和代码分开存放
什么是代码: 函数
什么是数据: 全局变量、局部变量。
在S5PV210中运行的linux系统上,运行程序时,应用程序的代码和数据都在DRAM,所以这种机构就是冯诺依曼机构;在单片机中,程序代码放在Flsah(Norflash)中,然后程序在Flash原地运行,程序中的数据(全局变量、局部变量)不能放在RAM,这种就叫哈佛结构
DRAM 动态内存 || SRAM是静态内存
为什么需要内存?
内存用来存储可变数据,数据在程序中表现为全局变量、局部变量等(特殊的:gcc中,其实常量也是存储在内存中的)(大部分单片机中,常量是存储在flash中的,也就是代码段),
GCC编译
编程的关键几乎在于 内存管理 。 譬如 数据结构,数据如何组织、算法,为了更优秀的方法来加工数据,既然与数据有关就里离不开内存
那么如何管理内存?
问题在于 写程序时,如何对内存进行管理,尤其是在程序运行时,内存的消耗量,内存对程序来说是一种的资源,
操作系统掌握多有的硬件系统,因为内存很大,所以操作系统把内存分成一个个的页面(一块,4KB),然后以页面为单位管理。再细分到页里里面 以字节为单位管理。
操作系统管理内存的原理分厂复杂,但是我们不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。
譬如C语言中使用 malloc free 这些接口 管理内存
没有操作系统时,其实就是裸机程序,程序需要操作内存,编程者需要自己计算内存使用和安排
从语言角度: 不同语言提供了不同的操作内存的接口。
譬如汇编 : 根本没有任何内存管理,内存管理全靠自己,汇编中操作内存时直接使用内存地址譬如0xd0020010),很麻烦;
譬如C语言中,通过API(malloc free)来访问系统内存
譬如C++,用new来创建对象(其实就是为对象分配内存),如果此对象用完后忘记Delete,就会造成这个对象的内存不能释放,这就是内存泄露。
什么是内存?
从逻辑角度:内存可以随机访问(给一个地址,就可以访问这个内存地址)、并且可以读写(在裸机上可限制读写,但是一般都是可读写的);内存在编程中天然用来存放变量的。一个变量对应内存中的一个单元。
内存位宽
从硬件角度讲:硬件内存实现本身是有宽度(内存芯片的实际数据总线数)的,也就是说有些内存条是8位的,而有些就是16位的,那么需要强调的是内存芯片之间是可以并联的。
位和字节
内存单元的大小:
位(1bit)
字节(8bit)
半字(一般是16bit)
字(一般是32bit)
bit就是计算机中信息的最基础单元,
字和半字
历史上曾经出现过16位、32位、64位系统三种,
平台不一样,子和半字的bit定义不一样,这些单位就提有多少bit 是依赖平台的
有没有8位系统
内存编址方法:
内存地址(一个数字)指向一个空间,一对一且永久不变
在程序运行时,计算机中CPU实际只通过内存地址,不需要找到这个空间在哪里?
这个设定由硬件设计保证。
关键:内存编址是以字节为单位的
随便给个内存地址,这个内存地址对应的空间的大小是固定的,就是一个字节(8bit)
如果把内存比为一栋大楼,楼里的一个个房间就是一个个内存空间,这个空间是固定大小 8bit !
内存和数据类型的关系:
C语言中基本数据类型: char short int long float double
int 整形 (整数类型,整个整体现在它和CPU本身的数据位宽是一样的)譬如32位的CPU,整形就是32位,int就是32位(4个字节)的
数据类型和内存的关系:
数据类型是用来定义变量的,而这些变量需要存储、运算在内存中。所以数据类型必须和内存相匹配才能获得做好的性能,否则可能不工作或者效率低下。
在32位系统中定义变量做好用int,因为这样效率高。原因在于32位的系统本身配合内存等也是32位,这样的硬件配置天生适合定义32位的int类型变量,效率最高。也能定义8位的char类型变量或者16位的short类型变量,但是实际上访问效率不高。
在很多32位环境下,我们实际定义bool类型变量(实际只需要一个bit就够了)都是用int来实现bool的。
譬如 定义 bool b1;时,编译器实际帮我们32的内存来存储这个bool变量b1,编译器这个做实际浪费了31位的内存,但是好处是效率高。
问题:省内存和运行效率,二者需具体情况解决。
内存对齐
在C中 int a; 定义哟个int 类型变量,在内存中必须分配4个字节来存储这个a。 两种方式:
第一种:0 1 2 3 |
对齐访问 |
第二种 1 2 3 4 或者 2 3 4 5 或者 3 4 5 6 |
非对齐访问 |
内存的对齐方式不是逻辑的问题,是硬件的问题。 从硬件角度,32位的内存它 0 1 2 3 四个单元本身裸机就有相关性,这四个字节组合起来当做一个int 硬件上就是合适的,效率就高。
对齐访问很配合硬件,所以效率很高;非对齐访问因为硬件本身不搭配,所以效率不高。
从内存编址看数组的意义
C语言对内存地址的封装
譬如C语言中 int a; a = 5; a += 4; // a == 9;
结合内存来解析C语言语句的本质:
int a; // 编译器帮我们申请了 1 个int 类型 的内存格子(长度是4 字节。地址只有编译器知道。我们是不知道的,也不需要知道。)并且把符号 a 和这个格子绑定。
a = 5; // 编译器会把 5 放到 a 这个格子
C语言中数据类型的本质含义是: 表示一个内存格子的长度和解析方法。
数据类型决定长度的含义:以一个内存地址(一个字节的长度单位(固定的))开始,长度是多少,一次往后面延生多少连续长度
数据类型决定解析方法: 通过内存地址不同的类型指定这个内存地址(包括整个长度)指向的内存单元格子中二进制数的解析方法。
强制类型转换
(int *)0;
C语言中,函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名的本质也是一个内存地址。
用指针来间接访问内存
关于类型(不管是普通变量类型int float 等,还是 指针类型int * float * 等),只要记住:
类型只是对后面数字或者符号(代表的是内存地址) 所表征的内存的一种长度规定和解析方法而已。
C语言中的指针,全名叫指针变量,指针变量其实很普通没有任何区别。譬如 int a 和int *p 其实没有任何区别,a 和p 都代表一个内存地址 (譬如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a 是 int 型所以a 的长度是4字节,解析方法是按照int 的规定的;p是int * 规定的(0x2000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)
用数组来管理内存
数组管理内存和变量其实没有本质区别,只是符号的解析方法不同。 (普通变量、数组、指针变量其实都没有本质差别,都是对内存地址的解析,只是解析方法不一样)
int a; //编译器分配4字节长度,并且把首地址和符号a绑定起来
int b[10]; //编译器分配40字节长度,并且把首元素首地址和符号b 绑定起来
int *c; //编译器分配4字节长度,并且把首地址和符号c绑定起来,而与c地址绑定的首地址开始,存放了一个地址,地址的内存单元存放的是一个int 类型的数
数组中第一个元素(a[0])就称为首元素;每一个元素类型都是int,所以长度都是4,其中第一个字节的地址
就称为首地址;首元素a[0]的首地址就称为首元素首地址。
譬如: int ages[20];
譬如:我们要管理三个学生的年龄(int 类型),怎么办?
第一种解法:用数组 int age[3];
第二种解法:用结构体
struct ages
{
int age1;
int age2;
int gae3;
};
struct ages age;
分析总结:在这个示例中,数组要比结构体好。但是不能得出结论说数组就比结构体好,在包中元素类型不同时就只能用结构体而不能用数组了;
struct people
{
int age;
char name[20];
int height;
};
struct people people;
struct s
{
int age; //普通变量
void (*pFunc)(void); //函数指针,指向void func(void)这类的函数
};
这样包含了函数指针的结构体就类似于面向对象的class,结构体中变量类似与class中的成员变量,结构体中的函数指针类似与class中的成员方法
栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。
定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上,而栈内存是反复使用的(脏的,上次用完没清零的),所以说使用栈来实现的局部变量定义时如果不显示初始化,值就是脏的。如果你显式初始化怎么样?
C语言是通过一个小手段来实现 局部变量 的初始化的。
int a = 15;//局部变量定义时初始化
//C语言编译器会自动把这行转为:
int a; //局部变量定义
a = 15; //普通的赋值语句
譬如要申请10个int 元素的内存:
malloc(40); malloc(10*sizeof(int));
calloc(10,4); calloc(10, sizeof(int));
语法技巧可以更改数组大小,但其实这只是一种障眼法。他的工作原理是:先重新创建一个新的数组大小为要更改后的数组,然后将原数组的所有元素复制进新的数组,然后释放原数组,最后返回新的数组给用户;
堆内存申请时给定大小,然后一旦申请完成大小不变,如果要变只能通过realloc接口(原理与上述语法技巧一致)
优势:灵活
劣势:需要程序员去处理各种细节,所以容易出错,严重依赖程序员的水水平。
举个例子:linux内核在字符设备驱动管理时,使用了哈希表(hash table,散列表)。所以字符驱动的很多特点都和哈希表的特点有关。