精华内容
下载资源
问答
  • C++内存对齐

    2018-06-13 10:05:48
    内存对齐用来做什么?所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存的读取操作是对齐的,如果x的地址不是4的倍数,...

    转自:https://www.cnblogs.com/xylc/p/3780907.html

    一,什么是内存对齐?内存对齐用来做什么?

    所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。

    比如对于int x;(这里假设sizeof(int)==4),因为cpu对内存的读取操作是对齐的,如果x的地址不是4的倍数,那么读取这个x,需要读取两次共8个字节,然后还要将其拼接成一个int,这比存取对齐过的x要麻烦很多。

     

    二,怎么算内存对齐大小(理论)?

    对于简单类型,如int,char,float等,其对齐大小为其本身大小,即align(int) == sizeof(int),align(char)==sizeof(char),等等。

    对于复合类型,如struct,class,其本身并无所谓对齐,因为CPU没有直接存取一个struct的指令。对于struct而言,它的对齐指的是它里面的所有成员变量都是对齐的,class同理。

    下面就讲讲struct对齐是怎么回事。

    首先要明白三个点:

    1,内存对齐是指首地址对齐,而不是说每个变量大小对齐;

    2,结构体内存对齐要求结构体内每一个成员变量都是内存对齐的;

    3,结构体对齐除了第2点之外还要求结构体数组也必须是对齐的,也就是说每个相邻的结构体内部都是对齐的。

    OK,先知道上面这3点之后,开始接触怎么算对齐大小。

    程序员可自己指定某些数据的对齐大小,通过使用下面的预处理指令,指定对齐大小为x。(这里需要注意:只能指定2的n次方作为对齐大小,对于指定对齐大小为6,9,10这样的编译器可能会不予理会)

    #pragma pack(x)
    //...
    #pragma pack()

    系统默认是pack(8)

    那到现在,可能大家有个疑问了,那对于int(这里假设sizeof(int)==4),手动指定对齐大小为8,那align(int)是等于sizeof(int)还是等于8呢 ?

    这里大家可以记住,align(x) = min ( sizeof(x) , packalign) , 即sizeof(x)和指定对齐大小哪个小,对齐大小就为哪个。

    因此,上面的疑问答案是align(int)=sizeof(int)=4 。

    三,怎么算内存对齐大小(示范)?

    复制代码
    #include <cassert>
    
    
    int main(int argc, char* argv[])
    {
        //此处指定对齐大小为1
        //对于a,实际对齐大小为min(sizeof(int),1)=min(4,1)=1
        //对于b,实际对齐大小为min(sizeof(char),1)=min(1,1)=1
        //编译器会确保TEST_A首地址即a的地首址是1字节对齐的,此时a对齐
        //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
        //对于TEST_A数组,第一个TEST_A是对齐的(假设其地址为0),则第二个TEST_A的首地址为(0+5=5),对于第二个TEST_A的两个变量a,b均对齐
        //OK,对齐合理。因此整个结构体的大小为5
    #pragma pack(1)
        struct TEST_A
        {
            int a;
            char b;
        };
    #pragma  pack()
        assert(sizeof(TEST_A) == 5);
    
        //此处指定对齐大小为2
        //对于a,实际对齐大小为min(sizeof(int),2)=min(4,2)=2
        //对于b,实际对齐大小为min(sizeof(char),2)=min(1,2)=1
        //编译器会确保TEST_A首地址即a的地首址是2字节对齐的,此时a对齐
        //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
        //对于TEST_B数组,第一个TEST_B是对齐的(假设其地址为0),则第二个TEST_B的首地址为(0+5=5),对于第二个TEST_B的变量a,显然地址5是不对齐于2字节的
        //因此,需要在TEST_B的变量b后面填充1字节,此时连续相连的TEST_B数组才会对齐
        //OK,对齐合理。因此整个结构体的大小为5+1=6
    #pragma pack(2)
        struct TEST_B
        {
            int a;
            char b;
        };
    #pragma  pack()
        assert(sizeof(TEST_B) == 6);
    
        //此处指定对齐大小为4
        //对于a,实际对齐大小为min(sizeof(int),2)=min(4,4)=4
        //对于b,实际对齐大小为min(sizeof(char),2)=min(1,4)=1
        //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
        //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
        //对于TEST_C数组,第一个TEST_C是对齐的(假设其地址为0),则第二个TEST_C的首地址为(0+5=5),对于第二个TEST_C的变量a,显然地址5是不对齐于4字节的
        //因此,需要在TEST_C的变量b后面填充3字节,此时连续相连的TEST_C数组才会对齐
        //OK,对齐合理。因此整个结构体的大小为5+3=8
    #pragma pack(4)
        struct TEST_C
        {
            int a;
            char b;
        };
    #pragma  pack()
        assert(sizeof(TEST_C) == 8);
    
        //此处指定对齐大小为8
        //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
        //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
        //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
        //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
        //对于TEST_D数组,第一个TEST_D是对齐的(假设其地址为0),则第二个TEST_D的首地址为(0+5=5),对于第二个TEST_D的变量a,显然地址5是不对齐于4字节的
        //因此,需要在TEST_D的变量b后面填充3字节,此时连续相连的TEST_D数组才会对齐
        //OK,对齐合理。因此整个结构体的大小为5+3=8
    #pragma pack(8)
        struct TEST_D
        {
            int a;
            char b;
        };
    #pragma  pack()
        assert(sizeof(TEST_D) == 8);
    
    
        //此处指定对齐大小为8
        //对于a,实际对齐大小为min(sizeof(int),8)=min(4,8)=4
        //对于b,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
        //对于c,这是一个数组,数组的对齐大小与其单元一致,因而align(c)=align(double)=min(sizeof(double),8)=min(8,8)=8
        //对于d,实际对齐大小为min(sizeof(char),8)=min(1,8)=1
        //编译器会确保TEST_A首地址即a的地首址是4字节对齐的,此时a对齐
        //对于b,由于b要求首地址1字节对齐,这显然对于任何地址都合适,所以a,b都是对齐的
        //对于c,由于c要求首地址8字节对齐,因此前面的a+b=5,还要在c后面补上3个字节才能对齐
        //对于d,显而易见,任何地址均对齐,此时结构体大小为4+1+3+10*8+1=89
        //对于TEST_E数组,第一个TEST_E是对齐的(假设其地址为0),则第二个TEST_E的首地址为(0+89=89),对于第二个TEST_E的变量a,显然地址89是不对齐于4字节的
        //因此,需要在TEST_E的变量d后面填充7字节,此时连续相连的TEST_E数组才会对齐 
        //(注意:此处不仅要确保下一个TEST_E的a,b变量对齐,还要确保c也对齐,所以这里不是填充3字节,而是填充7字节)
        //OK,对齐合理。因此整个结构体的大小为(4)+(1+3)+(10*8)+(1+7)=96
    #pragma pack(8)
        struct TEST_E
        {
            int a;
            char b;
            double c[10];
            char d;
        };
    #pragma  pack()
        assert(sizeof(TEST_E) == 96);
    
        return 0;
    }
    复制代码

    展开全文
  • 更进一步,如果4个double是一种经常使用的数据类型的话,我们就可以把它封装为一个Vector4d类,这样,用户就完全看不到内存对齐的具体实现了,像下面这样。 #include #include class Vector4d { using aligned_...

    作者丨王金戈@知乎(已授权)

    来源丨计算机视觉SLAM

    编辑丨realcat

    缘起

    Eigen是一个非常常用的矩阵运算库,至少对于SLAM的研究者来说不可或缺。然而,向来乖巧的Eigen近来却频频闹脾气,把我的程序折腾得死去活来,我却是丈二和尚摸不着头脑。

    简单说说我经历的灵异事件。我的程序原本在NVIDIA TX2上跑的好好的,直到有一天,我打算把它放到服务器上,看看传说中的RTX 2080GPU能不能加速一把。结果悲剧发生了,编译正常,但是一运行就立即double free。我很是吃惊,怎么能一行代码都没执行就崩了呢。但崩了就是崩了,一定是哪里有bug,我用valgrind检查内存问题,发现种种线索都指向g2og2o是一个SLAM后端优化库,里面封装了大量SLAM相关的优化算法,内部使用了Eigen进行矩阵运算。阴差阳错之间,我发现关闭-march=native这个编译选项后就能正常运行,而这个编译选项其实是告诉编译器当前的处理器支持哪些SIMD指令集,Eigen中又恰好使用了SSE、AVX等指令集进行向量化加速。此时,机智的我发现Eigen文档中有一章叫做Alignment issues,里面提到了某些情况下Eigen对象可能没有内存对齐,从而导致程序崩溃。现在,证据到齐,基本可以确定我遇到的真实问题了:编译安装g2o时,默认没有使用-march=native,因此里面的Eigen代码没有使用向量化加速,所以它们并没有内存对齐。而在我的程序中,启用了向量化加速,所有的Eigen对象都是内存对齐的。两个程序链接起来之后,g2o中未对齐的Eigen对象一旦传递到我的代码中,向量化运算的指令就会触发异常。解决方案很简单,要么都用-march=native,要么都不用。

    这件事就这么过去了,但我不能轻易放过它,毕竟花费了那么多时间找bug。后来我又做了一些深入的探究,这篇文章就来谈谈向量化和内存对齐里面的门道。

    什么是向量化运算?

    向量化运算就是用SSE、AVX等SIMD(Single Instruction Multiple Data)指令集,实现一条指令对多个操作数的运算,从而提高代码的吞吐量,实现加速效果。SSE是一个系列,包括从最初的SSE到最新的SSE4.2,支持同时操作16 bytes的数据,即4个float或者2个double。AVX也是一个系列,它是SSE的升级版,支持同时操作32 bytes的数据,即8个float或者4个double。

    但向量化运算是有前提的,那就是内存对齐。SSE的操作数,必须16 bytes对齐,而AVX的操作数,必须32 bytes对齐。也就是说,如果我们有4个float数,必须把它们放在连续的且首地址为16的倍数的内存空间中,才能调用SSE的指令进行运算。

    举个栗子

    为了给没接触过向量化编程的同学一些直观的感受,我写了一个简单的示例程序:

    #include <immintrin.h>
    #include <iostream>
    
    int main() {
    
      double input1[4] = {1, 1, 1, 1};
      double input2[4] = {1, 2, 3, 4};
      double result[4];
    
      std::cout << "address of input1: " << input1 << std::endl;
      std::cout << "address of input2: " << input2 << std::endl;
    
      __m256d a = _mm256_load_pd(input1);
      __m256d b = _mm256_load_pd(input2);
      __m256d c = _mm256_add_pd(a, b);
    
      _mm256_store_pd(result, c);
    
      std::cout << result[0] << " " << result[1] << " " << result[2] << " " << result[3] << std::endl;
    
      return 0;
    }

    这段代码使用AVX中的向量化加法指令,同时计算4对double的和。这4对数保存在input1和input2中。_mm256_load_pd指令用来加载操作数,_mm256_add_pd指令进行向量化运算,最后, _mm256_store_pd指令读取运算结果到result中。可惜的是,程序运行到第一个_mm256_load_pd处就崩溃了。崩溃的原因正是因为输入变量没有内存对齐。我特意打印出了两个输入变量的地址,结果如下:

    address of input1: 0x7ffeef431ef0
    address of input2: 0x7ffeef431f10

    上一节提到了AVX要求32字节对齐,我们可以把这两个输入变量的地址除以32,看是否能够整除。结果发现 0x7ffeef431ef00x7ffeef431f10 都不能整除。当然,其实直接看倒数第二位是否是偶数即可,是偶数就可以被32整除,是奇数则不能被32整除。

    如何让输入变量内存对齐呢?我们知道,对于局部变量来说,它们的内存地址是在编译期确定的,也就是由编译器决定。所以我们只需要告诉编译器,给input1和input2申请空间时请让首地址32字节对齐,这需要通过预编译指令来实现。不同编译器的预编译指令是不一样的,比如gcc的语法为__attribute__((aligned(32))),MSVC的语法为 __declspec(align(32)) 。以gcc语法为例,做少量修改,就可以得到正确的代码

    #include <immintrin.h>
    #include <iostream>
    
    int main() {
    
      __attribute__ ((aligned (32))) double input1[4] = {1, 1, 1, 1};
      __attribute__ ((aligned (32))) double input2[4] = {1, 2, 3, 4};
      __attribute__ ((aligned (32))) double result[4];
    
      std::cout << "address of input1: " << input1 << std::endl;
      std::cout << "address of input2: " << input2 << std::endl;
    
      __m256d a = _mm256_load_pd(input1);
      __m256d b = _mm256_load_pd(input2);
      __m256d c = _mm256_add_pd(a, b);
    
      _mm256_store_pd(result, c);
    
      std::cout << result[0] << " " << result[1] << " " << result[2] << " " << result[3] << std::endl;
    
      return 0;
    }

    输出结果为

    address of input1: 0x7ffc5ca2e640
    address of input2: 0x7ffc5ca2e660
    2 3 4 5

    可以看到,这次的两个地址都是32的倍数,而且最终的运算结果也完全正确。

    虽然上面的代码正确实现了向量化运算,但实现方式未免过于粗糙。每个变量声明前面都加上一长串预编译指令看起来就不舒服。我们尝试重构一下这段代码。

    重构

    首先,最容易想到的是,把内存对齐的double数组声明成一种自定义数据类型,如下所示:

    using aligned_double4 = __attribute__ ((aligned (32))) double[4];
    
      aligned_double4 input1 = {1, 1, 1, 1};
      aligned_double4 input2 = {1, 2, 3, 4};
      aligned_double4 result;

    这样看起来清爽多了。更进一步,如果4个double是一种经常使用的数据类型的话,我们就可以把它封装为一个Vector4d类,这样,用户就完全看不到内存对齐的具体实现了,像下面这样。

    #include <immintrin.h>
    #include <iostream>
    
    class Vector4d {
      using aligned_double4 = __attribute__ ((aligned (32))) double[4];
    public:
      Vector4d() {
      }
    
      Vector4d(double d1, double d2, double d3, double d4) {
        data[0] = d1;
        data[1] = d2;
        data[2] = d3;
        data[3] = d4;
      }
    
      aligned_double4 data;
    };
    
    Vector4d operator+ (const Vector4d& v1, const Vector4d& v2) {
      __m256d data1 = _mm256_load_pd(v1.data);
      __m256d data2 = _mm256_load_pd(v2.data);
      __m256d data3 = _mm256_add_pd(data1, data2);
      Vector4d result;
      _mm256_store_pd(result.data, data3);
      return result;
    }
    
    std::ostream& operator<< (std::ostream& o, const Vector4d& v) {
      o << "(" << v.data[0] << ", " << v.data[1] << ", " << v.data[2] << ", " << v.data[3] << ")";
      return o;
    }
    
    int main() {
      Vector4d input1 = {1, 1, 1, 1};
      Vector4d input2 = {1, 2, 3, 4};
      Vector4d result = input1 + input2;
    
      std::cout << result << std::endl;
    
      return 0;
    }

    这段代码实现了Vector4d类,并把向量化运算放在了operator+中,主函数变得非常简单。

    但不要高兴得太早,这个Vector4d其实有着严重的漏洞,如果我们动态创建对象,程序仍然会崩溃,比如这段代码:

    int main() {
      Vector4d* input1 = new Vector4d{1, 1, 1, 1};
      Vector4d* input2 = new Vector4d{1, 2, 3, 4};
    
      std::cout << "address of input1: " << input1->data << std::endl;
      std::cout << "address of input2: " << input2->data << std::endl;
    
      Vector4d result = *input1 + *input2;
    
      std::cout << result << std::endl;
    
      delete input1;
      delete input2;
      return 0;
    }

    崩溃前的输出为:

    address of input1: 0x1ceae70
    address of input2: 0x1ceaea0

    很诡异吧,似乎刚才我们设置的内存对齐都失效了,这两个输入变量的内存首地址又不是32的倍数了。

    Heap vs Stack

    问题的根源在于不同的对象创建方式。直接声明的对象是存储在栈上的,其内存地址由编译器在编译时确定,因此预编译指令会生效。但用new动态创建的对象则存储在堆中,其地址在运行时确定。C++的运行时库并不会关心预编译指令声明的对齐方式,我们需要更强有力的手段来确保内存对齐。

    C++提供的new关键字是个好东西,它避免了C语言中丑陋的malloc操作,但同时也隐藏了实现细节。如果我们翻看C++官方文档,可以发现new Vector4d实际上做了两件事情,第一步申请sizeof(Vector4d)大小的空间,第二步调用Vector4d的构造函数。要想实现内存对齐,我们必须修改第一步申请空间的方式才行。好在第一步其实调用了operator new这个函数,我们只需要重写这个函数,就可以实现自定义的内存申请,下面是添加了该函数后的Vector4d类。

    class Vector4d {
      using aligned_double4 = __attribute__ ((aligned (32))) double[4];
    public:
      Vector4d() {
      }
    
      Vector4d(double d1, double d2, double d3, double d4) {
        data[0] = d1;
        data[1] = d2;
        data[2] = d3;
        data[3] = d4;
      }
    
      void* operator new (std::size_t count) {
        void* original = ::operator new(count + 32);
        void* aligned = reinterpret_cast<void*>((reinterpret_cast<size_t>(original) & ~size_t(32 - 1)) + 32);
        *(reinterpret_cast<void**>(aligned) - 1) = original;
        return aligned;
      }
    
      void operator delete (void* ptr) {
        ::operator delete(*(reinterpret_cast<void**>(ptr) - 1));
      }
    
      aligned_double4 data;
    };

    operator new的实现还是有些技巧的,我们来详细解释一下。首先,根据C++标准的规定,operator new的参数count是要开辟的空间的大小。为了保证一定可以得到count大小且32字节对齐的内存空间,我们把实际申请的内存空间扩大到count + 32。可以想象,在这count + 32字节空间中, 一定存在首地址为32的倍数的连续count字节的空间。所以,第二行代码,我们通过对申请到的原始地址original做一些位运算,先找到比original小且是32的倍数的地址,然后加上32,就得到了我们想要的对齐后的地址,记作aligned。接下来,第三行代码很关键,它把原始地址的值保存在了aligned地址的前一个位置中,之所以要这样做,是因为我们还需要自定义释放内存的函数operator delete。毕竟aligned地址并非真实申请到的地址,所以在该地址上调用默认的delete 是会出错的。可以看到,我们在代码中也定义了一个operator delete,传入的参数正是前面operator new返回的对齐的地址。这时候,保存在aligned前一个位置的原始地址就非常有用了,我们只需要把它取出来,然后用标准的delete释放该内存即可。

    为了方便大家理解这段代码,有几个细节需要特地强调一下。::operator new中的::代表全局命名空间,因此可以调用到标准的operator new。第三行需要先把aligned强制转换为void**类型,这是因为我们希望在aligned的前一个位置保存一个void*类型的地址,既然保存的元素是地址,那么该位置对应的地址就是地址的地址,也就是void**

    这是一个不大不小的trick,C++的很多内存管理方面的处理经常会有这样的操作。但不知道细心的你是否发现了这里的一个问题:reinterpret_cast<void**>(aligned) - 1这个地址是否一定在我们申请的空间中呢?换句话说, 它是否一定大于original呢?之所以存在这个质疑,是因为这里的-1其实是对指针减一。要知道,在64位计算机中,指针的长度是8字节,所以这里得到的地址其实是reinterpret_cast<size_t>(aligned) - 8。看出这里的区别了吧,对指针减1相当于对地址的值减8。所以仔细想想,如果originalaligned的距离小于8字节的话,这段代码就会对申请的空间以外的内存赋值,可怕吧。

    其实没什么可怕的,为什么我敢这样讲,因为Eigen就是这样实现的。这样做依赖于现代编译器的一个共识:所有的内存分配都默认16字节对齐。这个事实可以解释很多问题,首先,永远不用担心originalaligned的距离会不会小于8了,它会稳定在16,这足够保存一个指针。其次,为什么我们用AVX指令集举例,而不是SSE?因为SSE要求16字节对齐,而现代编译器已经默认16字节对齐了,那这篇文章就没办法展开了。最后,为什么我的代码在NVIDIA TX2上运行正常而在服务器上挂掉了?因为TX2中是ARM处理器,里面的向量化指令集NEON也只要求16字节对齐。

    还有坑?

    如果你以为到这里就圆满结束了,那可是大错特错。还有个天坑没展示给大家,下面的代码中,我的自定义类Point包含了一个Vector4d的成员,这时候,噩梦又出现了。

    class Point {
    public:
      Point(Vector4d position) : position(position) {
      }
    
      Vector4d position;
    };
    
    int main() {
      Vector4d* input1 = new Vector4d{1, 1, 1, 1};
      Vector4d* input2 = new Vector4d{1, 2, 3, 4};
    
      Point* point1 = new Point{*input1};
      Point* point2 = new Point{*input2};
    
      std::cout << "address of point1: " << point1->position.data << std::endl;
      std::cout << "address of point2: " << point2->position.data << std::endl;
    
      Vector4d result = point1->position + point2->position;
    
      std::cout << result << std::endl;
    
      delete input1;
      delete input2;
      delete point1;
      delete point2;
      return 0;
    }

    输出的地址又不再是32的倍数了,程序戛然而止。我们分析一下为什么会这样。在主函数中,new Point动态创建了一个Point对象。前面提到过,这个过程分为两步,第一步申请Point对象所需的空间,即sizeof(Point)大小的空间,第二步调用Point的构造函数。我们寄希望于第一步申请到的空间恰好让内部的position对象对齐,这是不现实的。因为整个过程中并不会调用Vector4doperator new,调用的只有Pointoperator new,而这个函数我们并没有重写。

    可惜的是,此处并没有足够优雅的解决方案,唯一的方案是在Point类中也添加自定义operator new,这就需要用户的协助,类库的作者已经无能为力了。不过类库的作者能做的,是尽量让用户更方便地添加operator new,比如封装为一个宏定义,用户只需要在Point类中添加一句宏即可。最后,完整的代码如下。

    #include <immintrin.h>
    #include <iostream>
    
    #define ALIGNED_OPERATOR_NEW \
      void* operator new (std::size_t count) { \
        void* original = ::operator new(count + 32); \
        void* aligned = reinterpret_cast<void*>((reinterpret_cast<size_t>(original) & ~size_t(32 - 1)) + 32); \
        *(reinterpret_cast<void**>(aligned) - 1) = original; \
        return aligned;\
      } \
      void operator delete (void* ptr) { \
        ::operator delete(*(reinterpret_cast<void**>(ptr) - 1)); \
      }
    
    class Vector4d {
      using aligned_double4 = __attribute__ ((aligned (32))) double[4];
    public:
      Vector4d() {
      }
    
      Vector4d(double d1, double d2, double d3, double d4) {
        data[0] = d1;
        data[1] = d2;
        data[2] = d3;
        data[3] = d4;
      }
    
      ALIGNED_OPERATOR_NEW
    
      aligned_double4 data;
    };
    
    Vector4d operator+ (const Vector4d& v1, const Vector4d& v2) {
      __m256d data1 = _mm256_load_pd(v1.data);
      __m256d data2 = _mm256_load_pd(v2.data);
      __m256d data3 = _mm256_add_pd(data1, data2);
      Vector4d result;
      _mm256_store_pd(result.data, data3);
      return result;
    }
    
    std::ostream& operator<< (std::ostream& o, const Vector4d& v) {
      o << "(" << v.data[0] << ", " << v.data[1] << ", " << v.data[2] << ", " << v.data[3] << ")";
      return o;
    }
    
    class Point {
    public:
      Point(Vector4d position) : position(position) {
      }
    
      ALIGNED_OPERATOR_NEW
    
      Vector4d position;
    };
    
    int main() {
      Vector4d* input1 = new Vector4d{1, 1, 1, 1};
      Vector4d* input2 = new Vector4d{1, 2, 3, 4};
    
      Point* point1 = new Point{*input1};
      Point* point2 = new Point{*input2};
    
      std::cout << "address of point1: " << point1->position.data << std::endl;
      std::cout << "address of point2: " << point2->position.data << std::endl;
    
      Vector4d result = point1->position + point2->position;
    
      std::cout << result << std::endl;
    
      delete input1;
      delete input2;
      delete point1;
      delete point2;
      return 0;
    }

    这段代码中,宏定义ALIGNED_OPERATOR_NEW包含了operator newoperator delete,它们对所有需要内存对齐的类都适用。因此,无论是需要内存对齐的类,还是包含了这些类的类,都需要添加这个宏。

    再谈Eigen

    在Eigen官方文档中有这么一页内容:

    c6d9687a4018d5fdd6745adce7b23e43.png

    有没有觉得似曾相识?Eigen对该问题的解决方案与我们不谋而合(EIGEN_MAKE_ALIGNED_OPERATOR_NEW)。这当然不是巧合,事实上,本文的灵感正是来源于Eigen。但Eigen只告诉了我们应该怎么做,没有详细讲解其原理。本文则从问题的提出,到具体的解决方案,一一剖析,希望可以给大家一些更深的理解。

    总结

    最后做一个简短的总结。对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐。对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而申请堆内存时仍然需要重写operator new

    有一种特殊情况本文并未提到,如果使用std::vector<Vector4d> ,需要传入自定义内存申请器,即std::vector<Vector4d, AlignedAllocator>,其中AlignedAllocator是我们自定义的内存申请器。这是因为,std::vector中使用了动态申请的空间保存数据,因此默认的operator new是无法让其内存对齐的。在无法重写std::vector类的operator new的情况下,标准库提供了自定义内存申请器的机制,让用户可以以自己的方式申请内存。具体做法本文就不再展开了,理解了前面的内容,这个问题应该很容易解决。

    参考资料

    1. Eigen Memory Issues ethz-asl/eigen_catkin wiki, https://github.com/ethz-asl/eigen_catkin/wiki/Eigen-Memory-Issues

    2. Explanation of the assertion on unaligned arrays Eigen Doc, http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html

    3. 在C/C++代码中使用SSE等指令集的指令(4)SSE指令集Intrinsic函数使用 gengshenghong, https://blog.csdn.net/gengshenghong/article/details/7010615

    4. alignas cppreference, https://en.cppreference.com/w/c/language/_Alignas

    5. Data Alignment, Part 1 Noel Llopis, https://www.gamedeveloper.com/programming/data-alignment-part-1

    6. Data Alignment, Part 2: Objects on The Heap and The Stack Noel Llopis, https://www.gamasutra.com/view/feature/3975/data_alignment_part_2_objects_on_.php?print=1

    7. GCC中的aligned和packed属性 Shengbin, https://blog.shengbin.me/posts/gcc-attribute-aligned-and-packed

    8. new expression cppreference, https://en.cppreference.com/w/cpp/language/new

    9. Why are all arrays aligned to 16 bytes on my implementation? stackoverflow, https://stackoverflow.com/questions/59074271/why-are-all-arrays-aligned-to-16-bytes-on-my-implementation

    10. Why is dynamically allocated memory always 16 bytes aligned? stackoverflow, https://stackoverflow.com/questions/59098246/why-is-dynamically-allocated-memory-always-16-bytes-aligned

    本文仅做学术分享,如有侵权,请联系删文。

    3D视觉精品课程推荐:

    1.面向自动驾驶领域的多传感器数据融合技术
    2.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进
    3.国内首个面向工业级实战的点云处理课程
    4.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解
    5.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦
    6.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化
    7.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)

    干货领取:

    1. 在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

    2. 在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。

    3. 在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

    重磅!3DCVer-学术论文写作投稿 交流群已成立

    扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

    同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

    一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

    29d34dcc5a9df98ba342896ba038b808.png

    ▲长按加微信群或投稿

    4226fe0ef42b0af7eafec285556d8757.png

    ▲长按关注公众号

    3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列手眼标定相机标定orb-slam3知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近2000星球成员为创造更好的AI世界共同进步,知识星球入口:

    学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

    46e6ab455018c8dd55ff009321319c13.png

     圈里有高质量教程资料、答疑解惑、助你高效解决问题

    觉得有用,麻烦给个赞和在看~  716df5759910be16a72cece4783393f8.gif

    展开全文
  • 2、内存对齐 (1)什么是内存对齐? 看到的一句说明:数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上。 (2)为什么要内存对齐? 1> 硬件原因:一些硬件平台必须要求内存对齐,否则抛出异常;另外涉及到...


    1、现象

    先看一段代码:

    struct s1
    {
        char s;
        int i;
    };
     
    struct s2
    {
        int i;
        double d;
    };
     
    cout << "-------basic type" << endl;
    cout << "sizeof(char) " << sizeof(char) << endl;
    cout << "sizeof(int) " << sizeof(int) << endl;
    cout << "sizeof(double) " << sizeof(double) << endl;
     
    cout << endl;
    cout << "-------struct" << endl;
    cout << "sizeof(s1) " << sizeof(s1) << endl;
    cout << "sizeof(s2) " << sizeof(s2) << endl;
    

    运行结果:
    在这里插入图片描述
    不同机器环境,可能结果不同,但不影响说明问题。例如,结构体s1中包含一个char和int,那么s1的大小应该是5,但结果是8,s2也是如此,这就涉及到一个概念:内存对齐。


    2、内存对齐

    (1)什么是内存对齐?
    看到的一句说明:数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上。


    (2)为什么要内存对齐?
    1> 硬件原因:一些硬件平台必须要求内存对齐,否则抛出异常;另外涉及到不同平台的移植问题。

    2> 软件原因:对齐后访问效率更高。


    3、C++98/03 内存对齐实现

    此情况下主要是由编译器实现,不同编译器有不同方法。
    (1)MSVC

    __declspec(align(#)) //其#的内容可以是预编译宏,但不能是编译期数值
    #progma pack
    __alignof
    


    (2)gcc

     __attribute__((__aligned__((#))))
     __alignof__
    


    4、C++11内存对齐实现

    (1)C++11新引入操作符alignof,对齐描述符alignas,基本对齐值alignof(std::max_align_t)

    alignas可以接收常量表达式和类型作为参数,可以修饰变量、类的数据成员等,不能修饰位域和用register申明的变量。一般往大对齐。直接看一段代码:

    struct s1
    {
        char s;
        int i;
    };
     
    struct s2
    {
        int i;
        double d;
    };
    
    struct s3
    {
        char s;
        double d;
        int i;
    };
    
    struct s11
    {
        alignas(16) char s;
        int i;
    };
    
    struct s12
    {
        alignas(16) char s;
        int i;
    };
    
    // alignof
    cout << "-------------------alignof---------------------" << endl;
    
    // 基本对齐值
    cout << "alignof(std::max_align_t)" << alignof(std::max_align_t) << endl;
    
    cout << endl;
    cout << "-------basic type" << endl;
    cout << "alignof(char)" << alignof(char) << endl;
    cout << "alignof(int)" << alignof(int) << endl;
    cout << "alignof(double)" << alignof(double) << endl;
    
    cout << endl;
    cout << "-------struct" << endl;
    cout << "alignof(s1)" << alignof(s1) << endl;
    cout << "alignof(s2)" << alignof(s2) << endl;
    cout << "alignof(s3)" << alignof(s3) << endl;
    cout << endl;
    cout << endl;
    
    // alignas
    cout << "-------------------alignas---------------------" << endl;
    cout << "alignof(s1)" << alignof(s1) << endl;
    cout << "alignof(s11)" << alignof(s11) << endl;
    cout << "alignof(s12)" << alignof(s12) << endl;
    
    
    cout << "sizeof(s1)    " << sizeof(s1) << endl;
    cout << "sizeof(s11)" << sizeof(s11) << endl;
    cout << "sizeof(s12)" << sizeof(s12) << endl;
    

    运行结果:
    在这里插入图片描述
    (2)C++11还新增了几个内存对齐的函数,每个函数有特定作用
    std::alignment_of
    std::aligned_storage
    std::max_align_t
    std::align


    5、实例

    #include <iostream>
    //对齐支持
    //C++11标准定义alignof来查看数据的对齐方式
    //Microsoft定义为__alignof来查看数据的对齐方式
    struct EmptyStruct{};
    class EmptyClass{};
    class EmptyClass1;
    struct HowManyBytes
    {
         char a;
         int b;
    };
    
    struct ColorVector1
    {
         double r;
         double g;
         double b;
         double a;
    };
    
    //C++11标准提供修饰符alignas设定struct/class/union的对齐方式
    //Microsoft使用_declspec(align(#))来设定struct/class/union对齐方式
    struct _declspec(align(32)) ColorVector2    //直接将ColorVector设定在32字节的地址边界上,其起始地址必须是32的倍数
     //等价_declspec(align(32)) struct ColorVector
    {
         double r;
         double g;
         double b;
         double a;
    };
    
    int main()
    {
         std::cout << "sizeof(EmptyStruct): " << sizeof(EmptyStruct) << std::endl;     //1
         std::cout << "sizeof(EmptyClass): " << sizeof(EmptyClass) << std::endl;    //1
         std::cout << "__alignof(EmptyClass): " << __alignof(EmptyClass) << std::endl;    //1
    
         //error,类型不完整,编译失败
         //std::cout << "__alignof(EmptyClass1): " << __alignof(EmptyClass1) << std::endl;//1
         std::cout << "sizeof(char): " << sizeof(char) << std::endl;    //1
         std::cout << "sizeof(int): " << sizeof(int) << std::endl;    //4
         std::cout << "sizeof(HowManyBytes): " << sizeof(HowManyBytes) << std::endl;    //8
         std::cout << "offset of char a: " << offsetof(HowManyBytes, a) << std::endl;    //0
         std::cout << "offset of int b: " << offsetof(HowManyBytes, b) << std::endl;    //4
    
         std::cout << "__alignof(HowManyBytes): " << __alignof(HowManyBytes) << std::endl;    //4
         std::cout << "__alignof(ColorVector1): " << __alignof(ColorVector1) << std::endl;    //8
         std::cout << "__alignof(ColorVector2): " << __alignof(ColorVector2) << std::endl;    //32
         std::cout << "sizeof(ColorVector2): " << sizeof(ColorVector2) << std::endl;    //32
    
         std::cout << "in ColorVector1 offset of double r: " << offsetof(ColorVector1, r) << std::endl;		//0
         std::cout << "in ColorVector1 offset of double g: " << offsetof(ColorVector1, g) << std::endl;    //8
         std::cout << "in ColorVector1 offset of double b: " << offsetof(ColorVector1, b) << std::endl;    //16
         std::cout << "in ColorVector1 offset of double a: " << offsetof(ColorVector1, a) << std::endl;    //24
         std::cout << std::endl;
    
         std::cout << "in ColorVector2 offset of double r: " << offsetof(ColorVector2, r) << std::endl;		//0
         std::cout << "in ColorVector2 offset of double g: " << offsetof(ColorVector2, g) << std::endl;    //8
         std::cout << "in ColorVector2 offset of double b: " << offsetof(ColorVector2, b) << std::endl;    //16
         std::cout << "in ColorVector2 offset of double a: " << offsetof(ColorVector2, a) << std::endl;    //24
    
         int a;
         long long b;
         auto &c = b;
         char d[1024] = { 0 };
    
    
         //对内置类型
         std::cout << __alignof(int) << std::endl;    //4
    
         //对变量、引用或者数组
         std::cout << __alignof(a) << std::endl    //4
              << __alignof(b) << std::endl    //8
              << __alignof(c) << std::endl    //8  引用与其引用的数据对齐值相同
              << __alignof(d) << std::endl;    //1 数组的对齐值由其元素决定
    
         return 0;
    }
    
    展开全文
  • 首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体: 代码如下: struct ...
  • Eigen库数据结构内存对齐问题

    千次阅读 2017-04-13 16:09:09
    Eigen库数据结构内存对齐问题

    原文链接:http://blog.csdn.net/rs_huangzs/article/details/50574141


    我这里讲的是在用到开源库Eigen中的数据结构时会出现这样的错误
    error C2719: 't': formal parameter with __declspec(align('16')) won't be aligned
    意思就是t: 具有 __declspec(align('16')) 的形参将不被对齐
    还有一种错误的提示就是:
    Assertion failed: (reinterpret_cast<size_t>(array) & 0xf) == 0 && "this assertion is explained here: http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html" " **** READ THIS WEB PAGE !!! ****", file c:\eigen3.2.7\eigen\src\core\densestorage.h, line 86
    导致上述两个错误的原因是因为Eigen库为了使用SSE加速,所以内存分配上使用了128位的指针。如果大家觉得我说的不够详细,也可以去Eigen网站里去看英文的解释。这里我只讲下面的三种情况,因为在我的代码中这三种情况都涉及到了,所以也专门看了网站上的解决方法

    • Structures having Eigen objects as members
    • STL Containers
    • Passing Eigen objects by value
      上面三种情况的意思就是Eigen库中的数据结构作为自定义的结构体或者类中的成员STL容器含有Eigen的数据结构Eigen数据结构作为函数的参数
      另外的一种情况我没有遇到过,所以也就不详细叙述了,本人也没有仔细的去看,如果我有时间看了,我会把解决方法也贴出来。
    • Compiler making a wrong assumption on stack alignment (for instance GCC on Windows)

    Structures having Eigen objects as members

    官方给的例子是如果你的代码是下面这个样子的,在你自定义的类中,成员中有Eigen的数据结构,

    class Foo
    {
      //...
      Eigen::Vector2d v;
      //...
    };
    //...
    Foo *foo = new Foo();

    这个错误比较难发现,因为它在编译的时候是不会提示有错误的,只会在运行的时候提示出错,错误的提示就是Assertion failed: (reinterpret_cast<size_t>(array) & 0xf) == 0 && "this assertion is explained here: " "http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html" " **** READ THIS WEB PAGE !!! ****", file c:\eigen3.2.7\eigen\src\core\densestorage.h, line 86
    这是因为你在使用这个类的时候用到了new方法,这个方法是开辟一个内存,但是呢在上面的代码中没有自定义构造函数,所以在new的时候会调用默认的构造函数,调用默认的构造函数的错误在于内存的位数不对齐,所以会导致程序运行的时候出错。 解决的方法就是在类中加入宏EIGEN_MAKE_ALIGNED_OPERATOR_NEW, 如下:

     class Foo
    {
      ...
      Eigen::Vector2d v;
      ...
    public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    };


    通过查找这个宏,我们可以找到这样一句代码#define EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF(NeedsToAlign),大致意思就是指针对齐。
    大家可以自己新建一个控制台程序来验证上述的解决方案是否对。很简单的代码,copy一下就可以了。本人已经试验了,可以完美解决上述的错误。

    STL Containers

    如果STL容器中的元素是Eigen库数据结构,例如这里定义一个vector容器,元素是Matrix4d ,如下所示:

    vector<Eigen::Matrix4d>;

    这个错误也是和上述一样的提示,编译不会出错,只有在运行的时候出错。解决的方法很简单,定义改成下面的方式:

    vector<Eigen::Matrix4d,Eigen::aligned_allocator<Eigen::Matrix4d>>;

    其实上述的这段代码才是标准的定义容器方法,只是我们一般情况下定义容器的元素都是C++中的类型,所以可以省略,这是因为在C++11标准中,aligned_allocator管理C++中的各种数据类型的内存方法是一样的,可以不需要着重写出来。但是在Eigen管理内存和C++11中的方法是不一样的,所以需要单独强调元素的内存分配和管理。

    Passing Eigen objects by value

    如果自定义的一个函数中的参数有Eigen数据类型,对于这个参数需要特别的注意,大多数情况下,我们学习的C++相关的书中,函数和参数的定义如下:

    FramedTransformation( int id, Eigen::Matrix4d t );

    这个错误会在你编译的时候提示你错误,错误的信息是error C2719: 't': formal parameter with __declspec(align('16')) won't be aligned
    要解决这个错误也是很简单的,只有我们把参数t的类型稍加变化即可。如下:

    FramedTransformation( int id, const Eigen::Matrix4d& t );
    展开全文
  • 还以为是内存没有对齐的问题,结果加了   #pragma pack(4) // 1, 2, 4 也是枉然。。。 T_T     局部变量vector的自动释放 Crash 位置:   而且同样的代码在OpenCV 3.1.0上是正常...
  • 一.背景:1.使用#pragma定义结构体:RECV_CMD_DATA_t和RECV_CMD_PACK_t,按照1字节进行内存对齐#pragma pack(1) typedef struct recv_cmd_data { int iType; long long llInt; int iLen; ...
  • 栈上的内存对齐

    2014-11-11 13:55:24
    C++ Primer Plus第四章10节第一个程序清单(4.24 choices.cpp),代码如下: #include #include <vector> ...只是结构体的内存对齐会影响到sizeof的结果,而栈上的内存对齐几乎是对程序员透明的。
  • C 结构体内存对齐原则

    千次阅读 2014-07-30 10:45:59
    最近在实习的公司,作为新人和其他的...好了,言归正传,C中对于结构体的使用可以说是非常的频繁和必要的,正确理解结构体的内存对齐原则,对于用好C是很有必要的(小子在实际项目中确实遇到过一个结构体内存对齐引起的
  • C++内存对齐总结

    2018-05-31 20:38:41
    最近准备C++面试,看到了内存对齐,发现一篇讲的很好的博客,转一下:C++内存对齐总结
  • 1. 为何需要内存对齐 2. 内存对齐规则 2.1 内置类型的对齐规则 2.2 结构体or类类型对齐规则 2.2.1 不考虑结构体嵌套情况 2.2.2 结构体嵌套结构体 1. 为何需要内存对齐 提升CPU从内存中读取数据的效率。 CPU...
  • Data alignment(数据、内存对齐)漫谈

    千次阅读 2017-04-18 19:31:06
    比如他听说过内存对齐和其大概的作用,但是却不知道cache对齐以及对齐到底有什么作用,更不了解怎么能更好的对结构进行内存布局以提高性能,在本文,你会得到解答。 以下讨论的概念性的东西应该都是适用于所有系统的...
  • 更进一步,如果4个double是一种经常使用的数据类型的话,我们就可以把它封装为一个Vector4d类,这样,用户就完全看不到内存对齐的具体实现了,像下面这样。 #include #include class Vector4d { using aligned_...
  • 动态内存分配的弊端和解决 在游戏编程中,以new或者malloc在堆上进行动态内存的分配是非常低效的操作。低效的原因两个: (1).堆分配器(heap allocation)是通用的设施,它必须处理任何大小的分配请求,从1字节到...
  • 在《Game Coding Complete, 4rd Edition》(第3版应该也有,国内有中文版了)一书中,作者在第3章专门针对满足对齐要求和不满足对齐要求的同一个结构,其运行时间进行了测试,从而印证了内存对齐的重要性。...
  • C/C++中的内存对齐

    千次阅读 2006-06-04 11:32:00
    [Intel Architecture 32 Manual] 字,双字,和四字在自然边界上不需要在内存对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。) 无论如何,为了提高程序的...
  • 结构体内存对齐详解

    2017-03-10 15:31:28
    之所以要有结构体内存对齐,是因为处理器每次读写内存的时候从k倍数的地址开始,每次读取或写入k个字节的数据。如果能保证结构体内存对齐,那么每个成员数据就能在这k个字节里,而不会横跨在两个符号对齐要求的k字节...
  • 关于内存对齐

    千次阅读 2012-02-21 11:02:15
    编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密。  首先,至少有一点可以...
  • 结构体struct 联合体union 及内存对齐

    千次阅读 2015-09-24 10:33:43
    ...编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密
  • 如题,文件中设置 #pragma pack(1) 1字节对齐时,结构体中的vector的首地址一定要是奇数地址! 如 0x00226a8f;如果为偶数地址,会出现内存错误导致程序崩溃。 推测原因,应该是字节对齐的问题导致的。 原先...
  • 对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐。对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而申请堆内存时仍然需要...
  • [C++]字节对齐与结构体大小  转自:http://pppboy.blog.163.com/blog/static/30203796201082494026399/ 结构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题。...
  • 结构体和联合体的内存对齐问题

    千次阅读 2011-09-19 22:45:14
    当在C中定义了一个结构...编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密。
  • Vulkan要求你结构体中的数据在内存中以一种特殊方式对齐,例如: 标量必须是N对齐的(如对32位浮点数来说就是4个字节); vec2必须是2N对齐的(8个字节); vec3和vec4必须4N对齐(16字节); 内嵌...
  • 综的说来就是结构体成员的对齐是用成员本身的大小和#pragma pack(push,n)中的n中较小的数对齐,例如如果成员大小为2,而你指定的对齐方式是4,则该成员按2对齐;结构本身的对其是用结构中最大成员的大小和#pragma ...
  • C++中内存对齐与对象指针指向空指针的对象大小 首前看一段程序,判断一下其输出结果是什么: #include <iostream> #include <vector> #include <algorithm> #include <bitset> using ...
  • 内存对齐为什么要有内存对齐1、平台原因:不是所有的硬件平台都可以随意的访问某个内存地址上的数据的,有些硬件平台只能访问某些特定地址上的某些特定类型的数据,否则会抛出异常。2、性能原因:为了访问没有对齐的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,858
精华内容 4,743
关键字:

vector内存对齐