对象大小基础
正如你在第4.1课 —— 基本数据类型简介中学到的那样,现代机器上的内存通常被组织成字节大小的单元,每个字节的内存都有一个唯一的地址。到目前为止,把内存想象成一堆小隔间或邮箱,我们可以把信息放进去并取出来,而变量则是访问这些小隔间或邮箱的名称,这一直很有用。
然而,这个类比在一方面并不完全正确 —— 大多数对象实际上占用的内存超过1个字节。一个单独的对象可能会使用1个、2个、4个、8个,甚至更多的连续内存地址。一个对象所使用的内存量取决于它的数据类型。
内存访问和编译器角色
由于我们通常通过变量名称(而不是直接通过内存地址)来访问内存,编译器能够隐藏一个给定对象使用了多少字节的细节。当我们在源代码中访问某个变量x时,编译器知道需要检索多少字节的数据(基于变量x的类型),并且会输出适当的机器语言代码来为我们处理这个细节。
对象大小的重要性
尽管如此,知道一个对象使用了多少内存还是很有用的,原因有几个。
信息存储容量
首先,一个对象使用的内存越多,它就能存储越多的信息。
一个单独的位可以存储2种可能的值,0或1:
位0 |
---|
0 |
1 |
2位可以存储4种可能的值:
位0 | 位1 |
---|---|
0 | 0 |
0 | 1 |
1 | 0 |
1 | 1 |
3位可以存储8种可能的值:
位0 | 位1 | 位2 |
---|---|---|
0 | 0 | 0 |
0 | 0 | 1 |
0 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
1 | 1 | 1 |
概括来说,一个有n位(n是整数)的对象可以存储(2^n)(2的n次方,也常写作2^n)个独特的值。因此,一个8位的字节大小的对象可以存储(2^8)(256)个不同的值。一个使用2个字节的对象可以存储(2^{16})(65536)个不同的值!
关键见解
新程序员往往过于关注优化代码以使用尽可能少的内存。在大多数情况下,这几乎没有区别。专注于编写可维护的代码,并且只在有实质性好处的地方进行优化。
基本数据类型大小
标准规定
C++标准并没有定义任何基本类型的精确大小(以位为单位)。相反,标准规定:
- 一个对象至少必须占用1个字节(以便每个对象都有一个独特的内存地址)。
- 一个字节至少有8位。
- 整型char、short、int、long和long long的最小大小分别为8、16、16、32和64位。
- char和char8_t正好是1个字节(至少8位)。
术语说明
当我们谈论一个类型的大小时,我们真正指的是该类型实例化对象的大小。
现代架构假设
在本教程系列中,我们将通过做出一些通常对现代架构成立的合理假设:
- 一个字节是8位。
- 内存是按字节寻址的(我们可以独立访问每个字节的内存)。
- 浮点支持符合IEEE-754标准。
- 我们处于一个32位或64位的架构上。
数据类型大小表
类别 | 类型 | 最小大小 | 典型大小 |
---|---|---|---|
布尔 | bool | 1字节 | 1字节 |
字符 | char | 1字节(正好) | 1字节 |
wchar_t | 1字节 | 2或4字节 | |
char8_t | 1字节 | 1字节 | |
char16_t | 2字节 | 2字节 | |
char32_t | 4字节 | 4字节 | |
整型 | short | 2字节 | 2字节 |
int | 2字节 | 4字节 | |
long | 4字节 | 4或8字节 | |
long long | 8字节 | 8字节 | |
浮点 | float | 4字节 | 4字节 |
double | 8字节 | 8字节 | |
long double | 8字节 | 8、12或16字节 | |
指针 | std::nullptr_t | 4字节 | 4或8字节 |
提示
为了最大限度地提高可移植性,你不应该假设对象大于指定的最小大小。
sizeof运算符使用
基本语法
C++提供了一个名为sizeof的运算符,它是一个一元运算符,用于确定特定机器上数据类型的大小。以下是使用示例:
#include <iomanip> // 为了std::setw(它设置后续输出的宽度)
#include <iostream>
#include <climits> // 为了CHAR_BIT
int main()
{
std::cout << "A byte is " << CHAR_BIT << " bits\n\n";
std::cout << std::left; // 左对齐输出
std::cout << std::setw(16) << "bool:" << sizeof(bool) << " bytes\n";
std::cout << std::setw(16) << "char:" << sizeof(char) << " bytes\n";
std::cout << std::setw(16) << "short:" << sizeof(short) << " bytes\n";
std::cout << std::setw(16) << "int:" << sizeof(int) << " bytes\n";
std::cout << std::setw(16) << "long:" << sizeof(long) << " bytes\n";
std::cout << std::setw(16) << "long long:" << sizeof(long long) << " bytes\n";
std::cout << std::setw(16) << "float:" << sizeof(float) << " bytes\n";
std::cout << std::setw(16) << "double:" << sizeof(double) << " bytes\n";
std::cout << std::setw(16) << "long double:" << sizeof(long double) << " bytes\n";
return 0;
}
输出示例:
bool: 1 bytes
char: 1 bytes
short: 2 bytes
int: 4 bytes
long: 4 bytes
long long: 8 bytes
float: 4 bytes
double: 8 bytes
long double: 8 bytes
变量使用sizeof
你也可以在变量名上使用sizeof运算符:
#include <iostream>
int main()
{
int x{};
std::cout << "x is " << sizeof(x) << " bytes\n";
return 0;
}
输出:
x is 4 bytes
注意:对于gcc用户
如果你没有禁用编译器扩展,gcc允许sizeof(void)返回1而不是产生诊断(Pointer-Arith)。我们在第0.10课 —— 配置你的编译器:编译器扩展中展示了如何禁用编译器扩展。
基本数据类型性能
在现代机器上,基本数据类型的对象速度很快,因此在使用或复制这些类型时,性能通常不应成为问题。
顺便说一句……
你可能会认为使用较少内存的类型会比使用更多内存的类型更快。这并不总是正确的。CPU通常被优化为处理特定大小的数据(例如32位),与该大小匹配的类型可能会被更快地处理。在这样的机器上,一个32位的int可能比一个16位的short更快。