奇怪的数据对齐

Strange data alignment

据我所知,数据对齐是将数据放入内存中的 64 位/32 位块中以获得 CPU 性能,我使用的是 64 位 linux 机器,我做了一些测试和得到了一些奇怪的结果(我无法解释这种行为)。

以下是我使用的结构:

  class A {
    long l0,l1,l2;
  };

  class B {
    long l0,l1,l2,l3;
  };

  class C {
    long l0,l1,l2,l3,l4;
  };

测试:

int main() {
    C* newC = new C();
    B* newB = new B();
    A* newA = new A();
    int* i = new int();

    std::cout << sizeof(A) << std::endl;
    std::cout << sizeof(B) << std::endl;
    std::cout << sizeof(C) << std::endl;

    std::cout << "C : " << newC << std::endl;
    std::cout << "B : " << newB << std::endl;
    std::cout << "A : " << newA << std::endl;
    std::cout << "i : " << i << std::endl;

    delete (i);
    delete (newC);
    delete (newA);
    delete (newB);

    return 0;
}

只是将每个对象的1个对象放入堆中,我在末尾添加了一个指针以查看newA

占用的内存

结果是这样的:

24
32
40
C : 0x603010
B : 0x603040
A : 0x603070
i : 0x603090

3*16 bytes 介于 newCnewB 的地址之间:C 是 40 字节,已经是 64 位的倍数,为什么还要多 8 个字节??

3*16 bytesnewBnewA 之间?? B 只有 32 个字节,我预计:A : 0x603060

2*16 bytesnewAi的地址之间??

当你在堆上分配时,你不能对地址做出任何明确的声明。

内存分配函数很有可能维护有关已分配块的内联信息,这将影响后续块的地址,例如:

+--------+-------------+--------+-------------+
| header | alloced mem | header | alloced mem | ...
+--------+-------------+--------+-------------+

此外,为了提高效率,这些函数可能会将您的内存四舍五入为(例如)八或十六的倍数(您仍然不允许使用它,因为您不 知道)。这可能会进一步影响您看到的分配内存地址。


双方的经典案例可以看到:

#include <iostream>
#include <cstdlib>
int main (int argc, char *argv[]) {
    char *one = new char[std::atoi(argv[1])];
    char *two = new char[std::atoi(argv[1])];
    std::cout << static_cast<void*>(one) << '\n';
    std::cout << static_cast<void*>(two) << '\n';
    return 0;
}

和脚本:

#!/usr/bin/bash
for i in {01..37}; do
    echo $i $(./qq $i)
done

在我的系统上,输出:

01 0x800102e8 0x800102f8
02 0x800102e8 0x800102f8
:: (all the same address pairs in here and in gaps below)
12 0x800102e8 0x800102f8
13 0x800102e8 0x80010300
::
20 0x800102e8 0x80010300
21 0x800102e8 0x80010308
::
28 0x800102e8 0x80010308
29 0x800102e8 0x80010310
::
36 0x800102e8 0x80010310
37 0x800102e8 0x80010318

当您只分配一个字符时,两者之间的字节数高达 16

一直保持 十六个字节,一直到 new char[12] 并且每次添加八个字符后增加八个 似乎 表示四字节 header,header+ 数据最少十六字节,header+ 数据区域的八字节分辨率。

请记住,这是基于我对这些东西的书写方式的了解,而 受过教育 的猜测,仍然 一个猜测,你不应该依赖它。它可能会使用与我想象的完全不同的策略,或者它可能会针对更大的块改变策略。

如果您想知道 space 类型 真正 占用多少,请制作一个包含两个的数组并计算 x[0] 之间的内存地址差异] 和 x[1]。你会发现它应该和你从 sizeof.

得到的一样

您不能假定对 new 的后续调用会 return 内存中的连续块。

如果您想尝试测试用例,我建议您执行以下操作:

  struct D{
    C c;
    B b;
    A a;
  }

现在您可以开始打印地址了。但是,只有花车,我预计不会出现对齐问题。