作者 | 梁唐
出品 | 公众号:Coder梁(ID:Coder_LT)
大家好,我是梁唐。
今天我们来聊聊C++当中的内存占用,简单回顾一下C++当中的基本变量类型,以及它们分别占用的内存,从而方便我们对程序中使用的变量占用的内存有一个大概的认知。
C++当中提供了许多内置数据类型,下表中列出了其中的七种。
这里的宽字符型大家可能不太熟悉,其实和char
类似,也用来存储字符。不同的是char
只占一个字节,表示英文字母和一些标点符号没有问题,但是无法支持其他语言。而wchar_t
通常会占两个字节,采用unicode编码,因此可以正确存储汉字。
实际上在编译器内部,wchar_t
类型是这样定义的:
typedef short int wchar_t;
也就是说它等价于short int
类型。
接下来我们来看看这些类型占用空间的大小,以及它们对应的范围:
这里要注意,各种类型的存储大小与系统的位数有关,以上是64位系统中的结果。而在32位系统当中可能存在一定的差异,可以参考下表:
除了这些以外,还有指针需要特别注意。在32位系统当中,指针的大小是4个字节,而在64位系统当中,则是8个字节。
这是因为通常64位的系统拥有更大的内存,对于4个字节的指针来说,它最多完成
空间内的寻址,也就是4GB。当内存超过4GB时,4个字节的指针就无法表示所有地址了,因此要采用更长的8个字节的指针。
另外,变量类型占据的空间也和编译器版本有关,我们可以使用sizeof
函数查看变量类型占用的字节数。
我在菜鸟教程当中找到了完整的代码,大家可以在自己的编译器当中运行一下,查看每一种变量类型对应的内存大小。
#include<iostream>
#include <limits>
using namespace std;
int main()
{
cout << "type: \t\t" << "************size**************"<< endl;
cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);
cout << "\t最大值:" << (numeric_limits<bool>::max)();
cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;
cout << "char: \t\t" << "所占字节数:" << sizeof(char);
cout << "\t最大值:" << (numeric_limits<char>::max)();
cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;
cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);
cout << "\t最大值:" << (numeric_limits<signed char>::max)();
cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;
cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);
cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();
cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;
cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);
cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();
cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;
cout << "short: \t\t" << "所占字节数:" << sizeof(short);
cout << "\t最大值:" << (numeric_limits<short>::max)();
cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;
cout << "int: \t\t" << "所占字节数:" << sizeof(int);
cout << "\t最大值:" << (numeric_limits<int>::max)();
cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;
cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);
cout << "\t最大值:" << (numeric_limits<unsigned>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;
cout << "long: \t\t" << "所占字节数:" << sizeof(long);
cout << "\t最大值:" << (numeric_limits<long>::max)();
cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;
cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);
cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;
cout << "double: \t" << "所占字节数:" << sizeof(double);
cout << "\t最大值:" << (numeric_limits<double>::max)();
cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;
cout << "long double: \t" << "所占字节数:" << sizeof(long double);
cout << "\t最大值:" << (numeric_limits<long double>::max)();
cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;
cout << "float: \t\t" << "所占字节数:" << sizeof(float);
cout << "\t最大值:" << (numeric_limits<float>::max)();
cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;
cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);
cout << "\t最大值:" << (numeric_limits<size_t>::max)();
cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;
cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;
// << "\t最大值:" << (numeric_limits<string>::max)() << "\t最小值:" << (numeric_limits<string>::min)() << endl;
cout << "type: \t\t" << "************size**************"<< endl;
return 0;
}
另外一个和内存相关并且很重要的概念是内存对齐。
所谓内存对齐,其实是说变量在内存当中的摆放方式,并不是紧密的。从结构体的首地址开始,每个元素放置的时候,都会认为内存是按照自己的大小来划分的。元素在放置的时候,一定保证自己距离结构体的首地址刚好是自己宽度的整数倍。
这样说可能还有些抽象,我们可以来看一个简单的小例子:
#include<stdio.h>
struct{
char y;
int x;
}Test;
这里我们定义了一个结构体Test
,它当中有两个元素,分别是一个int
和一个char
。我们知道int
占据4个字节,而char
占据1个字节。按理说,整个结构体应该占据5个字节。
但是当我们使用sizeof
函数打印出它的大小时,会发现结果是8而非5。
int main() {
printf("%d\n",sizeof(Test)); // 输出8不是5
return 0;
}
这是因为char
占用了一个字节之后,在填入int
时它会跳过三个字节,从第四个字节处开始。也就是说char
和int
中间空了三个字节,这就是内存对齐。
看到这里,相信很多同学会感到很纳闷,这样对齐了之后不是浪费了内存了吗?
的确如此,我们浪费了一些内存空间。只不过由于摩尔定律,随着计算机行业的发展,内存的价格越来越便宜,浪费一点点无关痛痒。现在动辄32G甚至是64G的超大内存,十年前个人电脑的硬盘可能都没有这么大。
更重要的是内存对齐可以给我们带来很多好处,首先一个好处是可以提升性能。虽然我们的内存是以字节为单位的,但是CPU在读取内存的时候并不是以字节为单位读取的,而是按照CPU的位数来读取的。比如32位的CPU读取内存时就是一次4个字节,64位的就是8个字节。
如果没有内存对齐,那么很有可能一个变量刚好横跨了两次读取。那么CPU还需要读取之后再拼接,就会导致很多额外的工作。
在编译器中有一个参数叫做pragma pack(对齐系数)。gcc中默认是4,我们可以通过预编译命令#pragma pack(n)
来修改。
在对齐时,会区对齐系数和结构体中最长数据类型长度中较小的那个,这个值称为有效对齐值,也叫对齐单位。
内存对齐时会遵循两个规则:
比如还是刚才那个例子,一旦我们加上#pragma pack(1)
之后再运行,它的输出结果就变成了5。
#include<stdio.h>
#pragma pack(1)
struct {
int x;
char y;
}Test;
int main() {
printf("%d\n",sizeof(Test)); // 输出8不是5
return 0;
}
大家如果感兴趣还可以再写一些其他的例子进行尝试,还算是挺有意思的。
这篇文章的内容就到这里,如果喜欢,请不要忘了给予三连支持。如果有什么疑问或者想法,欢迎在文章下方给我留言。