标签:提高效率 har 种类 地址 ons test 操作符 fse private
从内存的角度考虑,不同情况下的C++类有什么区别呢?下面从空类、具有不同变量/函数、具有静态变量、继承、多态、虚拟继承等情况分析C++对象的内存空间大小和内存布局。本文讨论没有继承的情况,下一篇讨论有继承的情况
如无特别说明,本文代码均在64位机器上的VS2019运行。
class Test {
};
输出sizeof(Test)得到的结果为1。
空类的大小并不是0。这是因为空类也可以被实例化,而且它的每个实例也和其他实例一样在内存中拥有独一无二的地址。因此编译器会给空类加一个字节,这样在实例化时就可以给它的实例分配内存地址了。
class Test {
public:
//一般需要定义构造函数、复制构造函数和重载操作符时,类是有成员变量的,
//但这里仅为测试类的函数的内存占用情况,因此不设成员变量,且让函数为空
Test() {} //constructor
Test(const Test& t) {} //copy constructor
void func(){ //inline function
cout<<"hey hacker\n";
}
void func2(); //non-inline function
Test& operator =(const Test& t){} //operator
~Test() {} //destructor
};
void Test::func2() {
cout << "haha\n";
}
输出sizeof(Test)得到的结果为1。
我们测试了各种各样的非静态函数,最终输出结果表明它们都不占用对象的内存空间。事实也确实如此,C++对象模型中,函数不占用对象的内存。这是因为调用函数只需要知道函数的地址即可,而函数与类的实例无关。
这说明将函数封装并不需要额外的成本,C++在布局和存取时间上主要的额外负担是由virtual引起的。(下一篇会讨论有virtual的情况)
先介绍一个叫做padding的概念:为实例填充字节,使其大小成为某个数的整数倍(比如一个int和一个char一共占5字节,这时会再加上3字节的padding使其大小为8字节)。这通常是为了提高效率,具体实现取决于编译器。
这时就出现了两种情况:有padding和没有padding。
成员变量只有一种类型时没有padding
class Test {
public:
char c1;
char c2;
};//sizeof(Test) = 2*1(char) = 2 bytes
输出sizeof(Test)结果为2,说明实例没有被填充其他字节。
成员变量有多种时有padding
class Test {
public:
int a;
char c;
};//sizeof(Test) = 4(int) + 1(char) + 3(padding) = 8 bytes
输出sizeof(Test)结果为8,说明填充了3字节。
class Test {
public:
static int a;
static void func() {}
};
输出sizeof(Test)结果为1。这是因为静态成员变量只与类有关而与实例无关;C++对象模型中,静态成员不占用对象的内存空间。
概念介绍:access section包括public, private和protected三种段落,如果一个类的定义中有2个public和1个private,那么它就有3个access sections。
C++ Standard要求,在同一个access section内,只要较晚出现的成员在类对象中有较高的地址即可。因此成员在内存中未必是连续的,中间可能会被填充一些字节(上面提到的padding);也可能会有编译器合成的一些内部使用的成员(data member),以支持整个对象模型(比如指向虚函数表或虚拟继承中指向父类的指针)。
代码测试如下:
class Point {
public:
char x;
char y;
char z;
};
//main函数中:
printf("&Point::x = %p\n", &Point::x);//00000000
printf("&Point::y = %p\n", &Point::y);//00000001
printf("&Point::z = %p\n", &Point::z);//00000002
这里用的&Point::x
是表示x在Point对象中的相对位置,即偏移量(offset),而&x
是表示x的内存地址。这里输出成员变量的地址也可以,但输出偏移量会更直观一些。另外,注意这里要用printf,不要用cout。cout的输出都是1,无论位置和变量。
通过代码测试发现,x, y, z确实是按照声明顺序在内存中排列的。那么如果还有其他access section呢?
C++ Standard允许编译器将多个access sections中的成员自由排列而不必在乎它们在类中的声明顺序。但目前大部分编译器都是讲一个以上的access sections连锁在一起,依照声明顺序成为一个连续区块。而且access sections的数量不会带来额外负担,在3个public中声明3个int和在1个public中声明3个int,得到的对象大小是一样的。
代码测试如下:
class Point {
public:
char x;
char y;
private:
char a;
public:
char z;
static void where_is_a() {
printf("&Point::a = %p\n", &Point::a);
}
};
//main函数中:
printf("&Point::x = %p\n", &Point::x);//00000000
printf("&Point::y = %p\n", &Point::y);//00000001
printf("&Point::z = %p\n", &Point::z);//00000003
Point::where_is_a(); //00000002
可见编译器是按照各个成员的声明顺序将它们排列在了一起。
标签:提高效率 har 种类 地址 ons test 操作符 fse private
原文地址:https://www.cnblogs.com/saltedreed/p/11922532.html