前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >原来C++变量在内存中不是紧密排列的,聊聊内存对齐

原来C++变量在内存中不是紧密排列的,聊聊内存对齐

作者头像
TechFlow-承志
发布2022-12-22 11:01:55
发布2022-12-22 11:01:55
1.4K00
代码可运行
举报
文章被收录于专栏:TechFlowTechFlow
运行总次数:0
代码可运行

作者 | 梁唐

出品 | 公众号:Coder梁(ID:Coder_LT)

大家好,我是梁唐。

今天我们来聊聊C++当中的内存占用,简单回顾一下C++当中的基本变量类型,以及它们分别占用的内存,从而方便我们对程序中使用的变量占用的内存有一个大概的认知。

C++基本内置类型

C++当中提供了许多内置数据类型,下表中列出了其中的七种。

这里的宽字符型大家可能不太熟悉,其实和char类似,也用来存储字符。不同的是char只占一个字节,表示英文字母和一些标点符号没有问题,但是无法支持其他语言。而wchar_t通常会占两个字节,采用unicode编码,因此可以正确存储汉字。

实际上在编译器内部,wchar_t类型是这样定义的:

代码语言:javascript
代码运行次数:0
运行
复制
typedef short int wchar_t;

也就是说它等价于short int类型。

接下来我们来看看这些类型占用空间的大小,以及它们对应的范围:

这里要注意,各种类型的存储大小与系统的位数有关,以上是64位系统中的结果。而在32位系统当中可能存在一定的差异,可以参考下表:

除了这些以外,还有指针需要特别注意。在32位系统当中,指针的大小是4个字节,而在64位系统当中,则是8个字节。

这是因为通常64位的系统拥有更大的内存,对于4个字节的指针来说,它最多完成

2^{32}

空间内的寻址,也就是4GB。当内存超过4GB时,4个字节的指针就无法表示所有地址了,因此要采用更长的8个字节的指针。

另外,变量类型占据的空间也和编译器版本有关,我们可以使用sizeof函数查看变量类型占用的字节数。

我在菜鸟教程当中找到了完整的代码,大家可以在自己的编译器当中运行一下,查看每一种变量类型对应的内存大小。

代码语言:javascript
代码运行次数:0
运行
复制
#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;  
}

内存对齐

另外一个和内存相关并且很重要的概念是内存对齐。

所谓内存对齐,其实是说变量在内存当中的摆放方式,并不是紧密的。从结构体的首地址开始,每个元素放置的时候,都会认为内存是按照自己的大小来划分的。元素在放置的时候,一定保证自己距离结构体的首地址刚好是自己宽度的整数倍。

这样说可能还有些抽象,我们可以来看一个简单的小例子:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>

struct{
    char y;
    int x;
}Test;

这里我们定义了一个结构体Test,它当中有两个元素,分别是一个int和一个char。我们知道int占据4个字节,而char占据1个字节。按理说,整个结构体应该占据5个字节。

但是当我们使用sizeof函数打印出它的大小时,会发现结果是8而非5。

代码语言:javascript
代码运行次数:0
运行
复制
int main() {
    printf("%d\n",sizeof(Test)); // 输出8不是5
    return 0;
}

这是因为char占用了一个字节之后,在填入int时它会跳过三个字节,从第四个字节处开始。也就是说charint中间空了三个字节,这就是内存对齐。

内存对齐的作用

看到这里,相信很多同学会感到很纳闷,这样对齐了之后不是浪费了内存了吗?

的确如此,我们浪费了一些内存空间。只不过由于摩尔定律,随着计算机行业的发展,内存的价格越来越便宜,浪费一点点无关痛痒。现在动辄32G甚至是64G的超大内存,十年前个人电脑的硬盘可能都没有这么大。

更重要的是内存对齐可以给我们带来很多好处,首先一个好处是可以提升性能。虽然我们的内存是以字节为单位的,但是CPU在读取内存的时候并不是以字节为单位读取的,而是按照CPU的位数来读取的。比如32位的CPU读取内存时就是一次4个字节,64位的就是8个字节。

如果没有内存对齐,那么很有可能一个变量刚好横跨了两次读取。那么CPU还需要读取之后再拼接,就会导致很多额外的工作。

内存对齐规则

在编译器中有一个参数叫做pragma pack(对齐系数)。gcc中默认是4,我们可以通过预编译命令#pragma pack(n)来修改。

在对齐时,会区对齐系数和结构体中最长数据类型长度中较小的那个,这个值称为有效对齐值,也叫对齐单位。

内存对齐时会遵循两个规则:

  • 结构体的第一个成员的偏移量为0,以后每个成员的偏移量都是它本身长度与有效对齐值中较小那个的整数倍。
  • 结构体的总大小是有效对齐值的整数倍。

比如还是刚才那个例子,一旦我们加上#pragma pack(1)之后再运行,它的输出结果就变成了5。

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
#pragma pack(1)

struct {
    int x;
    char y;
}Test;

int main() {
    printf("%d\n",sizeof(Test)); // 输出8不是5
    return 0;
}

大家如果感兴趣还可以再写一些其他的例子进行尝试,还算是挺有意思的。

这篇文章的内容就到这里,如果喜欢,请不要忘了给予三连支持。如果有什么疑问或者想法,欢迎在文章下方给我留言。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coder梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C++基本内置类型
  • 内存对齐
  • 内存对齐的作用
  • 内存对齐规则
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档