操作系统 内存对齐

文章目录

内存管理

  • 内存是计算机的重要组成部分,内存是与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)的倍数

  1. 一个字节的变量,例如char,存放在任意地址的位置上
  2. 两个字节的变量,例如short,存放在2的整数倍的地址上
  3. 四个字节的变量,例如flaot、int,存放在4的整数倍地址上
  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:各种操作系统基本类型所占大小

相关推荐
岑梓铭41 分钟前
计算机网络第九章——数据链路层《局域网》
网络·笔记·计算机网络·考研·408
阿巴~阿巴~2 小时前
Linux 第一个系统程序 - 进度条
linux·服务器·bash
小白爱电脑2 小时前
什么是2.5G交换机?
运维·网络·5g·千兆宽带
?ccc?2 小时前
容器技术技术入门与 Docker 环境部署
运维·docker·容器
时时刻刻看着自己的心2 小时前
docker启动报错
运维·docker·容器
我科绝伦(Huanhuan Zhou)2 小时前
华为泰山服务器重启后出现 XFS 文件系统磁盘“不识别”(无法挂载或访问),但挂载点目录仍在且无数据
运维·服务器·华为
阿沁QWQ3 小时前
UDP的socket编程
网络·网络协议·udp
匆匆那年9673 小时前
Docker容器中安装MongoDB,导入数据
运维·docker·容器
望获linux3 小时前
【Linux基础知识系列】第四十三篇 - 基础正则表达式与 grep/sed
linux·运维·服务器·开发语言·前端·操作系统·嵌入式软件
HXR_plume3 小时前
【计算机网络】王道考研笔记整理(1)计算机网络体系结构
网络·笔记·计算机网络