(嵌入式八股)第2章 C++(五)

2.41 为什么要字节对齐?

字节对齐(Memory Alignment)是指数据在内存中的存放方式,以确保数据类型的起始地址是其大小的倍数。字节对齐的主要目的是优化存储器访问速度,并减少存储器的浪费。以下是字节对齐的主要原因:

优化存储器访问速度

硬件架构通常要求数据按特定的边界进行存储。例如,32位系统通常要求32位(4字节)数据类型存储在4字节的地址上。如果数据没有对齐,处理器就需要执行额外的操作来访问不对齐的数据,这会导致性能下降。

  • 例子:假设在没有字节对齐的情况下,一个4字节的数据存储在地址为 0x1001 的位置,这就需要处理器进行两次内存访问操作:一次访问低地址部分,一次访问高地址部分,才能将数据正确读取。而如果数据按照对齐要求存储(例如,存储在 0x1000 或 0x1004 处),处理器只需一次内存访问就能直接读取整个数据,显著提高存取速度。

减少存储器浪费

通过字节对齐,计算机能够更有效地使用内存。如果数据结构没有对齐,可能会导致内存中留下“空洞”或“填充”空间,从而浪费内存。

在没有字节对齐的情况下,MyStruct 的大小可能是 5 字节,因为 a 占 1 字节,b 占 4 字节,但是 a 和 b 之间的内存间隙可能是 3 字节,从而浪费了 3 字节。

但是,如果进行字节对齐,int b 会被放置在 4 字节对齐的位置,可能会在 a 后面填充 3 字节的空间来确保 b 对齐。这可能会导致结构体的大小为 8 字节,但这样可以提高内存访问的效率,避免访问不对齐的内存。

硬件架构的要求

现代处理器的设计通常要求按照特定的对齐方式访问内存。某些处理器会对不对齐的内存访问产生错误或异常,甚至无法正确执行。因此,为了兼容硬件架构,进行字节对齐是必需的。

提高并行处理效率

许多现代处理器采用了SIMD(单指令多数据)指令集或多核处理架构,要求数据在内存中的布局是对齐的,以便进行高效的并行计算。如果数据不对齐,处理器在执行并行计算时可能需要进行额外的计算或内存访问,导致性能下降。

2.42 字节对齐的具体实现

字节对齐的规则通常由编译器或编程语言的标准定义,目的是为了确保数据按照硬件的要求进行存储,进而提高程序的执行效率。具体的字节对齐实现主要遵循以下几个原则:

数据类型的大小

  • 对齐要求: 在大多数系统中,数据类型的对齐要求通常是其大小的倍数。例如:int 类型在大多数系统中占用 4 字节,因此通常要求按 4 字节对齐。char 类型占 1 字节,它的对齐要求也是 1 字节。double 类型通常占 8 字节,因此需要按 8 字节对齐。

在这种情况下,char 类型的 a 可能会占用 1 字节,后面会有 3 字节的填充,使得 int 类型的 b 从 4 字节对齐的位置开始存储。

结构体和类的成员变量

结构体和类的成员变量在内存中的存放位置通常根据以下规则进行对齐:

  • 成员变量的对齐要求: 每个成员变量的对齐要求通常由其数据类型的大小决定。如果成员变量的数据类型占用 N 字节,那么它通常要求按 N 字节对齐。
  • 结构体整体对齐要求: 结构体或类的整体对齐要求通常是其最大成员对齐要求的倍数。也就是说,结构体或类的起始地址必须是最大对齐要求(例如,最大成员对齐要求是 8 字节,那么结构体的起始地址必须是 8 的倍数)。

  • char a 占 1 字节,接下来有 3 字节的填充,确保 int b 按 4 字节对齐。
  • int b 占 4 字节,接下来可能会有 4 字节的填充,确保 double c 按 8 字节对齐。
  • 整个结构体的大小通常会对齐到 8 字节的倍数(最大成员的对齐要求)。
  • 编译器控制字节对齐

    编译器通常会提供一些控制字节对齐的选项或指令,允许开发者精细化控制数据的内存布局。这些选项和指令通常有以下几种形式:

    • 对齐指令: 编译器提供的关键字或者编译器指令,可以显式地控制数据对齐方式。

    例如,在 GCC 中可以使用 __attribute__((aligned(N))) 来设置数据对齐:

    • 编译器选项: 许多编译器允许通过编译选项来控制对齐方式。例如,在 GCC 中可以使用 -fpack-struct 来禁用结构体内存对齐,从而节省内存空间:

    • 默认对齐: 默认情况下,编译器通常会按照平台的要求对数据进行对齐,以便在常见的硬件架构上获得最佳性能。

    2.43 struct 和 class 的区别

    虽然在 C++ 中,structclass 在功能上几乎相同,都可以包含成员变量、成员函数、构造函数、析构函数等,但它们之间的主要区别通常体现在访问控制和继承的默认方式上。以下是 structclass 的几个关键区别:

    默认访问权限

    • struct 默认的成员访问权限是 公共 (public),这意味着结构体的成员可以直接在外部访问。
    • class 默认的成员访问权限是 私有 (private),这意味着类的成员默认不可直接访问,除非通过公共的成员函数或友元函数。

    默认继承方式

    • struct 默认的继承方式是 公共继承 (public),这意味着从 struct 派生的类默认会继承基类的公共成员。
    • class 默认的继承方式是 私有继承 (private),这意味着从 class 派生的类默认会将基类的公共成员变为私有成员。

    使用习惯

    • struct 一般用于简单的数据结构,当不需要对成员进行访问控制时,使用 struct 更为合适。
    • class 更常用于实现复杂的数据类型,尤其是在实现面向对象编程时,使用 class 来封装数据和提供方法。

    成员变量和成员函数的默认访问权限

    • structstruct 中的成员变量和成员函数默认是 公共 (public) 的,可以在外部直接访问。
    • classclass 中的成员变量和成员函数默认是 私有 (private) 的,必须通过公共的成员函数来访问。

    成员访问控制

    • struct 结构体的成员可以直接访问,不需要通过任何方法封装或访问控制。
    • class 类的成员通常是私有的,需要通过公共成员函数(即 getter 和 setter)来访问或修改这些成员。

    默认的构造函数和析构函数

    • structstruct 中没有自动生成默认构造函数和析构函数,除非显式声明。
    • classclass 中会自动生成默认的构造函数和析构函数。如果没有显式声明,编译器会生成一个默认构造函数和析构函数。

    总结

    2.44 static静态成员变量

    静态成员变量是通过关键字 static 声明的,它是类的一部分,但与类的实例无关,多个对象共享同一个静态成员变量。下面是关于静态成员变量的详细说明:

    静态成员变量的共享性

    • 共享数据: 静态成员变量属于类,而不是类的实例,因此所有该类的对象都共享同一个静态成员变量。这意味着,无论创建多少个类的实例,它们都引用同一块内存位置。

    在上面的例子中,m_total 是静态成员变量,s1s2 是两个 Student 对象,它们共享同一份 m_total

    静态成员变量的内存分配

    • 内存位置: 静态成员变量与普通成员变量不同,它们并不随对象的创建和销毁而分配和释放内存。相反,静态成员变量在程序的全局数据区分配内存,并在程序结束时才释放内存。即使没有创建任何对象,静态成员变量也会在程序启动时分配内存。
    • 普通成员变量:在对象创建时分配内存,在对象销毁时释放内存。
    • 静态成员变量:在程序开始时分配内存,并在程序结束时释放内存,所有对象共享同一份内存。

    静态成员变量的初始化

    • 初始化要求: 静态成员变量必须在类外部进行初始化。虽然可以在类内部声明静态成员变量,但它必须在类外部单独初始化。

    如果不显式初始化静态成员变量,编译器会将其默认初始化为 0(对于静态变量在全局数据区)。

    静态成员变量的访问方式

    • 访问方式: 静态成员变量可以通过类名或对象名进行访问。无论使用哪个方式,访问的始终是同一份内存。
    • 通过类名访问: 推荐使用类名来访问静态成员变量,这样可以清晰地表明这是类的成员。
    • 通过对象名访问: 尽管可以通过对象名访问静态成员变量,但这会给人一种误导,因为静态成员变量不属于对象,而是类的所有对象共享的。

    总结

    • 共享性: 静态成员变量是类的所有对象共享的,所有对象访问同一份内存。
    • 内存分配: 静态成员变量在全局数据区分配内存,而不是在对象的栈上,且它的生命周期从程序开始到程序结束。
    • 初始化: 静态成员变量必须在类外进行初始化。
    • 访问方式: 静态成员变量既可以通过类名也可以通过对象名访问,但推荐通过类名来访问。

    2.45 静态成员函数与普通成员函数的区别?

    静态成员函数和普通成员函数之间有许多区别,主要包括调用方式、访问权限、存储方式、this 指针等方面。

    调用方式

    • 静态成员函数: 可以通过类名直接调用,无需创建类的对象。
    • 普通成员函数: 必须通过类的对象或指针来调用。

  • MyClass::staticFunc() 调用了静态成员函数,不需要创建对象。
  • obj.normalFunc() 通过对象调用普通成员函数。
  • 访问权限

    • 静态成员函数: 只能直接访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。
    • 普通成员函数: 可以访问类的所有成员,包括静态成员和非静态成员。

  • 静态成员函数 staticFunc() 无法访问 num(非静态成员变量),只能访问静态成员变量 staticNum
  • 普通成员函数 normalFunc() 可以访问静态成员和非静态成员。
  • 存储方式

    • 静态成员函数: 存储在类的命名空间中,所有对象共享同一个静态成员函数,不影响类的对象大小。
    • 普通成员函数: 存储在类的对象中,每个对象都有自己的成员函数。

    • 所有对象共享同一个静态成员函数 staticFunc()
    • 每个对象有自己的普通成员函数 normalFunc(),但每次调用时会共享相同的代码。

    this 指针

    • 静态成员函数: 没有 this 指针,不能访问对象的非静态成员。

    剩余60%内容,订阅专栏后可继续查看/也可单篇购买

    作者简介:仅用大半年时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验60+,收藏20+面经,分享自己的求职历程与学习心得。 专栏内容:最新求职与学习经验,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从测评,笔试,技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。

    全部评论
    点赞 回复 分享
    发布于 02-25 15:06 浙江
    字节对齐很重要
    点赞 回复 分享
    发布于 02-25 15:44 浙江
    好详细啊!
    点赞 回复 分享
    发布于 02-25 15:49 浙江
    结构体大小怎么算
    点赞 回复 分享
    发布于 02-25 15:49 浙江
    接好运
    点赞 回复 分享
    发布于 02-25 15:50 浙江
    求份资料
    点赞 回复 分享
    发布于 02-27 10:13 广东

    相关推荐

    C++的上限非常高,但是分阶段性逐步学习是没有问题的,一步步的学,慢慢领悟,总有一天会熟练掌握的。C++ 语言的学习其实就三个阶段就好了:(1) 入门阶段这个阶段的学习主要是熟悉 C++ 语言的语法知识。在这个阶段要做到理解对象的思想方法,培养自己的编程思维能力。目标是可以开发一些像贪吃蛇这种简单的控制台小程序。(2) 进阶阶段进阶阶段的学习主要是要掌握 C++ 标准模板库(STL)、设计模式、数据结构基础以及 UI 界面开发、数据库开发等高级技能。在这个阶段是要达到可以开发复杂的程序,达到工作中 C++ 开发程序员的能力。(3) 应用阶段这个是实战阶段,要具备一定的综合性应用软件开发能力。这个阶段就是多观摩别人的项目,看人家的写法,模仿项目,学习其中的思想,一点点的积累,一步步形成自己的东西,厚积而薄发,慢慢你就会发现你也可以了。注意!下面都是超极干的干货一、入门阶段入门阶段的学习主要是熟悉 C++ 语言的语法知识。除了基础的变量、常量、关键字、数据类型、运算符、数组、函数、指针、结构体外,还要学习 C++ 的面向对象编程思想、命名空间 namespace、引用、函数扩展、类的封装、构造和析构、继承、多态、异常处理等内容。语言部分的学习建议不要拖太久,一定要规划好时间,一鼓作气,不然自己容易泄气!1.视频推荐此时同学们应该是毫无基础或者稍微有点 C 语言基础的小白。对于小白来说,不建议上来就看书,因为干看看不懂,容易劝退。可以先从视频教程开始,教材为辅。我当初 C++ 视频是在 b 站看的黑马程序员的 C++ 课程(我不是他们的托儿从 0 到 1 教 C++,三百多个小节,每个小节时间都不是很长,除了个别几个在二十多分钟,其余的基本上都在几分钟到十几分钟之间。每一个阶段都会有相应的小项目教学,对初学者来说是很友好的。看视频的时候不是看看就过去了,编程毕竟是门一门手艺活,孰能生巧。建议一边看,一边将视频中的示例或者小项目教学自己也实现一下,刚开始不会可以照着敲,比只看不动手强一百倍。此外,我最近发现深蓝学院出品的「C++ 基础与深度解析」课程也很不错,深入基础,讲解语法细节。从基础语法讲到 Modern C++,从面向过程开发到新编程范式,对大家学习 C++ 很有帮助。2.书籍推荐入门阶段的书籍为辅,怎么为辅呢?就是视频看完一个阶段,然后就可以去看书上对应阶段的内容,这样看书,一方面看书的时候会很快,容易理解,另一方面可以印证自己在看视频的时候一些不太理解的地方。入门阶段推荐两本书,一本薄的,一本厚的,都是超级经典的书籍。《Essential C++》《Essential C++》是一本内容不多但很实用的 C++ 入门书籍,这本书强调的是快速上手与理解 C++ 编程。主要围绕一系列逐渐复杂的程序问题,以及用以解决这些问题的语言特性展开讲解。你不只学到 C++ 的函数和结构,也会学习到它们的设计目的和基本原理。《C++ Primer Plus》&《C++ Primer》很多人 C++ 入门的时候会推荐《C++ Primer Plus》,很多人 C++ 入门的时候会推荐《C++ Primer Plus》,我当年先看的也是这本书,当年 C 语言除了学校的教材,我看的就是《C Primer Plus》。这本书怎么说的,讲的超级全面,甚至有点过于全面了,书中的例子和课后习题循序渐进,不夸张的讲所有的知识点可能都囊括进去了,作者可能为了怕大家学不明白,讲的巨细,甚至我感觉都有点啰嗦,造成这本书巨厚,字又巨小,看完感觉近视又加了几度。当时我学习的时候《C++ Primer》还是第 4 版,现在都到第 5 版了!《C++ Primer》堪称 C++ 语法学习的最权威书籍,非常全面地讲解了C++的语法以及C++11的各种新特性,看完之后真的帮助特别大!如果有时间建议至少看两遍以上!时面向 C++ 语言的初学者,是一本很友好的自学教材!而且例程和习题丰富,相信认真读过之后,可以完成 C++ 语言入门这个目标!!如果你在这个阶段觉得差不多了,可以尝试找一些在线的练习题做下,如果你不知道去哪找,那可以去下面这个初学者练习编程巩固语法的绝佳去处。它有专门的 C++ 入门编程练习题,专门练习语法和大家的编程逻辑,从变量、数据类型这些基础语法,到数组、字符串这种复合类型,再到函数、面向对象,以及在 C++ 中很重要的 STL,最后再来点综合练习,差不多 70 多道题,够你练的。除了编程练习以外,如果你想知道你自己的知识点掌握的如何,也可以做一下专项练习。以类似试卷的形式,可以很好的检验自己的学习成果,不管是对之后应对考试,或者应付笔试面试都很有帮助。二、进阶阶段在进阶阶段,你已经对 C++ 有一定的认知了。这个时候我们可以深入学习 C++ 标准模板库(STL)、设计模式、数据结构基础以及 UI 界面开发、数据库开发等高级技能。1.书籍推荐《C++标准程序库》关于 STL,可以先读这本侯捷老师翻译的《C++ 标准程序库》。通过这本书对STL有个基本认识,学会使用 STL。《STL源码剖析》读完 《C++ 标准程序库》,就可以来读这本侯捷老师编写的《STL源码剖析》了。这本书建议必读!这本书讲解了 C++ 底层实现,主要包括 C++ 底层内存管理、各种容器的数据结构实现、常见算法的实现等。可以帮助深入理解C++底层,同时也是对数据结构的复习和巩固。《Effective C++》《Effective C++》讲了 C++ 编程的 55 条准则,提高你的 C++ 编程质量,也是侯捷老师翻译的!这本书有助于梳理在编写 C++ 程序时的一些常见错误和注意事项,也是面试常考的。《深度探索C++对象模型》《深度探索C++对象模型》这本书讲解了C++面向对象特性的底层实现机制。侯捷老师翻译的,看完这本书,对C++面向对象的理解帮助极大,建议必读!2.视频推荐不知道大家注意了没,上面我推荐了四本书,都和一个人有关:侯捷老师。书要么是他翻译的,要么是他写的,C++ 领域 YYDS!同意吧?侯捷老师当然也有讲课,针对书都有对应内容的视频课程!三、应用阶段其实编程语言就是要多练,怎么多练,就是代码量。自己多写,然后多观摩别人的项目,看人家的写法,模仿项目,学习其中的思想,一点点的积累,一步步形成自己的东西,厚积而薄发,慢慢你就会发现你也可以了。面经可以参考c++面经 总结的很详细https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
    点赞 评论 收藏
    分享
    02-24 14:00
    已编辑
    新余学院 C++
    软牛科技 C++逆向 试用期7k,转正8k(需加班)
    pengyany:我也是今天面过了软牛的逆向
    点赞 评论 收藏
    分享
    评论
    5
    5
    分享

    创作者周榜

    更多
    牛客网
    牛客企业服务