windows下和linux 高低字节,c++类的大小计算及分析windows中VS与Linux中gcc结果的差异...

2024-01-20 14:30

本文主要是介绍windows下和linux 高低字节,c++类的大小计算及分析windows中VS与Linux中gcc结果的差异...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

c++类的大小计算及分析windows中VS与Linux中gcc结果的差异

c++类的大小计算及分析windows中VS与Linux中gcc结果的差异

写在开头:文中提到Windows中VS用的是默认的MSVC编译器,Linux使用Gcc编译器。其实在Windows下使用MinGW可以得到与Gcc一样的结果。

另外,在结构体中存在long,int的时候还需要考虑编译器数据模型:关于int,long等数据类型占用字节数

喵哥最近在复习C++虚函数的时候遇到一道题,关于计算类的大小的问题,为了吃透这种题目,在网上找到几篇文章学习了一下。也发现了不少问题,借此契机记录一二。

C++的类大小计算通常会加入虚函数,静态成员,虚继承,多继承等情况,这些情况都对应着不同的计算方式。

在计算类占用空间的大小时,一般先粗略的用如下规则进行判断:

1.类大小的计算遵循结构体的对齐原则

2.类的大小与普通数据成员有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,

静态常量数据成员均对类的大小无影响(静态数据成员之所以不计算在类的对象大小内,是因为类的静态数据成

员被该类所有的对象所共享,并不属于具体哪个对象,静态数据成员定义在内存的全局区)

4.虚函数对类的大小有影响,是因为虚函数表指针带来的影响

5.虚继承对类的大小有影响,是因为虚基表指针带来的影响

6.空类的大小是一个特殊情况,空类的大小为1

VS与gcc的编译结果都是在继承的地方会出现偏差,所以在后面再具体区分gcc和VS,使用的都是x64,指针大小为8字节。

1.字节对齐。静态成员、普通成员函数无影响

#include

using namespace std;

class base

{

public:

base()=default; //构造函数

~base()=default;//析构函数

void haha(int x){ b = x;}

private:

static int a; //静态成员

int b;

char c;

};

int main()

{

base obj;

cout<

}

fd583857da27a9a941e47a8ac89ac18b.png

base的大小为8。用之前的判断方法,只有b和c是需要计算大小的,又由于字节对齐的要求,所以就是4+4 = 8。

需要注意的是这个字节对齐的方式,它是按照变量声明的顺序对齐的。比如:

int b;

char c1;

char c2;

a21a1813083efca61b06d11a1727e48c.png

这样的结果也是8,因为后面的俩char可以在一个4字节的段内。

而对于这样的情况:

char c1;

int b;

char c2;

a53e2f5b6f2155f2d7b36014595a3b2c.png

由于俩char分开了,总的大小变成12字节。

2.计算空类

对于一个不带任何数据的类,生成的对象大小不会为0,毕竟需要分配内存的,大小为1。

在继承空类时,这一个字节是不会被加到派生类中的。但是在一个类中声明一个空类对象为数据成员,那么在计算这个类时,需要加上这一个字节。

class Empty {};

class AA{

int x;

Empty e;

};

AA的大小为8。

3.含有虚函数的类

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类 中,编译器秘密地置入一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。

class Base {

public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

一个没有继承的其他虚函数表的类只有一个虚函数表,所以其对象只有一个虚函数表指针。在x64的环境下,大小为8.

348f8c72a1dcd84fc5b3f1505eabf526.png

虚函数指针在对象的最前面,所以在对齐字节的时候不用考虑虚函数声明的位置。

#include

using namespace std;

class Base {

public:

int a;

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

char c;

};

int main()

{

Base obj;

cout << sizeof(obj) << endl;

return 0;

}

这里的对齐方式是:

cbb52d6f57959c65fd75bc809da06e0e.png

所以结果为16。

4.含有虚函数的继承

class Base {

public:

int a;

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

char c;

};

class Derived : public Base

{

public:

virtual void f1() { cout << "Derived::f1" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

Derived的对象的虚函数表的指针指向的的结构为:

b07716eee98a0b1fedfc9e49d47b1d7f.png

1)虚函数按照其声明顺序放于表中。

2)基类的虚函数在派生类的虚函数前面。

此时基类和派生类的sizeof都是数据成员的大小+指针的大小8。

class Base {

public:

int a;

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

char c;

};

class Derived : public Base

{

public:

virtual void f() { cout << "Derived::f" << endl; }

virtual void g1() { cout << "Derived::g1" << endl; }

virtual void h1() { cout << "Derived::h1" << endl; }

};

此时Derived对象的虚函数表的指针指向的结构为:

9a3a3eaa7548f2b59326dff9f5949691.png

可以发现:

1)覆盖的f()函数被放到了虚表中原来基类虚函数的位置。

2)没有被覆盖的函数依旧。

派生类的大小仍是基类和派生类的非静态数据成员的大小+一个vptr指针的大小

所以继承一个基类的情况下,只会增加一个虚函数表。同理,简单的继承多个基类的情况,就会有同样个数的虚函数表。

继承关系:

6d41ddf9a8255c2a5267ed5dd9d872e4.png

派生类对象的虚函数表:

0d1fcb1b54b68006f63044afd30cd010.png

我们可以看到:

1) 每个基类都有自己的虚表。

2) 派生类的成员函数被放到了第一个基类的表中。(所谓的第一个基类是按照声明顺序来判断的)

由于每个基类都需要一个指针来指向其虚函数表,因此d的sizeof等于d的数据成员加上三个指针的大小。

继承的结构:

365b0b0fab222668e1b7b2d44cdbd47c.png

派生类对象的虚函数表:

27ff9951f147c4fb11720c694e447e0c.png

#include

using namespace std;

class A

{

/*char a;

long long aa;*/

};

class B

{

virtual void func0() { }

char ch;

char ch1;

};

class C

{

char ch1;

char ch2;

virtual void func() { }

virtual void func1() { }

};

class D : public A, public C

{

int d;

virtual void func() { }

virtual void func1() { }

};

class E : public B, public C

{

int e;

virtual void func0() { }

virtual void func1() { }

};

class F : public D, public E {

int f;

};

int main(void)

{

//测试结果再x64下运行

cout << "int = " << sizeof(int) << endl;

cout << "A=" << sizeof(A) << endl; //result=1

cout << "B=" << sizeof(B) << endl; //result=16

cout << "C=" << sizeof(C) << endl; //result=16

cout << "D=" << sizeof(D) << endl; //result=24

cout << "E=" << sizeof(E) << endl; //result=40

cout << "F=" << sizeof(F) << endl; //result=72

return 0;

}

注意:以上结果都是在VS的x64环境下运行的结果,如果在gcc下会不一样,具体表现为:

cout << "int = " << sizeof(int) << endl;

cout << "A=" << sizeof(A) << endl; //result=1

cout << "B=" << sizeof(B) << endl; //result=16

cout << "C=" << sizeof(C) << endl; //result=16

cout << "D=" << sizeof(D) << endl; //result=16

cout << "E=" << sizeof(E) << endl; //result=32

cout << "F=" << sizeof(F) << endl; //result=56

return 0;

c22c7610c7a7df5e8b764dbcb16e4eee.png

因为派生类会继承基类的普通数据成员,所以需要考虑数据的对齐方式。在windows中,各个基类的数据各自对齐,派生类也自己对齐。

f39c8f54a614b7a78de16c3ecafd8bc2.png

在linux下,最后一个基类的数据将和派生类的数据一起对齐。

用一个例子说明:

#include

using namespace std;

class B

{

virtual void func0() { }

char ch;

};

class C

{

char ch1;

short c2;

virtual void func() { }

virtual void func1() { }

};

class E : public B, public C

{

int e;

virtual void func0() { }

virtual void func1() { }

};

int main(void)

{

//测试结果再x64下运行

cout << "E=" << sizeof(E) << endl; //Windows result=40 Linux result=32

return 0;

}

分析:

在windows下,B、C、E类的数据成员分别对齐8字节,又有两个虚函数表的指针,所以8*5 = 40.

在Linux下,B单独对齐8字节,C、E的数据对齐8字节,加上两个虚函数表的指针,8*4 = 32.

并且这个例子可以说明,B、C、E三个类的数据没有一起对齐,假设三个类一起参与字节对齐,那么:

78a8349aa235060dd0881d3ac0d865b9.png

红绿蓝分别代表:char、short、int。完全可以对齐一个8字节空间,这样的话,结果应该是24,但是事实上在Linux中是32.

5.虚继承的情况

虚继承时,会产生虚基表,有一个虚基表指针,用来指向虚基类,多重继承虚基类就有多个虚基表指针。

如下例子(x64):

#include

using namespace std;

class A //大小为4

{

public:

int a;

};

class B :virtual public A //Windows 24 Linux 16

{

public:

int b;

};

class C :virtual public A //Windows 24 Linux 16

{

public:

int c;

};

class D :public B, public C //Windows 48 Linux 40

{

public:

int d;

};

int main()

{

A a;

B b;

C c;

D d;

cout << sizeof(a) << endl;

cout << sizeof(b) << endl;

cout << sizeof(c) << endl;

cout << sizeof(d) << endl;

return 0;

}

A类的对象大小为4字节是比较清楚的。

对于Windows和Linux的情况分别画图解释。

Windows:

B类继承了A类的int a(4字节),自己拥有一个int b(字节),并且有一个虚基表的指针(8字节),这个指针位于成员变量的前面。

4de8a0156d21acf0d0b0586230041a06.png

C类是类似的情况。

D类两个指针在前面,各自的成员变量各自对齐。

ddf33d514657ececd2dbb2d8635cfb16.png

Linux

类似于虚函数的继承情况,派生类与最后一个基类的成员变量进行对齐,拿D类作示例:

1b38e8143f39061a1e721d6e87b3c20d.png

C、D的成员变量一起参与对齐。

c++类的大小计算及分析windows中VS与Linux中gcc结果的差异相关教程

这篇关于windows下和linux 高低字节,c++类的大小计算及分析windows中VS与Linux中gcc结果的差异...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

深入理解C++ 空类大小

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

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(

Linux使用nohup命令在后台运行脚本

《Linux使用nohup命令在后台运行脚本》在Linux或类Unix系统中,后台运行脚本是一项非常实用的技能,尤其适用于需要长时间运行的任务或服务,本文我们来看看如何使用nohup命令在后台... 目录nohup 命令简介基本用法输出重定向& 符号的作用后台进程的特点注意事项实际应用场景长时间运行的任务服

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2