对象大小和sizeof运算符:C++内存管理基础

对象大小基础

正如你在第4.1课 —— 基本数据类型简介中学到的那样,现代机器上的内存通常被组织成字节大小的单元,每个字节的内存都有一个唯一的地址。到目前为止,把内存想象成一堆小隔间或邮箱,我们可以把信息放进去并取出来,而变量则是访问这些小隔间或邮箱的名称,这一直很有用。

然而,这个类比在一方面并不完全正确 —— 大多数对象实际上占用的内存超过1个字节。一个单独的对象可能会使用1个、2个、4个、8个,甚至更多的连续内存地址。一个对象所使用的内存量取决于它的数据类型。

内存访问和编译器角色

由于我们通常通过变量名称(而不是直接通过内存地址)来访问内存,编译器能够隐藏一个给定对象使用了多少字节的细节。当我们在源代码中访问某个变量x时,编译器知道需要检索多少字节的数据(基于变量x的类型),并且会输出适当的机器语言代码来为我们处理这个细节。

对象大小的重要性

尽管如此,知道一个对象使用了多少内存还是很有用的,原因有几个。

信息存储容量

首先,一个对象使用的内存越多,它就能存储越多的信息。

一个单独的位可以存储2种可能的值,0或1:

位0
0
1

2位可以存储4种可能的值:

位0位1
00
01
10
11

3位可以存储8种可能的值:

位0位1位2
000
001
010
011
100
101
110
111

概括来说,一个有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位的架构上。

数据类型大小表

类别类型最小大小典型大小
布尔bool1字节1字节
字符char1字节(正好)1字节
wchar_t1字节2或4字节
char8_t1字节1字节
char16_t2字节2字节
char32_t4字节4字节
整型short2字节2字节
int2字节4字节
long4字节4或8字节
long long8字节8字节
浮点float4字节4字节
double8字节8字节
long double8字节8、12或16字节
指针std::nullptr_t4字节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更快。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"