精华内容
下载资源
问答
  • 首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体:  struct vector{int x...

    这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密。

    首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体:

      struct vector{int x,y,z;} s;
      int *p,*q,*r;
      struct vector *ps;
      p = &s.x;
      q = &s.y;
      r = &s.z;
      ps = &s;
     
      assert(p < q);  assert(p < r);  assert(q < r);  assert((int*)ps == p);  // 上述断言一定不会失败

     

    解释:(不相邻的意思是有padding)
    这时,有朋友可能会问:"标准是否规定相邻字段在内存中也相邻?"。 唔,对不起,ANSI C没有做出保证,你的程序在任何时候都不应该依赖这个假设。那这是否意味着我们永远无法勾勒出一幅更清晰更精确的结构体内存布局图?哦,当然不是。不过先让我们从这个问题中暂时抽身,关注一下另一个重要问题――――内存对齐。

    许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。

    这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错,但是Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家,如果想提升性能,那么所有的程序数据都应该尽可能地对齐。

    Win32平台下的微软C编译器(cl.exe for 80x86)在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。

    现在回到我们关心的struct上来。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。嗯?填充区?对,这就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身有什么对齐要求吗?有的,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格(但此非强制要求,VC7.1就仅仅是让它们一样严格)。我们来看一个例子(以下所有试验的环境是Intel Celeron 2.4G + WIN2000 PRO + vc7.1,内存对齐编译选项是"默认",即不指定/Zp与/pack选项):

      typedef struct ms1
      {
         char a;
         int b;
      } MS1;
    

    假设MS1按如下方式内存布局(本文所有示意图中的内存地址从左至右递增):
           _____________________________

           |   a   |        b          |

           +---------------------------+
    Bytes:    1             4
    因为MS1中有最强对齐要求的是b字段(int),所以根据编译器的对齐规则以及ANSI C标准,MS1对象的首地址一定是4(int类型的对齐模数)的倍数。那么上述内存布局中的b字段能满足int类型的对齐要求吗?嗯,当然不能。如果你是编译器,你会如何巧妙安排来满足CPU的癖好呢?呵呵,经过1毫秒的艰苦思考,你一定得出了如下的方案:
           _______________________________________
           |       |///|                 |
           |   a   |//padding//|       b         |
           |       |///|                 |
           +-------------------------------------+
    Bytes:    1         3             4
    这个方案在a与b之间多分配了3个填充(padding)字节,这样当整个struct对象首地址满足4字节的对齐要求时,b字段也一定能满足int型的4字节对齐规定。那么sizeof(MS1)显然就应该是8,而b字段相对于结构体首地址的偏移就是4。非常好理解,对吗?现在我们把MS1中的字段交换一下顺序:

      typedef struct ms2
      {
         int a;
         char b;
      } MS2;
    


    或许你认为MS2比MS1的情况要简单,它的布局应该就是

           _______________________

           |     a       |   b   |

           +---------------------+
    Bytes:      4           1
    因为MS2对象同样要满足4字节对齐规定,而此时a的地址与结构体的首地址相等,所以它一定也是4字节对齐。嗯,分析得有道理,可是却不全面。让我们来考虑一下定义一个MS2类型的数组会出现什么问题。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。按照上面的方案,一个MS2数组array的布局就是:

    |<-    array[1]     ->|<-    array[2]     ->|<- array[3] .....
    __________________________________________________________

    |     a       |   b   |      a       |   b  |.............

    +----------------------------------------------------------
    Bytes:  4         1          4           1

     

    当数组首地址是4字节对齐时,array[1].a也是4字节对齐,可是array[2].a呢?array[3].a ....呢?可见这种方案在定义结构体数组时无法让数组中所有元素的字段都满足对齐规定,必须修改成如下形式:
           ___________________________________
           |             |       |///|
           |     a       |   b   |//padding//|
           |             |       |///|
           +---------------------------------+
    Bytes:      4           1         3

    现在无论是定义一个单独的MS2变量还是MS2数组,均能保证所有元素的所有字段都满足对齐规定。那么sizeof(MS2)仍然是8,而a的偏移为0,b的偏移是4。

    好的,现在你已经掌握了结构体内存布局的基本准则,尝试分析一个稍微复杂点的类型吧。

      typedef struct ms3
      {
         char a;
         short b;
         double c;
      } MS3;
    


    我想你一定能得出如下正确的布局图: 

            padding  

          _____v_________________________________ 
          |   |/|     |/|               | 
          | a |/|  b  |/padding/|       c       | 
          |   |/|     |/|               | 
          +-------------------------------------+ 
    Bytes:  1  1   2       4            8 

    sizeof(short)等于2,b字段应从偶数地址开始,所以a的后面填充一个字节,而sizeof(double)等于8,c字段要从8倍数地址开始,前面的a、b字段加上填充字节已经有4 bytes,所以b后面再填充4个字节就可以保证c字段的对齐要求了。sizeof(MS3)等于16,b的偏移是2,c的偏移是8。接着看看结构体中字段还是结构类型的情况: 

      typedef struct ms4
      {
         char a;
         MS3 b;
      } MS4;
    


    MS3中内存要求最严格的字段是c,那么MS3类型数据的对齐模数就与double的一致(为8),a字段后面应填充7个字节,因此MS4的布局应该是: 
           _______________________________________ 
           |       |///|                 | 
           |   a   |//padding//|       b         | 
           |       |///|                 | 
           +-------------------------------------+ 
     Bytes:    1         7             16

    显然,sizeof(MS4)等于24,b的偏移等于8。

    在实际开发中,我们可以通过指定/Zp编译选项来更改编译器的对齐规则。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告诉编译器最大对齐模数是n。在这种情况下,所有小于等于n字节的基本数据类型的对齐规则与默认的一样,但是大于n个字节的数据类型的对齐模数被限制为n。事实上,VC7.1的默认对齐选项就相当于/Zp8。仔细看看MSDN对这个选项的描述,会发现它郑重告诫了程序员不要在MIPS和Alpha平台上用/Zp1和/Zp2选项,也不要在16位平台上指定/Zp4和/Zp8(想想为什么?)。改变编译器的对齐选项,对照程序运行结果重新分析上面4种结构体的内存布局将是一个很好的复习。

    到了这里,我们可以回答本文提出的最后一个问题了。结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项,而你的程序可能需要运行在多种平台上,你的源代码可能要被不同的人用不同的编译器编译(试想你为别人提供一个开放源码的库),那么除非绝对必需,否则你的程序永远也不要依赖这些诡异的内存布局。顺便说一下,如果一个程序中的两个模块是用不同的对齐选项分别编译的,那么它很可能会产生一些非常微妙的错误。如果你的程序确实有很难理解的行为,不防仔细检查一下各个模块的编译选项。

     

    思考题:请分析下面几种结构体在你的平台上的内存布局,并试着寻找一种合理安排字段声明顺序的方法以尽量节省内存空间。

        A. struct P1 { int a; char b; int c; char d; };
        B. struct P2 { int a; char b; char c; int d; };
        C. struct P3 { short a[3]; char b[3]; };
        D. struct P4 { short a[3]; char *b[3]; };
        E. struct P5 { struct P2 *a; char b; struct P1 a[2];  };

    http://www.cnitblog.com/tinnal/archive/2008/09/05/48748.html
    展开全文
  • 还以为是内存没有对齐的问题,结果加了   #pragma pack(4) // 1, 2, 4 也是枉然。。。 T_T     局部变量vector的自动释放 Crash 位置:   而且同样的代码在OpenCV 3.1.0上是正常...

     

    当我想通过 swap 释放成员变量的 vector的内存时,总是Crash,断点进去Crash的位置也很诡异:

    Crash 位置 :

    还以为是内存没有对齐的问题,结果加了

     

    #pragma pack(4)	// 1, 2, 4

    也是枉然。。。 T_T
     

     

    局部变量vector的自动释放 Crash 位置:

     

    而且同样的代码在OpenCV 3.1.0上是正常的!

     

    诡异的我都快怀疑人森的时候,终于上帝让我找到了这篇文章

    看到最后一句,才回想起来自己的OpenCV2.4.12 是由 VS2012 编译的(当初编OpenCV+CUDA,发现最高仅支持VS2013,而我机子上除了VS2015就是VS2012,所以将VS2012设为默认的CMAKE 编译器),而自己默认的开发 IDE却是 VS2015,才导致了这么诡异的问题。。。(类似的 cout  打印  Mat 的时候也会异常崩溃,也是这个原因)

     

    仅做记录,以防再犯

    附:意外收获

     

    展开全文
  • C++内存对齐

    2015-09-10 22:09:46
    STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector 、deque);另一类是以不连续的节点形式存储的容器(如:list、set、map)。在使用erase方法来删除元素时,需要注意一些问题。  在使用 ...
      STL中的容器按存储方式分为两类,一类是按以数组形式存储的容器(如:vector 、deque);另一类是以不连续的节点形式存储的容器(如:list、set、map)。在使用erase方法来删除元素时,需要注意一些问题。
          在使用 list、set 或 map遍历删除某些元素时可以这样使用:

    正确使用方法1      std::list< int> List;
          std::list< int>::iterator itList;
          for( itList = List.begin(); itList != List.end(); )
          {
                if( WillDelete( *itList) )
                {
                   itList = List.erase( itList);
                }
                else
                   itList++;
          }

           或

    正确使用方法2      std::list< int> List;
          std::list< int>::iterator itList;
          for( itList = List.begin(); itList != List.end(); )
          {
                if( WillDelete( *itList) )
                {
                   List.erase( itList++);
                }
                else
                   itList++;
          }

          
          下面是两个错误的使用方法:

    错误使用方法1      std::list< int> List;
          std::list< int>::iterator itList;
          for( itList = List.begin(); itList != List.end(); itList++)
          {
                if( WillDelete( *itList) )
                {
                   List.erase( itList);
                }
          }

             或
    错误使用方法2      std::list< int> List;
          std::list< int>::iterator itList;
          for( itList = List.begin(); itList != List.end(); )
          {
                if( WillDelete( *itList) )
                {
                   itList = List.erase( ++itList);
                }
                else
                   itList++;
          }

          正确使用方法1:通过erase方法的返回值来获取下一个元素的位置
          正确使用方法2:在调用erase方法之前先使用 “++”来获取下一个元素的位置
          错误使用方法1:在调用erase方法之后使用“++”来获取下一个元素的位置,由于在调用erase方法以后,该元素的位置已经被删除,如果在根据这个旧的位置来获取下一个位置,则会出现异常。
          错误使用方法2:同上。

          这里“++”运算符与我们平常的理解刚好相反,erase( itList++) 是先获取下一个元素的位置在删除; erase( ++itList) 是删除以后再获取下一个元素的位置。

         在使用 vector、deque遍历删除元素时,也可以通过erase的返回值来获取下一个元素的位置:
    正确使用方法      std::vector< int> Vec;
          std::vector< int>::iterator itVec;
          for( itVec = Vec.begin(); itVec != Vec.end(); )
          {
                if( WillDelete( *itVec) )
                {
                     itVec = Vec.erase( itVec);
                }
                else
                   itList++;
          }
          
          注意:vector、deque 不能像上面的“正确使用方法2”的办法来遍历删除。
    展开全文
  • 首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体: 代码如下: struct ...
  • C++中内存对齐与对象指针指向空指针的对象大小 首前看一段程序,判断一下其输出结果是什么: #include <iostream> #include <vector> #include <algorithm> #include <bitset> using ...

    C++中内存对齐与对象指针指向空指针的对象大小

    首前看一段程序,判断一下其输出结果是什么:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <bitset>
    
    using namespace std;
    
    class A{
        int a;
        int b;
        double c;
        char name;
        char name2;
    public:
        virtual void myFun(){
            cout<<"test"<<endl;
        }
        void myFun2(){
            cout<<"fun2"<<endl;
        }
    };
    
    int main(){
        A *a = NULL;
        cout<< sizeof(a)<<" "<< sizeof(*a)<<endl;
        a->myFun();
        a->myFun2();
        return 0;
    }
    

    在Windows10 64位系统下,使用MInGW(gcc)作为编译器的情况下,
    输出:
    在这里插入图片描述
    当执行a的myFun()函数时程序出错退出.

    将普通函数的调用注释掉:

    int main(){
        A *a = NULL;
        cout<< sizeof(a)<<" "<< sizeof(*a)<<endl;
    //    a->myFun();
        a->myFun2();
        return 0;
    }
    

    输出:
    在这里插入图片描述
    可以看到程序是正常结束的。
    分析:
    因为对象a的虚函数表示空的,所以调用虚函数时程序退出;而在调用普通函数时,实际调用形式是:A::myFun2(this),this指向调用对象,这里的this对象是一个空对象,在进行与成员变量无关的操作的时候是可以正常进行的。
    sizeof(a)得到的是一个指针的大小,为8字节;sizeof(*a)得到了一个对象的大小,但是这里要遵循字节对齐的规则,虚函数表指针:8,成员变量:4+4+8+1+1=18,总大小为8+18=26,但是大小应为最大成员变量的整数倍,所以为:32


    下面再来看一下一个空对象的大小:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <bitset>
    
    using namespace std;
    
    class A{
    //    int a;
    //    int b;
    //    double c;
    //    char name;
    //    char name2;
    public:
    //    virtual void myFun(){
    //        cout<<"test"<<endl;
    //    }
        void myFun2(){
            cout<<"fun2"<<endl;
        }
    };
    
    int main(){
        A *a = NULL;
        cout<< sizeof(a)<<" "<< sizeof(*a)<<endl;
    //    a->myFun();
        a->myFun2();
        return 0;
    }
    

    输出结果为:
    在这里插入图片描述
    “To ensure that the addresses of two different objects will be different.”所以即使一个类不含任何成员,也要有一个字节来标示其身份。

    参考:
    C++空指针能调用类成员函数吗?
    C++ 中空类对象为什么占一个字节?

    展开全文
  • 如题,文件中设置 #pragma pack(1) 1字节对齐时,结构体中的vector的首地址一定要是奇数地址! 如 0x00226a8f;如果为偶数地址,会出现内存错误导致程序崩溃。 推测原因,应该是字节对齐的问题导致的。 原先...
  • 查阅资料后,说是初始化vector容器时,要加上内存对齐参数。 上述的这段代码才是标准的定义容器方法,只是我们一般情况下定义容器的元素都是C++中的类型,所以可以省略,这是因为在C++11标准中,aligned_allo
  • 因为SSE/SSE2指令集要求数据必须对齐到16字节的边界, 所以vector的分配器必须替换成一个可以对齐内存分配器(x86架构). 本文适用于想在代码中引入新鲜空气的x86 Windows开发者, 你将会看到如何把强大的XNAMat
  • Eigen使用vector容器出错

    千次阅读 2016-01-25 11:36:02
    固定大小(fixed-size)的类是指在编译过程中就已经分配好内存空间的类,为了提高运算速度,对于SSE或者AltiVec指令集,向量化必须要求向量是以16字节即128bit对齐的方式分配内存空间,所以针对这个问题,容器需要...
  • vector删除用erase+remove

    2021-01-28 00:07:46
    (注意以下逐个元素对齐,模拟元素在内存中的位置,这样就容易看出变化规律) 原vector 10 20 10 15 12 7 9 遇到第一个10,数组变成 20 10 15 12 7 9 遇到第二个10 20 15 12 7 9 7 9 因此,remove()需要和erase()...
  • c结构体字节对齐

    2012-12-12 11:35:29
    首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体:    struct vector{...
  • opencl knernel中对全局内存(__global)向量类型数据的读写有两种方式, 一种是直接用=操作符赋值,一种则是通过vstoren/vloadn函数来实现向量数据读写。 =操作符赋值方式使用简单,但在msvc下以CL_MEM_USE_HOST_PTR...
  • 关于地址对齐,大小端 可以弄一个enum,然后将内存数据加上code和len,像发协议包似的 code len value 这样的格式   然后存到字符串里面,比如vector里面 这样就可以避免了...
  • Arena:Leveldb的简单的内存池,它所作的工作十分简单,申请内存时,将申请到的内存块放入std::vector blocks_中,在Arena的生命周期结束后,统一释放掉所有申请到的内存,内部结构如图所示。 Arena主要提供了两...
  • C++方向的面试题总结

    2019-08-17 17:34:35
    为什么要进行内存对齐?4.this指针存在哪里?this指针可以为空吗?5.malloc/calloc/realloc的区别6.malloc/free和new/delete的区别7.vector和list的区别?8.为什么要将deque作为stack和queue的底层结构?...
  • #include "stdafx.h" #include #include using namespace std; typedef struct Node{ int length; //单词的长度 int count;... //内存对齐,30与32占的空间相同 }; void insert( vector &vNode, N
  • C++基础

    2021-04-13 14:35:47
    2、struct内存对齐 3、内存泄露的情况 4、指针和引用的区别 5、野指针产生与避免 6、27、四种智能指针及底层实现:auto_ptr、unique_ptr、shared_ptr、weak_ptr 28、shared_ptr中的循环引用怎么解决?(weak_ptr) 8...
  • new开辟空间多大事怎么决定的(内存对齐) 构造函数能是虚函数吗?析构函数能吗? 构造函数,析构函数执行顺序,为什么是这样? 虚函数表示放在那里的? 介绍一下vector vector是线程安全的吗? 什么是线程安全? 怎么判断线.....
  • 阿里一面

    2017-03-21 22:46:32
    1、深拷贝和浅拷贝的区别 ...5、内存对齐的意义?为什么可以提高效率 6、简述一下map的原理 7、vector存在哪里,它的增长机制是什么?频繁的改变其大小对内存有什么坏的影响 8、tcp四次挥手的过程有一个wait实在哪里
  • c++常见面试题

    2021-04-07 19:42:07
    文章目录语言C++ 中智能指针和指针的区别是什么?C++ 中多态是怎么实现的简述 C++ 右值引用与转移语义STL 中 vector 与 list 具体是怎么实现的?...简述 C++ 中内存对齐的使用场景变量的声明和定义
  • C++ 之 allocator

    2018-10-27 23:40:26
    allocator – 是一个模板 1.头文件 memory 2.作用:帮助我们将内存和对象构造分开, 它分配的...根据给定的对象类型恰当的分配内存对齐位置 分配内存且未构造: allocator&amp;lt;string&amp;gt; a...
  • 解释内存对齐及其原理(wyyx) 派生类虚表的布局是怎样的(wyyx) 模板类了解吗?实现一个unique_list容器(插入操作)(代码,借助vector实现的)(wyyx) 动态库,静态库区别(扩充知识点) 简单来说,...
  • C++方向复习总结

    千次阅读 2020-04-13 15:19:36
    内存对齐???2. static关键字的作用3. 请你来介绍一下STL的allocaotr4.请你来说一说STL迭代器删除元素5. 请你说一说vector和list的区别,应用,越详细越好6. 请你来回答一下include头文件的顺序以及双引号””和尖...
  • 面试问题整理 2.20

    2021-02-19 20:50:31
    1.extern作用?...3.说一下struct里面的内存对齐,举例说说 4.说一下c++里多线程间通信,如果你有一个类的成员,在函数内想传给另一个线程,如何办? 5.说- -下friend关键字的作用 6.说一下智能指针, .
  • 9月11日面试总结

    2020-09-11 15:35:10
    1-多态的实现:动态多态和静态多态 2-vector特点 3-多线程之间同步 4-static修饰函数 5-struct的对齐方式,和Union的区别 6-虚函数的实现 7-内存分配:
  • Leveldb源码分析6 Arena

    2011-12-26 20:12:02
    Arena所作的工作十分简单,是一个只负责申请空间的内存池,将申请到的内存放入std::vector blocks_中 ,在Arena的生命周期结束后,统一释放掉所有申请到的内存,内部结构如下图所示。 有两个申请函数:其中一个...
  • 002

    2020-02-21 23:11:55
    -利用vector set操作按序存储,后将两个vector相加,建立链表返回 -链表逆序存储,最低位自然对齐,无需转换数组直接相加即可 难点: -逐位相加实现进位 调试问题: -单纯拼接剩余数字到返回值,缺乏分析问题的整体...
  • dlmalloc 2.8.6 源码详解—[2]分箱(bins)

    千次阅读 2016-12-04 21:28:58
    转载自:vector032.2 分箱(bins)内存分配器设计中需要解决的两个重要问题就是空间和时间的矛盾.所谓空间矛盾是指要减少两方面的内存浪费,一是来自分配器本身overhead信息的占用,另外则来自分配的chunk由于对齐或碎片...
  • 腾讯面试八股文12-29

    2020-12-29 00:29:30
    stl,vector内存管理 golang协程如何通讯 golang defer顺序(队列还是栈) tcp四次挥手状态转换 udp与tcp的区别 如何解决tcp黏包 select和epoll实现 select和epoll使用场景 epoll水平触发和边缘触发,两...

空空如也

空空如也

1 2 3
收藏数 56
精华内容 22
关键字:

vector内存对齐