常用单位介绍:位、字节、字
内存以字节为单位;一个字节由8位二进制数组成(00000001);地址为十六进制数(0x100、0x101……);
数据的存储方式:
整数:
浮点数:浮点数表示法将一个数分为小数部分和指数部分并分别储存。因此尽管1.00和1值相同,但存储方式不同。
计算机数据表示
(非数值数据:ASCII;
数值数据:
以三十二位操作系统为例:
数据类型intcharshortlongfloatdouble所占字节412848字符在内存中以ASCII码存储
无符号数:最大字节11111111,最小字节00000000;最大数为2^8-1=255,最小数为0
有符号数:最大字节01111111,最小字节11111111;最大数为127,最小数为-128
负数在内存中以补码存在
常量需要宏定义;变量在程序运行中可以被修改;只读变量(const int)只能通过指针来修改
typedef的意思大概有2种: 如:1.定义已有的类型:typedef int MM,经过定义以后MM就可以当作int使用了。如:MM x,y;那末x,y,就相当于int型变量了! 2.在结构体中,常用到typedef如: typedef struct snode { int x,y; othertype data; }NODE; 那末以后可以用NODE定义刚写好的结构体了,如:NODE a;相当于:snode a.
1). 数据类型(常用char, short, int, long, unsigned, float, double)
2). 运算和表达式( =, +, -, *, while, do-while, if, goto, switch-case)
3). 数据存储(auto, static, extern,const, register,volatile,restricted),
4). 结构(struct, enum, union,typedef),
5). 位操作和逻辑运算(<<, >>, &, |, ~,^, &&),
6). 预处理(#define, #include, #error,#if...#elif...#else...#endif等),
7). 平台扩展关键字(__asm, __inline,__syscall)
强制转换:变量前加(int)……
算数运算符:+、-、*、/(取整)、%(取余)
赋值运算符:=、+=、-=、*=、/=、%=
关系运算符:<、<=、>、>=、==、!=
优先级:算数运算符>关系运算符>赋值运算符 例:a=b>c等效于a=(b>c)
逻辑运算符:&&(与)、||(或)、!(非)
自增自减运算符:++、--
结合律:自右至左 例:-i++相当于-(i++)、i+++j相当于(i++)+j
条件运算符:表达式1?表达式2:表达式3(如果表达式1为真,则取表达式2的值做为整个表达式的值,否则取表达式3做为整个表达式的值)(c语言里,条件运算符不能做为左值使用)
逗号运算符:表达式1,表达式2,表达式3,表达式4,……(优先级最低)(从左到右计算每个表达式的值,算到最后为整个表达式的值)
1、顺序结构
2、选择结构①if语句:if……else if……else②多分支结构:switch……case,break
3、循环结构①while②for,break,continue
数组
一维数组
int[5]每个数组元素都有四个字节
a[0]a[1]a[2]a[3]a[4]0x1000x1040x1080x10c0x110数组在内存中是连续排布的
a[0]一定存在于低地址,从低地址到高地址排布
未初始化的局部变量是垃圾值
未初始化的全局变量全是零
&a[0]为数组首元素地址,&a(数组名)也是数组首元素地址,&&a为数组的地址
&a[0]是0x100到&&a相隔的长度为整个数组字节
a[2][3]={{1,2,3},{4,5,6}}
取&a[0]表示数组首行首元素地址,&a[1]为第二行首元素地址,&&a为数组地址
字符数组
char a[3]={'a','c','g'}相当于char a[10]="acg"
printf("%s\n,a)
任意类型的指针,六十四位时占8个字节,三十二位时占4个字节
如果*前面是一种类型,表示这是一条定义语句,定义一个指针变量
如果前面不是一种类型,如*pa=100,则*表示取值的意思,取指针pa指向的内存的值
不同类型的指针步长不一样
指针运算、指针与数组略
结构体、联合体、枚举
struct 所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
union 各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
enum
例如: enum weekday{sun,mon,tue,wed,thu,fri,sat}; 定义了一个枚举类型名 enum weekday,然后定义变量为该枚举类型。例如: enum weekday day; 当然,也可以直接定义枚举类型变量。例如: enum weekday{sun,mon,tue,wed,thu,fri,sat} day; 其中,sum,mon,…,sat 等称为枚举元素或枚举常量,它们是用户定义的标识符。 需要说明的有以下几点。 ① 枚举元素不是变量,而是常数,因此枚举元素又称为枚举常量。因为是常量,所以不能对枚举元素进行赋值。 ② 枚举元素作为常量,它们是有值的,C 语言在编译时按定义的顺序使它们的值为,1,2,…。 在上面的说明中,sun 的值为 0,mon 的值为 1,…sat 的值为 6,如果有赋值语句 day=mon; 则 day 变量的值为 1。当然,这个变量值是可以输出的。例如: printf ("%d",day); 将输出整数 1。 如果在定义枚举类型时指定元素的值,也可以改变枚举元素的值。例如: enum weekday{sun=7,mon=1,tue,wed,thu,fri,sat}day; 这时,sun 为 7,mon 为 1,以后元素顺次加 1,所以 sat 就是 6 了。
③ 一个整数不能直接赋给一个枚举变量,必须强制进行类型转换才能赋值。例如: day=(enum weekday)2; 这个赋值的意思是,将顺序号为 2 的枚举元素赋给 day,相当于workday=tue;
无参宏函数
#define PRINT prinnf("hello\n");
有参宏函数
#define P(s) printf("%s\n",s)
extern
extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。也就是说,在一个文件中定义了变量和函数, 在其他文件中要使用它们, 可以有两种方式:使用头文件,然后声明它们,然后其他文件去包含头文件;在其他文件中直接extern。
static
修饰局部变量,存放在静态数据区,改变变量的生命周期,知道程序结束释放。
const
修饰普通、只读变量,不能通过变量本身修改值,可以通过地址方式修改
修饰指针,就近原则,如const int *qq=&num,const修饰的是*,指针指向的内存不能修改,指针本身可以修改
int *const q1=#const修饰的是指针q1,指针指向的内容可以修改,指针本身不能修改
const int*const q2=#指针指向的内容不能修改,指针本身不能修改
与运算(&)
1&1=1;0&1=0;0&0=0;5&2=7
或运算(|)
1|1=1;0|1=1;0|0=0;5|2=0
异或运算(^)
两个二进制不同时结构为1,相同时结果为0。如0^1为1,1^1为0
1^1=0;1^0=1;0^0=0;5^2=7
取反运算(~)
ch=1;~ch=-2
左移运算(<<)
00000000 00000000 00000000 00000101=5
<<2(左移一位相当于乘以二)
<<2(右移一位相当于除以二)
00000000 00000000 00000000 00010100=20
右移运算(>>)
作者:VizXu 链接:https://www.zhihu.com/question/290504400/answer/485124116 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一、物理地址和物理内存
物理内存不需要我多解释,无非是真实的可见的物理存储器件,就像下面所画的一样,硬件电路通过总线能够依次查找到对应存储单元的值,物理上是高低电位,对应0/1表示。
————————————————-———————————————— | | | | | | | | | A ————————————————-———————————————— | | | | | | | | | B ————————————————-———————————————— | | | | | | | | | C ————————————————-———————————————— | | | | | | | | | D ————————————————-———————————————— 1 2 3 4 5 6 7 8比方这里A2就是第一排第二个存储单元,那么A2就是它的物理地址。假如现在你的电脑上插了1 个8GB的内存条,对应的就有8GB,也就是8589934592个内存单元。再假如现在你的电脑是32位的系统,那么cpu能寻找到每一个内存单元吗?如果能寻找到,那么对于32位操作系统最大的寻址能力是多少呢?对于64位的系统来说呢?这个问题下面解答。
二、内存寻址
假定没有内存管理单元(MMU)的支持下,对于32位操作系统而言,你给到CPU的地址是32位的,也就是一个long型的数据,所以最大的寻址能力就是2^32,也就是4 GB。所以,对于32位系统而言,你插个8 GB内存是有一半的空间,系统是没法寻找到的,等于浪费。而对于64位系统来说,寻址能力理论上可以达到2^64,但是,处理器所支持的RAM大小地址总线上的管脚限制,早期的Intel处理器只能支持4 GB寻址,从奔腾pro开始才出现了物理地址扩展的机制来支持更大内存空间,所以如果没有这个机制,或者这个机制被关闭,对于手机来说经常如此,那么内存寻址仍然只有4 GB。
但是,现代计算机都是会存在MMU的,这是一个硬件电路,能够将一个逻辑地址转换成一个线性地址,也就是虚拟地址。
三、逻辑地址和线性地址
逻辑地址,就是指机器语言指令中用来指定一个操作数或一条指令的地址,由一个段(segment)和偏移量(offset)组成,说地直白点就是CPU拿到的地址。
线性地址,也叫虚拟地址,就是题主所问的虚拟地址。值得注意的是这个地址就是一个32位无符号的整型数,所以虚拟地址空间总共就是4 GB大小。
系统中的每个进程所使用的地址就是线性地址或者说虚拟地址,而不是什么逻辑地址,更不是物理地址,所以对每个独立的进程来说,线性空间大小是4 GB是没有错的,且其中0-1GB的地址空间给予内核访问,其它3GB由每个进程自己访问。
那是不是说一个进程就只能访问4GB的物理内存大小呢?答案是否定的,具体访问到哪个物理地址要看MMU将线性地址转换后的物理地址才能知道,所以有可能两个进程的地址同时访问同一个物理地址,这在物理地址很小的情况下是经常会发生的事情。当然,同一时刻,一个物理内存单元只可能由某个进程来访问,至于具体原理,这个MMU来实现的,这里不细讲。
四、地址转换
有了MMU,CPU拿到的逻辑地址就可以经过它来找到对应的物理地址了。
MMU有两个硬件电路单元,一个称之为分段单元(segmentation unit)、一个称之为分页单元(paging unit),下面是它的工作原理:
--------------- -------------- 逻辑地址 ----> | 分段单元 | ----> 线性地址 ----> | 分页单元 | ----> 物理地址 --------------- --------------所以,如果系统、CPU、MMU和内存坐在一起,那肯定会发生下面的对话:
系统:CPU我给你个逻辑地址0xff84ed43,你去找到这个人
CPU: 好的系统,我去问问MMU。 MMU,我这里有个地址0xff84ed43,你帮忙找一下
MMU: 好的,请求分段单元,这个地址你先找到他家所在街道的地址
分段单元:报告,已经找到了他家所在的街道地址,地址是0x56ac21fe
MMU:好的,请求分页单元,这个是他家的街道地址,你找到他家住几号。
分页单元:报告,已经找到了,他家具体地址是0x12345678
CPU:谢谢,我这就去找他
虚拟地址空间
#include <stdlib.h>(Linux下) void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size);
如果分配成功:则返回指向被分配内存空间的指针
不然,返回空指针NULL。
同时,当内存不再使用的时候,应使用free()函数将内存块释放掉。
