【C语言 | 预处理】C语言预处理详解(三)——内存对齐、手把手教你计算结构体大小

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍内存对齐、手把手教你计算结构体大小🍭
😎金句分享😎:🍭🍭

本文未经允许,不得转发!!!

目录

  • 🎄一、内存对齐是什么?
  • 🎄二、为什么需要内存对齐?
  • 🎄三、计算结构体大小
    • ✨3.1 对齐参数
    • ✨3.2 计算结构体大小的步骤和例子
  • 🎄四、#pragma pack 的使用方法
    • ✨4.1 用法一:#pragma pack (n)、#pragma pack ()
    • ✨4.2 用法二:#pragma pack(push)、#pragma pack(n)、#pragma pack(pop)
  • 🎄五、总结


在这里插入图片描述

🎄一、内存对齐是什么?

内存对齐(Memory Alignment) 是指将数据存储在内存中时,是按照特定的规则将数据放置在地址为其大小倍数的位置上。具体而言,内存对齐要求变量的起始地址是它对齐参数的整数倍

看下面这两个结构体,看看按照内存对齐的要求,是怎么存储的:

struct Test1
{
	char c1;
	short s;
	char c2;
	int i;
};

struct Test2
{
	char c1;
	char c2;
	short s;
	int i;
};

两个结构体虽然结构体成员一样,但他们所占用的内存大小却不一样,Test1占用12个字节,Test2占用8个字节,他们在内存中的存储大致如下图:
看看struct Test1按照内存对齐要求是怎样安排的,假设结构体首地址为0:

  • 成员 c1 自身大小为 1 个字节,是结构体第一成员,所以直接在地址0
  • 成员 s 自身大小为 2 个字节,按照要求,其起始地址必须是 2 的整数倍,所以不能放在地址1 的位置,起始地址为地址2
  • 成员 c1 自身大小为 1 个字节,起始地址需要为 1 的整数倍,直接安排在地址4
  • 成员 i 自身大小为 4 个字节,起始地址需要为 4 的整数倍,从地址5 开始往后找,地址8是4的整数倍。
    在这里插入图片描述

在这里插入图片描述

🎄二、为什么需要内存对齐?

计算机处理器为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

所以,内存对齐的主要目的是提高访问数据的效率。当数据按照规定的对齐方式存放在内存中时,处理器可以更快地读取和存储数据,而不需要执行额外的内存操作。未对齐的数据可能导致性能下降,甚至在某些架构中导致程序崩溃。

所以,默认情况下,编译器都会将结构、栈中的成员数据进行内存对齐。

在这里插入图片描述

🎄三、计算结构体大小

按照内存对齐的要求,结构体在内存中是怎么存储的?这小节介绍复杂结构体各个成员在内存中怎么存储?

这里的复杂结构体是指包含了结构体和数组的结构体。先看看下面结构体ST_2,你能清楚它各个成员的在结构体中的偏移量吗?

typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;

✨3.1 对齐参数

在计算结构体大小之前,先了解几个概念:

  • 对齐参数:编译器进行内存对齐时,会涉及到一个对齐参数,不同的对齐参数,计算出来的结构体大小会不一样。可以通过下面代码查看编译器的默认对齐参数,(并非"32位系统就是4,64位系统为8");
    // default_align.c
    // gcc default_align.c -std=c11
    #include <stdio.h>
    #include <stddef.h>
    int main(void)
    {
    	printf("Default alignment: %zu\n", __alignof__(max_align_t));
    	printf("Biggest alignment: %d\n", __BIGGEST_ALIGNMENT__);
    	return 0;
    }
    
  • 基础数据类型的对齐方式:对齐参数就是类型大小;
  • 数组的对齐方式:对齐参数就是单个数组元素的大小。比如:char a[3];它的对齐方式和分别写 3 个 char 是一样的;也就是说它还是按 1 个字节对齐。
  • 结构体的对齐方式:对齐参数就是成员中的最大对齐参数。比如:上面的结构体 ST ,对齐参数就是成员d的对齐参数 8。

✨3.2 计算结构体大小的步骤和例子

计算结构体大小的步骤(计算各个成员的地址):

  • 1、确定成员对齐参数:成员的对齐参数是 自身对齐参数系统对齐参数中较小的一个。
  • 2、确定成员起始地址:起始地址为成员对齐参数的整数倍;
  • 3、确定结构体大小:最终结构体大小必须是最大对齐参数的整数倍。

在这里插入图片描述
按照步骤计算上面 ST2 结构体大小,假设结构体起始地址为0,系统对齐参数为 4,系统是32位系统:

  • 第一个成员 c:位于结构体第一个,起始地址为 0,结束地址为 1;

  • 第二个成员 i:自身对齐参数为 4 (sizeof(int)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址1开始数,找到4的整数倍,结果为 4,所以起始地址为 4,结束地址为 8;

  • 第三个成员 st:自身对齐参数为 8 (ST结构体最大对齐参数为8),大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址8开始数,找到4的整数倍,结果为 8,所以起始地址为 4,结束地址为 24;

  • 第四个成员 d:自身对齐参数为 8 (sizeof(double)=8),大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址24开始数,找到4的整数倍,结果为 24,所以起始地址为 24,结束地址为 32;

  • 第五个成员 c2:自身对齐参数为 1 (sizeof(char)=1),小于系统对齐参数 4,所以成员对齐参数为 1
    起始地址为:从上个成员结束地址32开始数,找到1的整数倍,结果为 32,所以起始地址为 32,结束地址为 33;

  • 第六个成员 st_arr:是一个结构体数组,对齐参数就是单个数组元素的对齐参数,数组元素又是一个结构体ST,按照该结构体最大对齐参数8作为自身对齐参数,大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址33开始数,找到4的整数倍,结果为 36,所以起始地址为 36,结束地址为 84;

  • 第七个成员 l:自身对齐参数为 4 (sizeof(double)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址84开始数,找到4的整数倍,结果为 84,所以起始地址为 84,结束地址为 88;

  • 第八个成员 i_arr:是一个结构体int型数组,数据元素是int类型的,所以自身对齐参数为 4 (sizeof(int)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址88开始数,找到4的整数倍,结果为 88,所以起始地址为 88,结束地址为 124;

计算完成员地址后,找出最大对齐参数,这个例子是 4,目前内存存储到124个字节,是4的整数倍,所以结构体ST2的大小是 124。

可以使用下面的代码验证是否计算正确:

// memAlign.c
// gcc memAlign.c -o memAlign
#include <stdio.h>
#include <stddef.h>

#pragma pack (4)
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack ()

int main(void)
{
	printf("c:%zu i:%zu st:%zu d:%zu c2:%zu st_arr:%zu l:%zu i_arr:%zu\n", 
		offsetof(ST2, c), offsetof(ST2, i), offsetof(ST2, st), 
		offsetof(ST2, d), offsetof(ST2, c2), offsetof(ST2, st_arr), 
		offsetof(ST2, l), offsetof(ST2, i_arr));
	return 0;
}

在这里插入图片描述

🎄四、#pragma pack 的使用方法

#pragma pack 可以用来改变编译器的默认对齐方式,也就是改变上文提到的系统对齐参数;

#pragma pack(n) 的n只能是2的次方幂,目前测试了,n的值可以为1、2、3、8、16,当设置32时会报错。

✨4.1 用法一:#pragma pack (n)、#pragma pack ()

使用指令#pragma pack (n),编译器将按照 n 个字节对齐。
使用指令#pragma pack (),编译器将取消自定义字节对齐方式。
用法可以参考下面代码,表示从#pragma pack (4)开始到#pragma pack ()之间的代码的系统对齐参数是4:

#pragma pack (4)
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack ()

✨4.2 用法二:#pragma pack(push)、#pragma pack(n)、#pragma pack(pop)

#pragma pack(push):保存当前对其方式到 packing stack;
#pragma pack(n):设置编译器按照 n 个字节对齐;
#pragma pack(pop):packing stack 出栈,并设置为对齐参数;
用法参考下面代码:

#pragma pack (push)	// 保存现在的对齐参数 
#pragma pack (4)	// 将对齐参数改为 4
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack (pop)	// 恢复之前保存的对齐参数

在这里插入图片描述

🎄五、总结

本文介绍内存对齐,也解释了为什么需要内存对齐,最后演示了一个结构体是怎样按照计算占用内存大小的。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/130558.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux——进度条小程序|行缓冲区概念|使用 git 命令行

目录 1./r 和 /n 2.行缓冲区概念 3.使用 git 命令行 安装git 克隆到本地仓库 添加文件到仓库 提交到本地仓库 提交到远端仓库 1./r 和 /n 对于 /n 想必都不陌生&#xff0c;是换行的意思 而 /r 就是回车的意思 &#xff0c;回到一行的开始 在C/C中 \n通常都代表 回车…

k8s-docker二进制(1.28)的搭建

二进制文件-docker方式 1、准备的服务器 角色ip组件k8s-master1192.168.11.111kube-apiserver,kube-controller-manager,kube-scheduler,etcdk8s-master2192.168.11.112kube-apiserver,kube-controller-manager,kube-scheduler,etcdk8s-node1192.168.11.113kubelet,kube-prox…

学习使用JS实现Echarts的图表保存为图片功能:saveAsImage和getDataURL

学习使用JS实现Echarts的图表保存为图片功能 接口getDataURL实现思路 需求分析 实际项目开发过程中经常会有图表展示功能&#xff0c;同时为了满足用户需要&#xff0c;会附带着图表导出功能&#xff0c;主要形式就是保存为图片。在Echarts中本身就提供这种配置项&#xff0c;…

MPC-模型预测控制笔记

线性mpc 凸优化 二次优化问题 1&#xff1a;建立预测模型 2&#xff1a;问题模型 3&#xff1a;求解优化问题 4&#xff1a;得到的优化控制驱动系统 上述方法与qp解一样 硬约束 硬约束 四组约束条件 二次规划求解 matlab代码&#xff1a; 软约束 可以用指数函数 加入…

Linux前言

目录 Linux的应用场景 Linux的应用现状 Linux的版本 操作系统 什么是Linux操作系统&#xff1f; 为什么要用操作系统&#xff1f; 上篇我们介绍了Linux的历史背景和安装环境。 Linux的应用场景 因为Linux操作系统是开源&#xff0c;所以它流向各个领域。 场景1&…

局域网内部服务器访问外部网络

​ 一、环境说明 如下图所示&#xff0c;局域网1中的服务器是可以访问外网的&#xff0c;局域网2中的服务器发出的数据包经过中间路由可以到达局域网1中的服务器。现在有一种需求需要使局域网2中的服务器也要能访问外网&#xff0c;这里考虑采用如下方法来实现。 ​​ 二、软…

图书网站信息采集

首先&#xff0c;你需要安装Haskell的HTTP库&#xff0c;比如http-conduit。你可以使用cabal包管理器来安装它。 然后&#xff0c;你需要定义一个函数来处理HTTP请求。这个函数需要接受一个URL和一个代理服务器的地址作为参数。 import Network.HTTP.ConduitgetURL :: String…

使用Python自动检测SSL证书是否过期

目录 一、概述 二、SSL证书过期检测原理 三、Python实现SSL证书过期检测 四、注意事项 总结 一、概述 随着互联网的普及和安全意识的提高&#xff0c;SSL证书的使用变得越来越重要。SSL证书可以提供加密通信&#xff0c;保护用户的数据安全&#xff0c;防止中间人攻击等。…

单链表按位序插入

按位序插入(带头结点) #define NULL 0typedef struct LNode {int data;struct LNode *next; }LNode,*LinkList;//在第i个位置插入数据e&#xff08;带头结点&#xff09; bool ListInsert(LinkList &L, int i, int e){if (i<1)return false;LNode *p L; //指…

js运算,笔试踩坑知识点

文章目录 前端面试系列运算符记住口诀先计算 后 赋值赋值从右向左 和 - -计算从左向右括号里的加减优先于括号外的乘除交换俩数的值答案 前端面试系列 js运算 笔试踩坑知识点 前端js面试题 &#xff08;三&#xff09; 前端js面试题&#xff08;二&#xff09; 前端js面试题 (…

mac-Yarn安装成功但提示 command not found 解决方案

文章目录 查看yarn配置卸载yarn删除注册表清除yarn缓存npm安装yarn安装完成后yarn -v提示command not found&#xff0c;故选择使用命令重新安装命令安装yarn然后打开.bash_profile文件&#xff1a;参考&#xff1a;https://www.python100.com/html/119013.html 最近遇到项目使…

ChatGPT、GPT-4 Turbo接口调用

接口地址 https://chat.xutongbao.top/api/light/chat/createChatCompletion 请求方式 post 请求参数 model可选值&#xff1a; “gpt-3.5-turbo-1106”、 “gpt-3.5-turbo-16k” 、 “gpt-4”、“gpt-4-1106-preview”。 默认值为&#xff1a; “gpt-3.5-turbo-1106” to…

【overleaf参考文献引用】Citation `r51‘ on page 1 undefined on input line 46

overleaf 编辑插入参考文献出现如下问题&#xff1a; 显示如下&#xff1a;连着三个参考文献有一个显示为问号&#xff0c;latex的错误如上&#xff1a; Citation r51 on page 1 undefined on input line 46 问题原因&#xff1a; 在文档的第一页&#xff08;Page 1&#xff0…

Docker部署ubuntu1804镜像详细步骤

Docker部署ubuntu1804镜像详细步骤 ubuntu镜像库地址&#xff1a;https://hub.docker.com/_/ubuntu/tags?page1&ordering-name 拉取镜像&#xff08;默认为最新版本&#xff09;&#xff1a; docker pull ubuntu或&#xff0c;拉取指定版本镜像&#xff1a; docker pull…

【JMeter】组件之 Listener监听器选择

JMeter中监听器的作用就是收集、显示JMeter取样器的结果&#xff0c;并以树形、图表、表格的形式显示出来。还可以将监听结果保存成文件。 View Results Tree-->察看结果树 Summary Report-->汇总报告 为测试中的每个不同命名的请求创建一行。这与聚合报告类似&#xff…

海康Visionmaster-通讯管理:使用 Modbus TCP 通讯 协议与流程交互

使用 Modbus TCP 通讯协议与视觉通讯&#xff0c;当地址为 0000 的保持型寄存器(4x 寄存器)变为 1 时&#xff0c;触发视觉流程执行一次&#xff0c;同时视觉将地址为 0000 的寄存器复位&#xff08;也即写为 0&#xff09;&#xff0c;视觉流程执行完成后&#xff0c;将结果数…

后台管理系统实用提示框,JavaScript实现(成功,失败,提示弹窗)

本篇就给大家分享一下超级好用的JavaScript提示框&#xff0c;使其开发中节省大量代码&#xff01;&#xff01;&#xff01; 由于本篇运用到了jQuery技术&#xff0c;所以在写之前一定记得引入jQuery库 目录 首先呢我们需要创建html元素 设置css样式&#xff0c;直接引入…

041:vue中 el-table每个单元格包含多个数据项处理

第041个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

故障诊断模型 | Maltab实现ELM极限学习机的故障诊断

文章目录 效果一览文章概述模型描述源码设计参考资料效果一览 文章概述 故障诊断模型 | Maltab实现ELM极限学习机的故障诊断 模型描述 在机器学习领域,我们常常需要通过训练数据来学习一个函数模型,以便在未知的数据上进行预测或分类。传统的神经网络模型需要大量的参数调整和…

ChatGPT的图识别来了

前几天ChatGPT推出了Dall-E 3功能&#xff0c;可以根据文字和描述一段话来生成一个或者一组图。 这次又来重磅了&#xff0c;图识别又来了&#xff01;换句话说&#xff0c;也即是文生图&#xff0c;图生文都可以实现了&#xff0c;一起来试试 1、解释图中的意思 &#xff0…