当前位置: 首页 > news >正文

突破编程_C++_面试(基础知识(8))

面试题20:什么内存对齐

以结构体为例来说明内存对齐:
结构体对齐是编译器在内存中布局结构体成员时遵循的一种规则。对齐的目的是提高内存访问效率,减少因内存访问不对齐而引发的性能下降或硬件异常。
在大多数系统中,数据对齐通常是按字节进行的,并且某些类型的数据(如整数和浮点数)需要按特定的对齐要求进行存储。例如,一个 4 字节的整数可能需要存储在 4 字节对齐的地址上。
编译器通常会在结构体成员之间插入填充字节以确保正确的对齐。填充字节的具体数量取决于成员的类型、大小以及编译器的对齐规则。
结构体对齐的规则可以因编译器、目标平台和编译器设置的不同而有所差异。然而,有一些常见的对齐规则:
(1)默认对齐规则:编译器通常会按照成员的顺序和它们的大小来布局结构体。它会尝试将每个成员对齐到其类型所需的对齐边界上。
(2)指定对齐:可以使用 #pragma pack 指令或 [[gnu::packed]] 属性(取决于编译器)来指定结构体的对齐方式。例如,使用 #pragma pack(1) 可以告诉编译器按照 1 字节对齐来布局结构体,从而消除所有的填充字节。
(3)最大对齐规则:在某些情况下,编译器可能会根据结构体中最大对齐要求的成员来对齐整个结构体。这意味着结构体的大小可能是其最大对齐成员的整数倍。
(4)平台和编译器特定的对齐:不同的硬件平台和编译器可能有不同的默认对齐规则。例如,某些平台可能要求双字( double-word )类型(如 double 或 long long )按 4 字节或 8 字节对齐。
如下为样例代码:

#include <iostream>struct A
{int64_t val1;char val2;int val3;int val4;
};int main() {A a = { 1,0x02,3,4 };size_t len = sizeof(A);char* tmp = new char[len] {};memcpy(tmp,&a, len);delete[] tmp;tmp = nullptr;return 0;
}

对于 struct A ,具体的分配空间方式如下:
(1)为第一个成员 val1 分配空间,其起始地址跟结构的起始地址相同(恰好偏移量 0 且为 sizeof(int64_t) 的倍数),该成员变量占用 sizeof(int64_t)=8 个字节。
(2)为第二个成员 val2 分配空间,这时下一个能够分配的地址对于结构的起始地址的偏移量为8,是 sizeof(char) 的倍数,因此把 val2 存放在偏移量为 8 的地方,该成员变量占用 sizeof(char)=1 个字节。
(3)为第三个成员 val3 分配空间,这时下一个能够分配的地址对于结构的起始地址的偏移量为 9 ,不是 sizeof(int)=4 的倍数,为了知足对齐方式对偏移量的约束问题,编译器会自动填充 3 个字节,此时分配的地址对于结构的起始地址的偏移量为12,该成员变量占用 sizeof(int)=4 个字节。
(4)为第四个成员 val4 分配空间,这时下一个能够分配的地址对于结构的起始地址的偏移量为 16 ,正好是 sizeof(int)=4 的倍数,因此把 val4 存放在偏移量为 16 的地方,该成员变量占用 sizeof(int)=4 个字节。
(5)这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4+4=20,不是结构中占用最大空间的类型所占用的字节数( sizeof(double)=8 )的倍数,所以编译器还要填充 4 个字节,因此整个结构的大小为:sizeof(A)=8+1+3+4+4+3=20。
如下为调试中打印出来的内存字节(变量 tmp ):

-		tmp,24	0x00000281e0a73900 "\x1\0\0\0\0\0\0\0\x2ÌÌÌ\x3\0\0\0\x4\0\0\0ÌÌÌÌ"	char[24][0]	1 '\x1'	char[1]	0 '\0'	char[2]	0 '\0'	char[3]	0 '\0'	char[4]	0 '\0'	char[5]	0 '\0'	char[6]	0 '\0'	char[7]	0 '\0'	char[8]	2 '\x2'	char[9]	-52 'Ì'	char[10]	-52 'Ì'	char[11]	-52 'Ì'	char[12]	3 '\x3'	char[13]	0 '\0'	char[14]	0 '\0'	char[15]	0 '\0'	char[16]	4 '\x4'	char[17]	0 '\0'	char[18]	0 '\0'	char[19]	0 '\0'	char[20]	-52 'Ì'	char[21]	-52 'Ì'	char[22]	-52 'Ì'	char[23]	-52 'Ì'	char

注意其中 [9] 至 [11] 以及 [20] 至 [23] 是编译器填充的字节。
使用 #pragma pack(n) 可以指定一个对齐值 n,它告诉编译器按照 n 字节的边界进行对齐。这意味着编译器不会在成员之间插入更多的填充字节,除非这样做是为了确保下一个成员按照其类型所需的对齐要求进行存储。
例如,如果需要让结构体的成员紧密排列,不插入任何填充字节,可以使用 #pragma pack(1):

#include <iostream>#pragma pack(push, 1) // 保存当前对齐设置,并设置对齐为1字节  
struct A
{int64_t val1;char val2;int val3;int val4;
};
#pragma pack(pop) // 恢复之前的对齐设置  int main() {A a = { 1,0x02,3,4 };size_t len = sizeof(A);char* tmp = new char[len] {};memcpy(tmp,&a, len);// A 的大小将是 17 字节,而不是默认的可能是 24 字节(取决于平台和编译器)return 0;
}

需要注意的是,过度使用 #pragma pack 并将其设置得很小(如 1 )可能会导致内存访问性能下降,因为数据可能不会按照处理器最优的方式对齐。此外,某些硬件平台可能要求特定的对齐方式,因此在使用 #pragma pack 时需要谨慎考虑其对性能和可移植性的影响。

面试题21:结构体初始化由哪些方式

(1)默认初始化
当声明一个局部结构体变量时,如果没有显式地初始化它,那么它的成员将进行默认初始化。(通常是随机值,取决于内存的内容):

struct A
{int64_t val1;int64_t val2;
} ;A a;

变量 a 的值为:

a = {val1=-3689348814741910324 val2=-3689348814741910324 }

(2)聚合初始化
聚合初始化使用花括号 {} 来初始化结构体的成员。:

struct A
{int64_t val1;int64_t val2;
} ;A a = {1, 2}; 

变量 a 的值为:

a = {val1=1 val2=2 }

(3)指定成员初始化
在C++11及以后的版本中,可以使用指定成员初始化来明确指定要初始化的成员。这个特性适用于仅需要初始化部分成员变量:

struct A
{int64_t val1;int64_t val2;
} ;A a = { a.val1 = 1 };

变量 a 的值为:

a = {val1=1 val2=0 }

(4)构造函数初始化:
如果结构体有定义构造函数,那么可以使用构造函数来初始化结构体的成员。构造函数可以接收参数,并可以使用这些参数来初始化成员变量:

struct A
{A(int64_t val1In, int64_t val2In):val1(val1In), val2(val2In) {}int64_t val1;int64_t val2;
} ;A a(1, 2);

变量 a 的值为:

a = {val1=1 val2=2 }

(5)列表初始化:
列表初始化是 C++11 新标准中引入的一种新的初始化语法,它结合了聚合初始化和指定成员初始化的特点:

struct A
{int64_t val1;int64_t val2;
};A a{ 1, 2 };

变量 a 的值为:

a = {val1=1 val2=2 }

面试题22:如何将结构体作为函数的参数传递

使用结构体作为函数参数时,可以选择 按值传递 或 按引用传递 。按值传递会导致结构体的复制,这可能会消耗更多的资源,特别是当结构体很大时。按引用传递则避免了复制,但需要注意的是,如果再函数体内修改了引用的结构体,那么原始的结构体也会被修改。如果不希望原始结构体被修改,可以使用常量引用(这是最常用的结构体传参方式):

#include <iostream>
#include <string>using namespace std;struct Student
{string name;	//姓名    uint32_t age;	//年龄    double score;	//分数
};// 使用常量结构体引用作为函数参数
void printStudent(const Student& st)
{printf("name = %s, age = %u, score = %lf\n", st.name.c_str(), st.age, st.score);
}int main()
{Student st = { "zhangsan",10,98.5 };printStudent(st);return 0;
}

上面代码的输出为:

name = zhangsan, age = 10, score = 98.500000

面试题23:什么是结构体的位字段

结构体中的位字段变量( bit-field )是一种特殊的成员,用于在结构体中存储固定位数的数据。位域变量允许控制每个成员在内存中的确切位数,这在某些特定应用场景(如硬件编程、网络通信等)中非常有用,因为它可以实现更紧凑的数据存储和更精确的内存布局。
位域变量的声明方式是在结构体的成员声明中指定一个冒号和一个整数,这个整数表示该成员应该占用的位数。如下为样例代码:

#include <iostream>
#include <string>
#include <tuple>using namespace std;struct A {uint32_t val1 : 1; // 占用 1 个位  uint32_t val2 : 2; // 占用 2 个位  uint32_t val3 : 3; // 占用 3 个位   // 注意:编译器可能会插入填充位以满足内存对齐要求 ,不同编译器的 sizeof(A) 可能不一样
};int main() {A a;// 设置位域变量的值  a.val1 = 1; // 二进制:1 a.val2 = 2; // 二进制:10  a.val3 = 4; // 二进制:100  printf("val1 = %u, val2 = %u, val3 = %u\n", a.val1, a.val2, a.val3);printf("sizeof(A) = %u\n", sizeof(A));return 0;
}

上面代码的输出为:

val1 = 1, val2 = 2, val3 = 4
sizeof(A) = 4

注意,上面结构体 A 的理论大小为 6 个字节 ,不到 1 个字节,但是经过内存对齐,结构体 A 的实际大小为 4 个字节。

相关文章:

  • Python基础语法(内置Python, pycharm配置方式)
  • mmpose单机多卡训练问题
  • 黑马Java——集合进阶(List、Set、泛型、树)
  • Qt 数据库操作V1.0
  • 【链表】-Lc83-删除有序链表中的重复元素(快慢双指针,slow,fast)
  • 图解支付-金融级密钥管理系统:构建支付系统的安全基石
  • c语言贪食蛇游戏
  • P9240 [蓝桥杯 2023 省 B] 冶炼金属--2024蓝桥杯冲刺省一
  • leetcode | 杨辉三角 | 电话号码配对
  • 【Qt Design】界面介绍
  • 如何决定K8S Pod的剔除优先级
  • Linux下库函数、静态库与动态库
  • Ubuntu22.04切换系统cuda版本
  • 使用WPS制作三线表
  • Docker 常用命令详细介绍
  • 77. Combinations
  • C++类的相互关联
  • classpath对获取配置文件的影响
  • Consul Config 使用Git做版本控制的实现
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • ES6核心特性
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • github从入门到放弃(1)
  • Java Agent 学习笔记
  • JavaWeb(学习笔记二)
  • JS函数式编程 数组部分风格 ES6版
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Vue官网教程学习过程中值得记录的一些事情
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 初识 webpack
  • 第2章 网络文档
  • 对象引论
  • 工作中总结前端开发流程--vue项目
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 那些年我们用过的显示性能指标
  • 小程序开发之路(一)
  • 正则表达式小结
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • #stm32驱动外设模块总结w5500模块
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (C语言)fgets与fputs函数详解
  • (day6) 319. 灯泡开关
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (一)基于IDEA的JAVA基础10
  • (转)setTimeout 和 setInterval 的区别
  • (转)平衡树
  • ... 是什么 ?... 有什么用处?
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET 8.0 发布到 IIS
  • .NET delegate 委托 、 Event 事件