本文将通过测试C++中sizeof类大小,进一步理解进程内存空间,并理解const局部变量的生命周期不发生改变的原因。
class A {...};
简单来说,sizeof(A)
是用来获取**「编译器需要为一个类(在堆/栈上)分配空间的大小」**
注意关注.text
, .data
, .bss
, .rodata
都存储了什么内容(它们均不属于堆/栈的范畴,一般被称为静态存储区)。
class A {
};
std::cout << sizeof(A); // 1
这是因为空类也要进行实例化——即,即使是空类,也需要存储空间,但是由于类里面没有任何成员,编译器会对空类隐含添加一个字节(保证类在内存中有地址)。
class B {
public:
void B_print() {
cout << "B_print func" << endl;
}
};
std::cout << sizeof(B); // 1
因为类中的成员函数是存放在代码段(.text
),因此分配类B的时候,不需要为B_print()
在堆上分配任何空间。
这个部分主要是虚函数表,多继承的虚函数表的内容,如果对下面内容云里雾里,可以看我的文章 C++ 虚函数表解析(64位版)
class C {
public:
virtual void C_print1() {
cout << "C_print func" << endl;
}
};
std::cout << sizeof(C); // 8
这里sizeof(C)
= 8是因为,虚函数的特点:在定义了虚函数的类中,类中需要保存一个虚函数表的首地址,因为我这里是64bit系统,因此虚函数表的首地址是8B,所以输出为8。
class C2 {
public:
virtual void C2_print1() {
cout << "C2_print func" << endl;
}
virtual void C2_print2() {
cout << "C2_print func" << endl;
}
};
std::cout << sizeof(C2); // 8
正如3.1中所说,我们只是保存了虚函数表的首地址,因此还是8。示意图如下⬇️
class C3 : C {
};
std::cout << sizeof(C3); // 8
继承后C3类继承了C的虚函数表,因此也是输出8。
class C4 : C, C2 {
};
std::cout << sizeof(C4); // 16
这里也是继承的特性,当多重继承的时候,类型C4的首地址保存的是C的虚函数表首地址,下一个地址保存的是C2的虚函数表的首地址。如图所示⬇️
class D {
public:
uint8_t d0;
uint32_t d1;
uint64_t d2;
};
std::cout << sizeof(D); // 16
class E {
public:
E(int e){cout << "e = " << e << endl;}
~E(){}
private:
uint32_t e = 0;
};
std::cout << sizeof(D); // 4
这里有一个变量是uint32
因此输出结果是4B说明析构函数和构造函数也是不占用类的存储空间的。
// sizeof(F) = 1
class F {
public:
static uint64_t h;
};
std::cout << sizeof(F); // 1
静态变量是存储在.data
(其实这里应该是.bss
)中的,也不占用类的存储空间,因此这里输出sizeof(F)
是1。
PS: 已经初始化的静态变量放在.data
中;未初始化或者初始化为0的静态变量保存在.bss
段中。其实.bss
段和.data
段是完全重叠的,也就是.bss
中的数据全都是0,因此我们不需要进行存储,只需要做个标记就行。
// sizeof(H) = 32
class H {
public:
const string str_helo = "helo";
};
std::cout << sizeof(H); // 32
sizeof(H) = 32
着实令人困惑:按理说,const的内容是存放在.rodata
(只读数据段)中的,也就是理论上也并不会占用类的空间,但是为什么输出结果是32呢?这里和const变量的特性有关系。
.rodata
中,会发生什么?(生命周期发生变化).data
/ .bss
段,static的生命周期将是整个程序的生命周期,因为它并不存放在堆/栈上,而是存在静态存储区。 ——也就是如果我们简单的把const的局部变量放在.rodata
中,const的局部变量也会发生类似于static变量的特性,即生命周期是整个程序的生命周期,但是事实是,const仍然保留了局部变量的特性,它的生命周期相对普通局部变量并没有发生改变。
其实编译器做的是:在定义的时候将它放在.rodata
中,但是使用的时候是从.rodata
中copy一个变量过来。其实我们真正使用的是copy过来的局部变量,因此在我们看来生命周期没有发生改变。
回到sizeof的问题,因为编译器要对const局部变量做上述操作,因此我们在创建一个类的时候,要给cnost成员变量分配存储空间,所以sizeof(H) = 32
。
#include
using namespace std;
class A {
};
class B {
public:
void B_print() {
cout << "B_print func" << endl;
}
};
class C {
public:
virtual void C_print1() {
cout << "C_print func" << endl;
}
};
class C2 {
public:
virtual void C2_print1() {
cout << "C2_print func" << endl;
}
virtual void C2_print2() {
cout << "C2_print func" << endl;
}
};
class C3 : C {
};
class C4 : C, C2 {
};
class D {
public:
uint8_t d0;
uint32_t d1;
uint64_t d2;
};
// 包含构造函数和析构函数的类
class E {
public:
E(int e){cout << "e = " << e << endl;}
~E(){}
private:
uint32_t e = 0;
};
// sizeof(F) = 1
class F {
public:
static uint64_t h;
};
// sizeof(H) = 32
class H {
public:
const string str_helo = "helo";
};
void test_sizeof() {
// F::h = 1;
cout << "sizeof(A) = " <<sizeof(A) << endl; // 1
cout << "sizeof(B) = " <<sizeof(B) << endl; // 1
cout << "sizeof(C) = " <<sizeof(C) << endl; // 8
cout << "sizeof(C2) = " <<sizeof(C2) << endl; // 8
cout << "sizeof(C3) = " <<sizeof(C3) << endl; // 8
cout << "sizeof(C4) = " <<sizeof(C4) << endl; // 16
cout << "sizeof(D) = " <<sizeof(D) << endl; // 16
cout << "sizeof(E) = " <<sizeof(E) << endl; // 4
cout << "sizeof(F) = " <<sizeof(F) << endl; // 1
cout << "sizeof(H) = " <<sizeof(H) << endl; // 32
cout << "sizeof(H) = " <<sizeof(H) << endl; // 32
}
int main() {
test_sizeof();
exit(0);
}
注意我指的是Linux或者MacOS系统下⬇️
将8.1中代码保存在sizeof_class.cpp
中。
编译⬇️(命令行)
g++ sizeof_class.cpp -o sizeof_class
执行⬇️(命令行)
./sizeof_class
sizeof(A) = 1
sizeof(B) = 1
sizeof(C) = 8
sizeof(C2) = 8
sizeof(C3) = 8
sizeof(C4) = 16
sizeof(D) = 16
sizeof(E) = 4
sizeof(F) = 1
sizeof(H) = 32
sizeof(H) = 32
蔚天灿雨 2022.09.05