c内存字节对齐(4.3更新)

字数 1780阅读 232

4.3:新增class的相关内容


今天看到一个题目:

字节对齐

最开始简单的理解为,每个数据的size之和就是偏移量。因为偏移量为8是因为两个int数据的大小。
但这只是巧合,理解存在错误。因为我尝试了一下这样:

#include <iostream>

struct mystruct
{
    char x;
    int y;
    int z;
};

int main()
{
    mystruct* ptr;
    ptr = NULL;
    int offsets_x = (int)(&(ptr->x));
    int offsets_y = (int)(&(ptr->y));
    int offsets_z = (int)(&(ptr->z));
    std::cout << offsets_x<<" "<<offsets_y<<" "<<offsets_z;
}

按上面的想法得到的应该是0,1,5,however,得到的是0,4,8
改成这样的话:

struct mystruct
{
    char x;
    char y;
    int z;
};

得到的是0,1,4
我直接蒙逼了。。

但如果都是char类型的话,就是0,1,2

这样得到的也是0,4,8

struct mystruct
{
    char x;
    int y;
    char z;
};

我感觉这个里面有一些什么rules。。
于是google了,发现了之前一直没思考过的问题:

字节对齐

  • 概念
    对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
  • 为什么要字节对齐
    需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

看四个重要的基本概念:
1,数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。
2, 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
3, 指定对齐值:#pragma pack (value)时的指定对齐值value。
4, 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值。
基于上面这些值,就可以方便地讨论具体数据结构的成员和其自身的对齐方式。

有效对齐N表示“对齐在N上”,即该数据的“存放起始地址%N=0”。而数据结构中的数据变量都是按定义的先后顺序存放。第一个数据变量的起始地址就是数据结构的起始地址。
结构体的成员变量要对齐存放
结构体本身也要根据自身的有效对齐值圆整(即结构体成员变量占用总长度为结构体有效对齐值的整数倍)

理解 结构体的成员变量要对齐存放

  • 假设B从地址空间0x0000开始存放,且指定对齐值默认为4(4字节对齐)。
  • 成员变量a的自身对齐值是1,比默认指定对齐值4小,所以其有效对齐值为1,其存放地址0x0000符合0x0000%1=0。
  • 成员变量b自身对齐值为4,所以有效对齐值也为4,只能存放在起始地址为0x0004~0x0007四个连续的字节空间中,符合0x0004%4=0且紧靠第一个变量。
  • 变量c自身对齐值为 2,所以有效对齐值也是2,可存放在0x00080x0009两个字节空间中,符合0x0008%2=0。所以从0x00000x0009存放的都是B内容。

理解 结构体本身也要根据自身的有效对齐值圆整

  • 再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x00000x0009=10字节,(10+2)%4=0。所以0x0000A0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12。

上面的概念非常便于理解,不过个人还是更喜欢下面的对齐准则。
结构体字节对齐的细节和具体编译器实现相关,但一般而言满足三个准则:

  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  • 结构体每个成员相对结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding)
  • 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节{trailing padding}。

对于以上规则的说明如下:

  • 第一条:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

  • 第二条:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员大小的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

  • 第三条:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

感谢http://www.cnblogs.com/clover-toeic/p/3853132.html

与class相关:

那么问题来了,一个class里面还有成员函数什么的呢?

首先,一个class的大小包括:

  • 非静态成员变量
  • 虚函数

不包括:

  • 静态成员变量

注意:

  • 存在字节对齐!

size:

  • 空类的size为1
  • 普通成员函数不占大小
  • 虚函数的话,大小是指针大小,4字节(因为要通过虚函数的指针去找到虚函数)

推荐阅读更多精彩内容

  • 什么叫做字节对齐? 对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐...
  • @[c++|struct] 今天在编程中碰到一个坑,搞的调试了半天,最后发现程序中在写数据和读取数据时结构体定义不...
  • 1. 对齐 需要各类型数据按照一定的规则在内存空间上排列,而不是顺序的排放,这就是对齐。 2. 对齐的原因 最常见...
  • 一、结构体变量在内存中存放的位置,也就是对齐方式,默认情况下是由编译器决定的。如果我们需要对其进行更改,可以使用:...
  • 文:北海先生 Muamu 为期10几天的征文大赛结束了 ,旅行·在路上和摄影专题收到了很多热心简友的投稿。 在路...