C++内存对齐的实现
作者:TABE_ 发布时间:2023-11-22 00:42:20
内存对齐的基本原则:
结构(struct/class)的内置类型数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的起始位置要从自身大小的整数倍开始存储(特别注意64位机器的指针大小为8个字节)。
如果一个结构A里有结构体成员B,则结构体B要从其内部"最宽基本类型成员”的整数倍地址开始存储(如struct a里存有struct b,b里有char, int, double等元素,那b应该从8的整数倍位置开始存储)。
结构体的总大小为结构体的有效对齐值的整数倍,结构体的有效对齐值的确定:
当未明确指定时,以结构体或结构体所包含结构体成员中最长的成员长度为其有效值。
当用#pragma pack(n)指定时,以n和结构体中最长的成员的长度中较小者为其值。
当用__attribute__ ((packed))指定长度时,强制按照此值为结构体的有效对齐值。
不管# pragma pack和__attribute__如何指定,结构体内部成员的自对齐仍然按照其自身的对齐值。
union以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。
空类/静态成员
程序 1
class A{
};
int main() {
cout << sizeof(A) << endl; // 1
}
对于一个什么都没有的空类,实际并不是空的,因为有默认的函数,具体可以参考 (待填入网址),大小是 1,这是因为需要有一个地址,C++ 不允许两个不同的对象有相同的地址,所以 C++ 中空的类和结构体大小都是 1。
程序 2
class A{
A(){}
~A(){}
void print() { printf("print()\n"); }
void foo() { printf("print()\n"); }
static void sprint() { printf("sprint()\n"); }
};
int main() {
cout << sizeof(A) << endl; // 1
}
这个类的大小仍然是1,成员函数、静态成员函数、静态成员变量都是不占用类的内存的,这是因为这些东西都是类的,而不是每个对象分别存储。static变量就是存储在全局静态区。
需要注意的是,子类继承空类后,子类如果有自己的数据成员,而空基类的1个字节并不会加到子类中去。
程序 3
class Empty {};
struct D : public Empty {
int a;
};
sizeof(D)为4。
再来看另一种情况,一个类包含一个空类对象数据成员,则空类对象的大小仍为1。
程序 4
class Empty {};
class HaveAnInt {
int x;
Empty e;
}
在大多数编译器中,你会发现 sizeof(HaveAnInt) 输出为8。这是由于,Empty类的大小虽然为1,然而为了内存对齐,编译器会为HaveAnInt额外加上一些字节,使得HaveAnInt被放大到足够又可以存放一个int。
内置类型数据成员
程序 1
class Data
{
char c;
int a;
};
cout << sizeof(Data) << endl;
程序 2
class Data
{
char c;
double a;
};
cout << sizeof(Data) << endl;
显然程序 1 输出的结果为 8,程序 2 输出的结果为 16 .
程序 1 最大的数据成员是4bytes,1+4=5,补齐为4的倍数,也就是8。而程序 2 为8bytes,1+8=9,补齐为8的倍数,也就是16。
程序 3
class Data
{
char c;
int a;
char d;
};
cout << sizeof(Data) << endl;
程序 4
class Data
{
char c;
char d;
int a;
};
cout << sizeof(Data) << endl;
程序 3 运行结果为 12,程序 4 运行结果为 8
class中的数据成员放入内存的时候,内存拿出一个内存块来,数据成员们排队一个一个往里放,遇到太大的成员时,不是将其劈成两半能放多少就放多少,而是等下一个内存块过来。这样的话,就可以理解为什么程序 3 和程序 4 两段代码输出结果不一样了,因为程序 3 是
1 + (3) + 4 + 1 + (3) = 12,而程序 4 是1 + 1 + (2) + 4 = 8。括号中为补齐的bytes。
结构体数据成员
在默认条件下,内存对齐是以class中最大的那个基本类型为基准的,如果class中的数据成员包含其他class,则递归的取其中最大的基本类型来参与比较。
程序 1
class BigData
{
char array[33];
};
class Data
{
BigData bd;
int integer;
double d;
};
cout << sizeof(BigData) << " " << sizeof(Data) << endl;
程序 2
class BigData
{
char array[33];
};
class Data
{
BigData bd;
double d;
};
cout << sizeof(BigData) << " " << sizeof(Data) << endl;
程序 1 和程序 2 运行结果均为:33 48
程序 1 和程序 2 中内存对其的基准均为8字节,BigData的大小均为33。在程序 1 中,BigData接下来是个int(4bytes),能够放下,这时候内存块还剩3bytes,而接下来是个double(8bytes),放不下,所以要等下一个内存快到来。因此,程序 1 的Data的size = 33 + 4 + (3) + 8 = 48,同理程序 2 应该是
33 + (7) + 8 = 48。
程序 3
class A {
public:
double len;
char str[33];
};
class B {
public:
A a;
int b;
};
cout << sizeof(A) << " " << sizeof(B) << endl;
以上代码输出的结果为: 48 56
不同于程序 1 和程序 2 ,程序 3 中的class A实际会占用41字节,但会发生8字节对齐,所以大小为48字节。对于class B,成员b的起始位置已发生8字节对齐,而class B整体还会发生8字节对齐,所以最终大小为56。
虚函数
C++ 的类中如果有虚函数,类内就会有一个虚函数表的指针 _vptr,指向自己的虚函数表,vptr 一般都是在类的最前边(取决于编译器的实现)。
class A {
public:
A(){}
virtual ~A(){}
virtual void foo(){}
virtual void print() {}
};
由于只是存一个指向虚函数表的指针,所以不管有多少个虚函数,都是 4 字节大小(32位下,任何指针大小都是 4,64位下,任何指针大小都是 8),比如上面这个类 A,size 就是 4。
需要注意的是就是,对于没有 override 的虚函数,基类和子类中 _vptr 指向的虚函数表中,这个虚函数的地址是一样的,也就是上边的 foo() 函数,而对于重写了的或者默认重写的析构函数来说,_vptr 指向的虚函数表中,函数地址是不一样的(当然两个类的 _vptr 地址也是不一样的,这是肯定的),这就能窥探到多态的实现了。
继承
不同的编译器对继承后类的大小的计算方式不同,有的是先继承后对齐,有的是先对齐后继承。
class A
{
int i;
char c1;
}
class B:public A
{
char c2;
}
class C:public B
{
char c3;
}
sizeof(C)结果是多少呢,gcc和vs给出了不同的结果,分别是8、16。
gcc中:C相当于把所有成员i、c1、c2、c3当作是在一个class内部,(先继承后对齐)
vs中:对于A,对齐后其大小是8;对于B,c2加上对齐后的A的大小是9,对齐后就是12;对于C,c3加上对齐后的B大小是13,再对齐就是16 (先对齐后继承)
内存对齐的意义
效率原因:经过内存对齐之后,CPU的内存访问速度大大提升。
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
比较两个结构体可以使用memcmp(void*, void*)吗?
不可以,memcmp函数是逐个字节进行比较的,而struct存在内存对齐,内存对齐时补的字节内容是垃圾值,所以无法比较。
来源:https://blog.csdn.net/TABE_/article/details/122223702
猜你喜欢
- 由于是多态对象,基类类型的变量可以保存派生类型。 要访问派生类型的实例成员,必须将值强制转换 * 生类型。 但是,强制转换会引发 Invali
- 1. dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们
- 本文为大家分享了10道springboot常见面试题,供大家参考,具体内容如下1.什么是Spring Boot?多年来,随着新功能的增加,s
- 本文实例为大家分享了java 利用Socket实现SMTP协议发送邮件的具体代码,供大家参考,具体内容如下package mail;impo
- 前言:一个游戏里的一个人物会存在多种状态,那么就需要有一个专门管理这些状态的类。不然会显得杂乱无章,不易于后面状态的增加或者减少。思路:既然
- 1.将本地jar包放入本地仓库。只需执行如下命令即可:mvn install:install-file -Dfile=D:/demo/fib
- java POI合并两个word文档有需要的可以将主函数中写死的地方改为一个Listimport java.io.FileInputStre
- 本文实例为大家分享了JavaMail实现带附件的邮件发送的具体代码,供大家参考,具体内容如下发送纯文本的邮件package com.haiw
- 本文实例讲述了Android编程实现小说阅读器滑动效果的方法。分享给大家供大家参考,具体如下:看过小说都知道小说阅读器翻页有好多种效果,比如
- 在java中常常会遇到这样一个问题,在实际应用中,总会碰到对List排序并过滤重复的问题,如果List中放的只是简单的String类型过滤s
- 本文实例为大家分享了SpringBoot整合BCrypt实现密码加密的具体代码,供大家参考,具体内容如下一. 首先在pom依赖中加入依赖:&
- 介绍: 本文章主要针对web项目中的两个问题进行详细解析介绍:1- 页面跳转404,即controller转发无法跳转页面问题;2- 静态资
- 不啰嗦,上菜 QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.se
- 背景近期在调研一个开源仓库,于是将 代码 从github下载后,当IDEA sync依赖时出现Cannot resolve org.four
- 也许是本人不才,初识Maven时,被各种不明所以的教程搞得一头雾水,而在后来的使用中,我发现Maven大部分功能没有想象的那么困难。本片文章
- 本文实例讲述了Java类加载器和类加载机制。分享给大家供大家参考,具体如下:一 点睛1 类加载器负责将.class文件(可能在磁盘上,也可能
- hashCode()和equals()方法可以说是Java完全面向对象的一大特色.它为我们的编程提供便利的同时也带来了很多危险.这篇文章我们
- Android 中下拉菜单,即如html中的<select>,关键在于调用setDropDownViewResource方法,以
- 先看下效果:两种需求场景:1.广告页3s后跳转到首页2.短信验证码60s倒计时第一种的话,根据需求我们可以知道,我们想要的效果就是3s结束做
- java如何实现ftp上传?如何创建文件夹?最佳答案:准备条件:java实现ftp上传用到了commons-net-3.3.jar包首先建立