结构体内存对齐(面试重点)

结构体内存对齐

    • 1. 结构体类型的声明
      • 1.1 结构体的概念
        • 1.1.1 结构的声明
        • 1.1.2 结构体变量的创建和初始化
      • 1.2 结构的特殊声明
      • 1.3 结构的自引用
    • 2. 结构体内存对齐
      • 2.1 对齐规则
        • 2.1.1 练习1:
        • 2.1.2 练习2:
        • 2.1.3 练习3:
        • 2.1.4 练习4:
      • 2.2 offsetof宏的使用
      • 2.3 为什么存在内存对齐?
      • 2.4 修改默认对齐数
    • 3. 结构体传参
      • 3.1 代码一
      • 3.2 代码二

1. 结构体类型的声明

1.1 结构体的概念

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.1.1 结构的声明
struct tag
{
	member-list;
}variable-list;

例如描述⼀个学⽣:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢
1.1.2 结构体变量的创建和初始化
#include <stdio.h>

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};

int main()
{
	//按照结构体成员的顺序初始化
	struct Stu s = { "张三", 20, "男", "20230818001" };
	printf("name: %s\n", s.name);
	printf("age : %d\n", s.age);
	printf("sex : %s\n", s.sex);
	printf("id : %s\n", s.id);

	//按照指定的顺序初始化
	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "女"};
	printf("name: %s\n", s2.name);
	printf("age : %d\n", s2.age);
	printf("sex : %s\n", s2.sex);
	printf("id : %s\n", s2.id);
	
	return 0;
}

运行结果如图:
在这里插入图片描述

1.2 结构的特殊声明

在声明结构的时候,可以不完全的声明。

比如:

//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c;
}*p;

上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。那么问题来了?

//在上⾯代码的基础上,下⾯的代码合法吗?
p = &x;

警告:

  • 编译器会把上⾯的两个声明当成完全不同的两个类型,所以是非法的。
  • 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

1.3 结构的自引用

在结构中包含⼀个类型为该结构本⾝的成员是否可以呢?

比如,定义⼀个链表的节点:

struct Node
{
 int data;
 struct Node next;
};

上述代码正确吗?如果正确,那 sizeof(struct Node) 是多少?

仔细分析,其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤⼩就会⽆穷的⼤,是不合理的。

正确的自引用方式:

struct Node
{
	int data;//存放数据
	struct Node* next;//存放下一个节点的地址
};

在结构体⾃引⽤使⽤的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引⼊问题,看看下⾯的代码,可行吗?

typedef struct
{
	int data;
	Node* next;
}Node;

答案是不⾏的,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使⽤Node类型来创建成员变量,这是不⾏的。

解决方案如下:定义结构体不要使用匿名结构体了

typedef struct Node
{
	int data;
	struct Node* next;
}Node;

2. 结构体内存对齐

现在我们深⼊讨论⼀个问题:计算结构体的大小。这也是⼀个特别热⻔的考点: 结构体内存对齐

2.1 对齐规则

首先得掌握结构体的对齐规则:

  1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

  2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值

    - 结构体内存对齐VS 中默认的值为 8

    - Linux中 gcc 没有默认对齐数,对⻬数就是成员自身的大小

  3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

  4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

2.1.1 练习1:
//练习1
#include <stdio.h>
struct S1
{
	char c1;//0(偏移量为0的地址上)
	char c2;//1
	//因为是int,根据规则二,要对齐到4的的整数倍的地址处
	int i;//4~7
	//0~7一共7个字节
};

int main()
{
	printf("%zd", sizeof(struct S1));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.1.2 练习2:
//练习2:
#include <stdio.h>
struct S2
{
	char c1;//0(偏移量为0的地址上)
	int i;//4~7
	char c2;//8
	//对齐到4的整数倍,9~11,总共12个字节
};

int main()
{
	printf("%zd", sizeof(struct S2));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.1.3 练习3:
//练习3
#include <stdio.h>
struct S3
{
	double d;//0~7
	char c;//8
	int i;//12~15
	//一共16个字节
};

int main()
{
	printf("%zd", sizeof(struct S3));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.1.4 练习4:
//练习4-结构体嵌套问题
#include <stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char c1;//0
	struct S3 s3;//8~23(嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处)
	double d;//24~31
	//一共32个字节
};

int main()
{
	printf("%zd", sizeof(struct S4));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.2 offsetof宏的使用

在这里插入图片描述

offsetof (type,member)
  • offsetof - 计算结构体成员相较于起始位置的偏移量
  • 第一个参数是结构体类型,第二个参数是结构体成员名
  • 使用offsetof宏需要包含头文件 #include <stddef.h>

代码举例:

#include <stdio.h>
#include <stddef.h>
struct S3
{
	double d;
	char c;
	int i;
};

//练习4-结构体嵌套问题
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

int main()
{
	printf("%d\n", offsetof(struct S4,c1));
	printf("%d\n", offsetof(struct S4,s3));
	printf("%d\n", offsetof(struct S4,d));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.3 为什么存在内存对齐?

大部分的参考资料都是这样说的:

  1. 平台原因 (移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在⾃然边界上对齐。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。

那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到:

让占用空间小的成员尽量集中在一起

总体来说:结构体的内存对齐是拿空间来换取时间的做法。

 //例如:
struct S1
{
	char c1;
	int i;
	char c2;
	};

struct S2
{
	char c1;
	char c2;
	int i;
};

S1 S2 类型的成员⼀模⼀样,但是 S1S2 所占空间的大小有了⼀些区别。

2.4 修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S));
	return 0;
}

在这里插入图片描述

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。

3. 结构体传参

3.1 代码一

#include <stdio.h>

struct S
{
	int data[1000];
	int num;
};

void print1(struct S t)
{
	printf("%d %d\n", t.data[0], t.num);

}

int main()
{
	struct S s = { {1,2,3,4,5},100 };
	print1(s);
	return 0;
}

运行结果如图:
在这里插入图片描述

3.2 代码二

#include <stdio.h>

struct S
{
	int data[1000];
	int num;
};

void print1(struct S t)
{
	printf("%d %d\n", t.data[0], t.num);

}

void print2(struct S* ps)
{
	printf("%d %d\n", ps->data[0], ps->num);
}

int main()
{
	struct S s = { {1,2,3,4,5},100 };
	print1(s);
	print2(&s);
	return 0;
}

运行结果如图:
在这里插入图片描述

上面的 print1 和 print2 函数哪个好些?

答案是:首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。

结论:
结构体传参的时候,要传结构体的地址。

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

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

相关文章

Vue2的双向数据绑定

Vue2的双向数据绑定 Observer&#xff1a;观察者&#xff0c;这里的主要工作是递归地监听对象上的所有属性&#xff0c;在属性值改变的时候&#xff0c;触发相应的watcher。 Watcher&#xff1a;订阅者&#xff0c;当监听的数据值修改时&#xff0c;执行响应的回调函数&#x…

基于Springboot的民宿在线预定平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的民宿在线预定平台(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

通过完善价值观评价,建立企业多维度评价体系

一、背景A公司是一家互联网公司&#xff0c;主要负责技术开发、软件应用方面的工作&#xff0c;致力于长期的软件研发、服务器开发、游戏端开发等&#xff0c;依托于专业技术实力和长期的实践积累&#xff0c;公司不断整合各类资源、深入开发技术&#xff0c;规模不断扩大&…

详解Redisson

第1章&#xff1a;Redisson简介 大家好&#xff0c;我是小黑&#xff0c;咱们今天来聊聊Redisson&#xff0c;Redisson不只是简单地对Redis进行了封装&#xff0c;它还提供了一系列高级的分布式Java数据结构&#xff0c;像是分布式锁、原子长整型这种。 首先&#xff0c;Redi…

02_Collection

文章目录 集合Java的集合类 Collectioniterator方法 集合 在Java中&#xff0c;指的就是存放数据的容器&#xff0c;是一个载体&#xff0c;可以一次容纳多个对象。 解决Bug的两种方法&#xff1a; 打印 System.out.println();log.info(); debug 检查数据 Java的集合类 Co…

项目管理十大知识领域之项目干系人管理

一、项目干系人管理的概念解析 项目干系人管理是指在项目执行过程中&#xff0c;对项目相关方的需求进行识别、分析和管理的过程。项目干系人管理的核心在于有效地沟通、协调和满足各方的需求&#xff0c;以确保项目能够顺利实施并达到预期的成果。在现代项目管理实践中&#…

conda国内加速

1、配置国内源 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ 2、显示源地址 conda config --set show_channel_urls yes

Python实现GEE嵌套协方差结构仿真模型(GEE算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 广义估计方程&#xff08;Generalized Estimating Equations, GEE&#xff09;是一种用于分析具有重复…

CC工具箱使用指南:【按条件选择排序】

一、简介 此工具来自一个群友的需求&#xff0c;做完想想可能会有同样需求的人用得到&#xff0c;就稍改了一下做成公共版本。 具体需求是这样的&#xff1a; 1、按条件选择对部分图斑进行排序&#xff0c;比如说在所有地类中&#xff0c;只想对地类名称为【林地】的图斑进行…

【Elasticsearch】索引恢复(recovery)流程梳理之副本分片数据恢复

replica shard重启具体流程 replica shard node &#xff08;generic threadpool&#xff09; 也是因为应用新的集群状态触发recovery&#xff0c;进入index阶段进入translog 阶段。先尝试重放本地的translog到global checkpoint向primary shard发起start recovery的请求&…

C++参悟:正则表达式库regex

正则表达式库regex 一、概述二、快速上手Demo1. 查找字符串2. 匹配字符串3. 替换字符串 三、类关系梳理1. 主类1. basic_regex 2. 算法1. regex_match2. regex_search3. regex_replace 3. 迭代器4. 异常5. 特征6. 常量1. syntax_option_type2. match_flag_type3. error_type 一…

【Linux】第三十站:进程间通信

文章目录 一、是什么二、为什么三、怎么办四、管道1.什么是管道2.管道的原理3.接口4.编码实现5.管道的特征6.管道的四种情况 一、是什么 两个或者多个进程实现数据层面的交互 因为进程独立性的存在&#xff0c;导致进程通信的成本比较高 通信是有成本的&#xff0c;体现在要打破…

集美大学“第15届蓝桥杯大赛(软件类)“校内选拔赛 D矩阵选数

经典的状态压缩DP int dp[15][(1<<14)10]; int a[15][15]; void solve() {//dp[i][st]考虑到了第i行 并且当前考虑完第i行以后的选择状态是st的所有方案中的最大值for(int i1;i<13;i)for(int j1;j<13;j)cin>>a[i][j];for(int i1;i<13;i){for(int j0;j<…

conda修改默认环境安装位置

conda修改默认环境安装位置 文章目录 conda修改默认环境安装位置查看conda配置信息创建.condarc&#xff08;conda runtime controlling)配置文件没有.condarc怎么办 即使创建正确放置了.condarc创建环境时还是默认指定C盘目录写权限目录修改权限 查看conda配置信息 conda con…

Flutter 页面嵌入 Android原生 View

前言 文章主要讲解Flutter页面如何使用Android原生View&#xff0c;但用到了Flutter 和 Android原生 相互通信知识&#xff0c;建议先看完这篇讲解通信的文章 Flutter 与 Android原生 相互通信&#xff1a;BasicMessageChannel、MethodChannel、EventChannel-CSDN博客 数据观…

1027 打印沙漏 (20)

本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”&#xff0c;要求按下列格式打印 ************ *****所谓“沙漏形状”&#xff0c;是指每行输出奇数个符号&#xff1b;各行符号中心对齐&#xff1b;相邻两行符号数差2&#xff1b;符号数先从大到小顺序递…

数据结构:顺序表 模拟实现及详解

目录 一、线性表 二、顺序表 2.1顺序表的概念及结构 2.1.1静态顺序表 2.2.2动态顺序表 2.2动态顺序表接口实现 一、线性表 线性表&#xff08; linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见…

如何使用iPhone或iPad上的二维码共享Wi-Fi密码?这里有详细步骤

你有没有想过在不泄露网络密码的情况下与客人共享你的家庭或工作Wi-Fi?你肯定不是第一个这样想的人,我们很高兴地通知你,多亏了以下这个的变通方法,你现在可以使用iPhone或iPad做到这一点。 通常,如果你想让其他人访问网络,你需要共享你的Wi-Fi密码。苹果通过引入与任何…

Jetpack Compose -> 分包 自定义Composable

前言 上一章我们讲解了 Compose 基础UI 和 Modifier 关键字&#xff0c;本章主要讲解 Compose 分包以及自定义 Composable&#xff1b; Compose 如何分包 我们在使用 Button 控件的时候&#xff0c;发现如果我们想给按钮设置文本的时候&#xff0c;Button 函数并没有直接提供…

读书笔记-《数据结构与算法》-摘要8[桶排序]

桶排序和归并排序有那么点点类似&#xff0c;也使用了归并的思想。大致步骤如下&#xff1a; 设置一个定量的数组当作空桶。Divide - 从待排序数组中取出元素&#xff0c;将元素按照一定的规则塞进对应的桶子去。对每个非空桶进行排序&#xff0c;通常可在塞元素入桶时进行插入…