C语言结构体深度剖析:内存对齐与位段应用

news2024/10/6 0:49:10

目录:

  • 一、 引言✨
  • 二、 结构体的声明💫
    • 2.1 结构体的基础知识
    • 2.2 结构体的声明
    • 2.3 特殊的声明
  • 三、 结构体的自引用🌟
  • 四、 结构体变量的定义和初始化🌊
  • 五、 结构体的内存对齐🔉
  • 六、 默认对齐数的修改🌷
  • 七、 结构体的传参
  • 八、位段🌸
    • 8.1 位段的特征与声明
    • 8.2 位段的内存分配
    • 8.3 位段的跨平台问题
    • 8.4 位段的应用
  • 九、 总结

一、 引言✨

在C语言中,我们熟悉的基本数据类型包括整型(如 intshort)、字符型(如 char)、浮点型(如 floatdouble)等。然而,这些内置类型有时无法满足我们处理复杂对象的需求,例如描述一个学生、一本书等。为了应对这些情况,C语言提供了自定义数据类型,如结构体、枚举和联合体。其中,结构体是最常用的一种,它允许我们将不同类型的数据组合在一起,形成一个新的数据类型。

虽然许多人已经使用过结构体来解决实际问题,但结构体中仍有许多细节值得深入探讨。本文将详细介绍结构体的声明、自引用、变量的定义和初始化、内存对齐、默认对齐数的修改、传参以及位段等内容。

二、 结构体的声明💫

2.1 结构体的基础知识

结构体是C语言中一种非常重要的数据类型,它由一组称为成员变量的数据组成。每个成员可以是不同类型的变量,甚至可以是另一个结构体变量。结构体通常用于表示类型不同但相关的若干数据。

2.2 结构体的声明

结构体的声明格式如下:

struct tag {
    member-list;
} variable-list;
  • struct:结构体关键字,用于定义结构体类型。
  • tag:结构体标签,用于区分不同的结构体类型。
  • member-list:成员列表,包含结构体的成员变量。
  • variable-list:变量列表,可以在声明结构体类型的同时创建结构体变量。

例如,我们可以使用结构体来描述一个学生:

struct Student {
    char name[20]; // 姓名
    char sex[5];   // 性别
    char id[20];   // 学号
    int age;       // 年龄
    float score;   // 绩点
};

如果觉得结构体类型名太长,可以使用 typedef 进行重命名:

typedef struct Student {
    char name[20]; // 姓名
    char sex[5];   // 性别
    char id[20];   // 学号
    int age;       // 年龄
    float score;   // 绩点
} Stu;

2.3 特殊的声明

除了常规的声明方式,还可以使用不完全的声明,即匿名结构体类型:

struct {
    int a;
    char b;
    float c;
} x;

匿名结构体类型通常是一次性的,因为省略了标签,无法在其他地方重复使用该类型。

三、 结构体的自引用🌟

在创建链表时,结构体常用于表示链表的节点。每个节点包含数据域和指针域:

  • 数据域:存储当前节点的值。
  • 指针域:存储指向下一节点的地址。

例如:

typedef int ListDataType;
struct ListNode {
    ListDataType val; // 数据域
    struct ListNode* next; // 指针域
};

这种结构体中包含指向自身结构体变量的指针的方式称为结构体的自引用。需要注意的是,不能在结构体中直接包含自身类型的变量,因为这会导致无限递归,无法确定结构体的大小。

四、 结构体变量的定义和初始化🌊

定义结构体变量很简单,可以直接在声明结构体类型的同时定义变量,也可以在后续代码中定义:

struct Point {
    int x;
    int y;
} p1; // 声明类型的同时定义变量p1

struct Stu {
    char name[15]; // 名字
    int age;       // 年龄
};

int main() {
    struct Point p2; // 定义结构体变量p2
    struct Point p3 = {3, 4}; // 初始化
    struct Stu s = {"zhangsan", 20}; // 初始化
}

结构体嵌套结构体的初始化方式如下:

struct Point {
    int x;
    int y;
} p1; // 声明类型的同时定义变量p1

struct Node {
    int data;
    struct Point p;
    struct Node* next;
} n1 = {10, {4, 5}, NULL}; // 结构体嵌套初始化

int main() {
    struct Node n2 = {20, {5, 6}, NULL}; // 结构体嵌套初始化
}

五、 结构体的内存对齐🔉

结构体的内存对齐是一个重要的概念,它决定了结构体在内存中的存储方式。以下是内存对齐的规则:

  1. 结构体的第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 对齐数 = 编译器默认的对齐数与该成员大小的较小值。VS的默认对齐数为8。
  4. 结构体的总大小为最大对齐数的整数倍。
  5. 嵌套结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。

例如:

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

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

int main() {
    printf("%d\n", sizeof(struct S1)); // 输出12
    printf("%d\n", sizeof(struct S2)); // 输出8
}

六、 默认对齐数的修改🌷

C语言允许修改结构体的默认对齐数,使用 #pragma pack 预处理指令即可:

#include <stdio.h>
#pragma pack(1) // 修改默认对齐数为1

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

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

int main() {
    printf("%d\n", sizeof(struct S1)); // 输出6
    printf("%d\n", sizeof(struct S2)); // 输出6
}

七、 结构体的传参

结构体传参时,通常使用传址调用,以减少内存和时间的开销:

#include <stdio.h>

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

void print1(struct S s) {
    printf("%d", s.size);
}

void print2(struct S* sp) {
    printf("%d", sp->size);
}

int main() {
    struct S s1;
    print1(s1); // 传值调用
    print2(&s1); // 传址调用
}

八、位段🌸

8.1 位段的特征与声明

位段是一种特殊的结构体,具有以下特征:

  1. 成员必须是 intunsigned intchar 等整型家族的成员。
  2. 成员名后有一个冒号和数字,表示成员占多少个二进制位(bit位)。
  3. 位段的空间按照需要以4个字节(int)或1个字节(char)的方式来开辟。

例如:

struct A {
    char a : 1;
    char b : 4;
    char c : 5;
    char d : 5;
};

8.2 位段的内存分配

位段的内存分配方式在C语言中没有明确规定,不同的编译器可能有不同的实现。例如,在VS2022环境下,位段从右向左分配且不足时舍弃剩余位。

8.3 位段的跨平台问题

由于位段的内存分配方式不确定,位段的可移植性较差,存在跨平台问题。

8.4 位段的应用

位段在网络中的应用较多,例如IP数据包的封装,可以有效节省空间。

九、 总结

结构体是C语言中非常强大的工具,通过合理使用结构体,我们可以更好地组织和管理复杂的数据。深入理解结构体的声明、内存对齐、传参和位段等细节,有助于我们编写更高效、更可靠的代码。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DatePicker 日期控件

效果&#xff1a; 要求&#xff1a;初始显示系统当前时间&#xff0c;点击日期控件后修改文本控件时间。 目录结构&#xff1a; activity_main.xml(布局文件)代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:and…

环境可靠性

一、基础知识 1.1 可靠性定义 可靠性是指产品在规定的条件下、在规定的时间内完成规定的功能的能力。 可靠性的三大要素&#xff1a;耐久性、可维修性、设计可靠性 耐久性&#xff1a;指的是产品能够持续使用而不会故障的特性&#xff0c;或者说是产品的使用寿命。 可维修性&a…

1.MySQL存储过程基础(1/10)

引言 数据库管理系统&#xff08;Database Management System, DBMS&#xff09;是现代信息技术中不可或缺的一部分。它提供了一种系统化的方法来创建、检索、更新和管理数据。DBMS的重要性体现在以下几个方面&#xff1a; 数据组织&#xff1a;DBMS 允许数据以结构化的方式存…

【C++ STL】手撕vector,深入理解vector的底层

vector的模拟实现 前言一.默认成员函数1.1常用的构造函数1.1.1默认构造函数1.1.2 n个 val值的构造函数1.1.3 迭代器区间构造1.1.4 initializer_list 的构造 1.2析构函数1.3拷贝构造函数1.4赋值运算符重载 二.元素的插入,删除,查找操作2.1 operator[]重载函数2.2 push_back函数:…

读论文、学习时 零碎知识点记录01

1.入侵检测技术 2.深度学习、机器学习相关的概念 ❶注意力机制 ❷池化 ❸全连接层 ❹Dropout层 ❺全局平均池化 3.神经网络中常见的层

51c视觉~CV~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/11668984 一、 CV确定对象的方向 介绍如何使用OpenCV确定对象的方向(即旋转角度&#xff0c;以度为单位)。 先决条件 安装Python3.7或者更高版本。可以参考下文链接&#xff1a; https://automaticaddison.com/how-to-s…

【2024年最新】基于springboot+vue的毕业生信息招聘平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

基于keras的停车场车位识别

1. 项目简介 该项目旨在利用深度学习模型与计算机视觉技术&#xff0c;对停车场中的车位进行检测和状态分类&#xff0c;从而实现智能停车管理系统的功能。随着城市化的发展&#xff0c;停车场管理面临着车位检测效率低、停车资源分配不均等问题&#xff0c;而传统的人工检测方…

【Python】Dejavu:Python 音频指纹识别库详解

Dejavu 是一个基于 Python 实现的开源音频指纹识别库&#xff0c;主要用于音频文件的识别和匹配。它通过生成音频文件的唯一“指纹”并将其存储在数据库中&#xff0c;来实现音频的快速匹配。Dejavu 的主要应用场景包括识别音乐、歌曲匹配、版权管理等。 ⭕️宇宙起点 &#x1…

【AI知识点】泊松分布(Poisson Distribution)

泊松分布&#xff08;Poisson Distribution&#xff09; 是统计学和概率论中的一种离散概率分布&#xff0c;通常用于描述在固定时间或空间内&#xff0c;某个事件发生的次数。该分布适用于稀有事件的建模&#xff0c;特别是当事件发生是独立的、随机的&#xff0c;且发生的平均…

[Go语言快速上手]初识Go语言

目录 一、什么是Go语言 二、第一段Go程序 1、Go语言结构 注意 2、Go基础语法 关键字 运算符优先级 三、Go语言数据类型 示例 小结 一、什么是Go语言 Go语言&#xff0c;通常被称为Golang&#xff0c;是一种静态类型、编译型的计算机编程语言。它由Google的Robert Gr…

关闭IDM自动更新

关闭IDM自动更新 1 打开注册表2 找到IDM注册表路径 1 打开注册表 winR regedit 2 找到IDM注册表路径 计算机\HKEY_CURRENT_USER\Software\DownloadManager 双击LstCheck&#xff0c;把数值数据改为0 完成 感谢阅读

存储电话号码的数据类型,用 int 还是用 string?

在 Java 编程中&#xff0c;存储电话号码的选择可以通过两种常见方式进行&#xff1a;使用 int 类型或 String 类型。这种选择看似简单&#xff0c;但实际上涉及到 JVM 内部的字节码实现、内存优化、数据表示、以及潜在的可扩展性问题。 Java 基本数据类型与引用数据类型的差异…

Windows安全加固详解

一、补丁管理 使用适当的命令或工具&#xff0c;检查系统中是否有未安装的更新补丁。 Systeminfo 尝试手动安装一个系统更新补丁。 • 下载适当的补丁文件。 • 打开命令提示符或PowerShell&#xff0c;并运行 wusa.exe <patch_file_name>.msu。 二、账号管…

使用seata管理分布式事务

做应用开发时&#xff0c;要保证数据的一致性我们要对方法添加事务管理&#xff0c;最简单的处理方案是在方法上添加 Transactional 注解或者通过编程方式管理事务。但这种方案只适用于单数据源的关系型数据库&#xff0c;如果项目配置了多个数据源或者多个微服务的rpc调用&…

thinkphp 学习记录

1、PHP配置 &#xff08;点开链接后&#xff0c;往下拉&#xff0c;找到PHP8.2.2版本&#xff0c;下载的是ZIP格式&#xff0c;解压即用&#xff09; PHP For Windows: Binaries and sources Releases &#xff08;这里是下载地址&#xff09; 我解压的地址是&#xff1a;D:\…

Spring中Bean创建过程中各个阶段的作用

文章目录 Instantiate&#xff08;实例化&#xff09;Populate properties&#xff08;填充属性&#xff09;BeanNameAwares setBeanName()BeanFactoryAwares setBeanFactory()ApplicationContextAwares setApplicationContext()Pre-initialization BeanPostProcessorsInitiali…

【Python篇】从零到精通:全面分析Scikit-Learn在机器学习中的绝妙应用

文章目录 从零到精通&#xff1a;全面揭秘Scikit-Learn在机器学习中的绝妙应用前言第一部分&#xff1a;深入了解Scikit-Learn的基础知识1. 什么是Scikit-Learn&#xff1f;2. 安装Scikit-Learn3. Scikit-Learn中的基本构件4. 数据集的加载与探索5. 数据预处理标准化数据 6. 构…

【Kubernetes】常见面试题汇总(五十五)

目录 121. POD 创建失败&#xff1f; 122. POD 的 ready 状态未进入&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于【Kube…

C# 数组和集合

本课要点&#xff1a; 1、数组概述 2、一维数组的使用 3、二维数组的使用 4、数组的基本操作 5、数组排序算法 6、ArrayList集合 7、Hashtable类 8、常见错误 一 数组 1 数组引入1 问题&#xff1a; 简单问题&#xff1a;求4个整数的最大值&#xff1f; int a 40,…