文章目录
内存管理
- 内存是计算机的重要组成部分,内存是与cpu沟通的桥梁,用来暂存cpu中的运算数据。在早期,程序直接运行在物理内存中,直接操作物理内存,导致内存的利用率较低,地址空间不隔离等问题,这就引入了虚拟内存的概念。
- 虚拟内存是在程序和物理内存之间引入一个中间层,这个中间层就是虚拟内存。虚拟内存实现了对进程地址与物理地址的隔离
- 在linux中,虚拟内存划分为用户空间和内核空间,用户进程只能访问用户空间的虚拟内存,而系统调用、外设中断或者异常等才可以访问内核空间。
- 用户空间分为五个部分:代码段(存储可执行文件)、数据段(存储已经初始化的全局变量、静态变量)、BSS段(存储未初始化的全局变量)、堆区(动态分配的内存段)、栈区(临时创建的局部变量)
- 在计算机中,最小的存储单位是字节,理论上任意的地址都可以通过总线进行访问,每次寻址传输逇数据的大小与cpu的位数有关,常见的cpu的位数有8位,16位,32位,64位。位数越高,单次操作执行的数据量越大,性能越强。
- 操作系统的位数一般与cpu的位数相匹配,32位的cpu可以寻址4GB的内存空间,也可以运行32位的操作系统,同样的,64位的cpu可以运行32位的操作系统,也可以运行64位的操作系统。
内存对齐
- 按照理论上讲,对于任何变量的访问都是可以从任何地址开始访问,但实际上,访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按照一定规则排列,而不是简单的顺序排列,这就是内存对齐。
- 内存对齐说的是首地址对齐,而不是每个变量大小对齐
- 内存对齐是为了方便计算机读写数据
- 在c语言中,可以使用
sizeof()
去求取变量占用内存的大小
为什么需要内存对齐
首先需要明确,cpu是按照块来访问内存的,根据不同的平台,块的大小不同,通常是按照2的次方倍字节进行数据读取
对齐的地址一般都是n(n=2、4、8)的倍数
- 一个字节的变量,例如char,存放在任意地址的位置上
- 两个字节的变量,例如short,存放在2的整数倍的地址上
- 四个字节的变量,例如flaot、int,存放在4的整数倍地址上
- 八个字节的变量,例如long long、double,存放在8的整数倍的地址上
内存对齐的规则
-
对于标准数据类型(内建数据类型),内存地址是该种数据类型的整数倍,如int类型,地址要是4字节的整数倍;
-
对于非标准数据类型,比如结构体,由于结构体通常包含不同类型的数据,因此需要准守一定的规则
- 结构体成员对齐,第一个结构体成员的应该放在偏移地址为0 的地方,往后的每个结构体成员需要从最小长度min_L的整数倍的地方开始存放。min_L通过是该数据类型的长度和计算机默认存储模式长度取最小值确定(比如32位机器中int型占4个字节,机器默认存储是8个字节,那么存储是按照4的整数倍进行,如果机器默认存储是2个字节,那么存储是按照2的整数倍进行)
- 结构体的总大小,即
sizeof()
的计算结果,是结构体内部占用最大字节的数据类型长度 和机器默认存储模式字节长度 的最小值 的整数倍,不足需要补齐。 - 结构体嵌套时,如果结构体M中包含结构体N,还是按照最大字节成员类型大小对齐,但是结构体N的起点为N内部最大字节成员的整数倍(如,结构体M中嵌套结构体N,N中有int char 和double ,那么N应该从8的整数倍开始)
-
对于数组成员:比如 char a[5],它的对齐方式和连续写 5 个 char 类型变量是一样的,也就是说它还是按一个字节对齐。
-
含联合体成员:取联合体中最大类型的整数倍地址开始存储
举例说明
c
//假设默认存储8个字节//
typedef struct Student1{
int a ;// 从4的倍数开始,0-3
char b://从1的倍数开始,4(实际占用4-7)
double c;//从8的倍数开始,8-15
float d;//从4的倍数开始,16(16-19)
}Student1;
//结构体内部对齐后大小20个字节(0-19);整体对齐,要满足最大字节类型的整数倍,即8的整数倍,故为24,即sizeof(Student1)=24
c
//嵌套结构体
typedef struct Student2{
char a ;// 从1的倍数开始,0(0-7)
Student1 b://从内部成员最大字节整倍数开始,即从8开始存储,(占用第8-31字节)
double c;//从8的倍数开始,即32-39
}Student2;
//结构体内部对齐后大小40个字节(0-19);整体对齐,要满足最大字节类型的整数倍,即8的整数倍,故为40,即sizeof(student)=40
c
//含有数组
struct stu1 {
char a[18];//从1的倍数开始0-17(实际0-23)
double b;//从8的倍数开始24-31
char c;//从1的倍数开始32(实际32-35)
int d;//从4的倍数开始36-39
short e;//从2的倍数开始40-41
};
//结构体对齐后占42个字节,整体对齐后满足最大类型的整数倍,即8的整数倍,所以sizeof(stu1)=48
结果展示:
c
struct stu1 {
char a[18];//0-17(实际0-19)
int b[3];//20-31
short c;//32-33
char d;//34,实际34-35
int e;//36-39
short f;//40-41
};
//结构体对齐后占41个字节,整体对齐满足最大类型的整数倍,即4的倍数,所以sizeof()=44
结果展示:
c
//枚举类型:4个字节
enum DAY {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
struct stu1 {
char a[5];//0-4
char b[3];//5-7
enum DAY day;//枚举类型占4个字节,4的倍数,8-11
int *c;//linux64下指针占8个字节,8的倍数,16-23
short d;//24-25
int e;//28-31
};
//结构体对齐占32字节,sizeof是8的倍数也是32字节
结果展示:
c
//结构体嵌套
#include<stdio.h>
#include<stdlib.h>
typedef struct stu2 {
char x;
int y;
double z;
char v[6];
}stu2;//这里以8的倍数开始对齐
typedef struct stu1 {
union u1 {
int a1;
char a2[5];
}a;//联合体:取union中最大的一个变量类型的大小,这里是a2
stu2 b;
int c;
}stu1;
int main(){
printf("%d\n",sizeof(stu1));//输出结果是40
return 0;
}
结果展示:
c
struct stu2 {
char x;
int y;
double z;
char v[6];
};//以8的倍数来对齐
struct stu1 {
char a;
struct stu2 b;
int c;
};//sizeof=40,sizeof是8的倍数
结果展示:
两个函数
attribute((packed)):取消变量对齐,按照实际占用字节数对齐(就是让变量之间排列紧密,不留缝隙)。(gcc才支持)
c
struct __attribute__((packed)) stu1 { // 取消内存对齐
char a;
long b;
short c;
float d;
int e;
};//占用19字节=1+8+2+4+4
结果展示:
#pragma pack (n):让变量强制按照 n 的倍数进行对齐,并会影响到结构体结尾地址的补齐(详见四的通常情况下关于结尾地址补齐的描述)。
c
#pragma pack (2) // 强制以 2 的倍数进行对齐
struct stu1 {
short a;
int b;
long c;//linux64中long占8个字节
char d;
};//占用16字节
#pragma pack () // 取消强制对齐,恢复系统默认对齐
结果展示:
上面例子展示的都是以centos7,64位系统进行展示的
ps:各种操作系统基本类型所占大小