C语言结构体的大小,结构体内存对齐

2024-03-07 01:36

本文主要是介绍C语言结构体的大小,结构体内存对齐,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 结构体的大小

在自己正真了解过之前,一直认为结构体的大小就是结构体内部成员大小的总和。

但当你去尝试打印结构体的大小时,会发现事实并非如此,也不会像你想的那样简单。

#include <stdio.h>struct S1
{char c1;char c2;int i;
};struct S2
{char c1;int i;char c2;
};int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0;
}

给出这样的两个结构体,它们的元素完全相同,只是定义的顺序不一样。

按照直觉来说,它们的大小都应该是两个字符和一个整形的大小的总和,也就是6。

但是当我们实际打印出来之后会发现,struct S1的大小是8,而struct S2的大小是12。

也就是说,结构体的大小肯定不只是由其成员的大小和数量决定,至少还会与其成员定义的顺序有关。

那么,结构体的大小到底存在着什么样的机制呢?

这个所谓的机制,就是结构体内存对齐。

2. 结构体内存对齐

结构体内存对齐,就是指结构体成员在被定义时所分配到的空间并不是连续的,而是会基于不同变量对齐数的不同,去对应某些固定的位置的空间。

2.1 为什么要对齐

按理来说,对齐应该会导致元素因为没有紧密排列而造成空间的浪费,那么为什么要对齐呢?

2.1.1 平台原因

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.1.2 性能原因

数据结构(尤其是栈)应该尽可能地在自然然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取4个字节,则地址必须是4的倍数。如果我们能保证将所有的int类型的数据的地址都对齐成4的倍数(相对于结构体首个字节的地址来说),那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个4字节内存块中。

以struct S2为例:

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

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

2.2 对齐规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量(相距的字节数)为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值。

- VS 中默认的值为 8

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

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

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

2.3 举例

2.3.1 struct S1

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

1. c1作为第一个成员,在结构体的首地址处;

2. c2对齐数为1,所以紧贴着上一个元素;

3. i对齐数为4,由于前四格空间已经被占用,所以i向后寻找到的第一个为4的倍数的地址如图;

4. 最大对齐数为4,而8刚好是4的倍数,于是结构体大小为4。

2.3.2 struct S2

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

1. c1作为第一个成员,在结构体的首地址处;

2.  i对齐数为4,由于前四格空间已经被占用,所以i向后寻找到的第一个为4的倍数的地址如图;

3. c2对齐数为1,紧贴着前面一个元素;

4. 最大对齐数为4,目前结构体已经占用了9个字节,由于结构体的大小需要是4的倍数,所以其大小只能为12了。

2.3.3 其他

struct S3//16
{double d;char c;int i;
};struct S4//32
{char c1;struct S3 s3;double d;
};

这两个案例可以自己尝试一下,要自己动手才能记得牢,不是我懒得写。

2.4 修改默认对齐数

用“#pragma()”这个预处理指令,可以修改默认对齐数。

#pragma pack(4)//默认对齐数改为4
#pragma pack()//恢复默认对齐数
#pragma pack(1)//等价于不对齐

这篇关于C语言结构体的大小,结构体内存对齐的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/782034

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

结构体和联合体的区别及说明

《结构体和联合体的区别及说明》文章主要介绍了C语言中的结构体和联合体,结构体是一种自定义的复合数据类型,可以包含多个成员,每个成员可以是不同的数据类型,联合体是一种特殊的数据结构,可以在内存中共享同一... 目录结构体和联合体的区别1. 结构体(Struct)2. 联合体(Union)3. 联合体与结构体的

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return