Class中的容器问题

来源:互联网 发布:淘宝直播怎么申请的 编辑:程序博客网 时间:2024/05/16 09:27

前些天工作中同事突然为我一句:class 中vector到底占用多大的空间?它占用的空间会不会随着push_back添加元素而产生越界呢??当时听到这个问题是一头雾水,甚至有种被对方的分析说服。作为一个好奇心极强的程序员,我还是打开了VS探究了这其中的奥秘,现在就以大家最为熟悉的vector为例,讲讲vector到底是如何管理内存和如何在class 中被存储的。

为了解决以上的疑惑,我们先看vector的数据定义和内存分配策略,最后以一个实例来验证我们的猜想。

  • vector 的数据结构定义:

template <class T, class Alloc = alloc>  class vector {...protected:  iterator start;                    //表示目前已使用空间的头  iterator finish;   //表示目前已使用空间的尾iterator end_of_storage;         //表示目前可用空间的尾(已分配)...public:  iterator begin() { return start; }  const_iterator begin() const { return start; }  iterator end() { return finish; }  const_iterator end() const { return finish; }  reverse_iterator rbegin() { return reverse_iterator(end()); }  const_reverse_iterator rbegin() const {     return const_reverse_iterator(end());   }  reverse_iterator rend() { return reverse_iterator(begin()); }  const_reverse_iterator rend() const {     return const_reverse_iterator(begin()); }//求取已使用容量  size_type size() const { return size_type(end() - begin()); }  size_type max_size() const { return size_type(-1) / sizeof(T); }  //求取目前可使用空间容量  size_type capacity() const { return size_type(end_of_storage - begin()); }  bool empty() const { return begin() == end(); }...}

由以上的数据结构定义可以看出vector的数据存储区主要是start,finish和end_of_storage这三个iterator构成,而一个iterator的大小刚好是一个指针的大小,所以vector 的对象大小就有可能是3个指针了,为例验证自己的想法,写了以下的一个简答的测试程序:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <vector>#include <list>using namespace std;class Myclass{public:vector<int>m_vector;};int _tmain(int argc, _TCHAR* argv[]){Myclass instance;int num0 = sizeof(instance); //会是多少呢instance.m_vector.push_back(1);instance.m_vector.push_back(2);int num1 = sizeof(instance);//加入元素后是不是class的对象会改变size 呢?return 0;}

  • 从上面的结果中可以看到vector 的大小变化,如下图所示:


由此可以知道的是vector的push_back 操作并不改变其大小,但是这也让我有了两个疑问:

1.vector 添加元素操作并没有改变类大小的话,那么添加的元素说明也就不在class的数据区了,那么它放在哪里了呢? 

2.通过数据结构知道vector 中只有三个指针,为什么它的size不是12?


首先回答为什么size是16的问题,为此我们可以打开vector查看其中存储的数据:


从std::Vector_val中可以看出vector成员中的确包含上文提到的三个指针,并且从地址上看,它们是连续且属于class的数据区(可以从class对象的首地址看出),只是其中多了一个std::Container_base12成员变量,通过查询其地址发现其与_Myfirst地址是相差8位,所以知道std::Container_base12成员变量也在class中,并且是一个容器指针(所有的STL容器都有),因此也就好理解为什么是16了。


那么vector的元素数据是放在那里的呢,它又是如何实现数据的动态添加和管理的?为此我们可以看看vector的push_back时,计算机到底做了些什么:

 void push_back(const T& x) {    if (finish != end_of_storage) {  // 还有备用空间      construct(finish, x);   // 直接在备用空间中构建元素      ++finish;                          // 调整范围    }    else                                  // 已无备用空间      insert_aux(end(), x);  }template <class T, class Alloc>void vector<T, Alloc>::insert_aux(iterator position, const T& x) {  if (finish != end_of_storage) {  // 还有备用空间    // 在备用空间起始处构造一个元素,并以vector最后一个元素值为其初值      construct(finish, *(finish - 1));    ++finish;    // 以下做啥用?    T x_copy = x;    copy_backward(position, finish - 2, finish - 1);    *position = x_copy;  }  else {// 已无用空间    const size_type old_size = size();//此时的size()等同于capacity()    const size_type len = old_size != 0 ? 2 * old_size : 1;    // 以上配置原则:如果原大小为0,那么则配置1(个元素大小)    // 如果原大小不为0,那么配置原大小的2倍    // 前半段用来放置原数据,后半段用来放置新数据    iterator new_start = data_allocator::allocate(len); // 实际配置    iterator new_finish = new_start;    __STL_TRY {      // 复制      new_finish = uninitialized_copy(start, position, new_start);      construct(new_finish, x);      ++new_finish;      // 将安插点的原内容页拷贝过来(该函数也可能被insert(p,x)调用)      new_finish = uninitialized_copy(position, finish, new_finish);    }    catch(...) {      // "commit or rollback"(如果不成功那么一个都不留)      destroy(new_start, new_finish);       data_allocator::deallocate(new_start, len);      throw;    }    // 解构并释放原vector    destroy(begin(), end());    deallocate();    // 调整迭代器,指向新vector    start = new_start;    finish = new_finish;    end_of_storage = new_start + len;  }}
有以上代码很容容易看到,vector在添加元素时,依据添加元素的大小动态的在堆上分配内存,并且在其生命周期结束时又释放掉 分配的空间。因此通过 allocate 分配出来的地址空间并不和class中数据区空间(上文为栈)在同一位置,而是在堆中分配的地址。因此也就实现了数据在class中大小不变,只需要保存动态内存首地址存储就可,动态变化空间在堆中被动态分配与管理。


例子:
#include "stdafx.h"#include <vector>#include <list>#include <string>#include <map>using namespace std;class Myclass{public:vector<int>m_vector;int m_age;string m_name;map<int, int>m_map;};int _tmain(int argc, _TCHAR* argv[]){Myclass instance;int num0 = sizeof(instance);instance.m_vector.push_back(1);instance.m_vector.push_back(2);int num1 = sizeof(instance);//instance.m_list.push_back(10);return 0;}
通过断点调试,查看Myclass对象instance 中每一个元素的首地址知道:它们在数据区中是连续存储的;同时,通过查看vector中俩个int 类型的地址知,它们显然和class中其他成员变量的地址是不同的,依此知道容器中的元素是另外在堆中分配的,并且连续存储(下图中6fe888和6fe88c可以看出).这样也就不难解释为什么vector中添加元素不会引起class数据区的越界等问题了.


总结:

1.class的大小是其数据区元素的大小。

2.class中的数据成员是连续存储的。

3.容器变量在class中只是保留了三个指针和一个container,其元素的值是在堆中例外分配与管理的。

4.vector的大小是4个指针大小.。



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩子写作业爱磨蹭怎么办 孩子在学校不写作业怎么办 鳗鱼刺卡在喉咙怎么办 被小鱼刺卡住了怎么办 喉咙上卡了鱼刺怎么办 跟团出去受伤了怎么办 平安易宝冻结了怎么办 车的保险到期没有交怎么办 人保外地险出险怎么办 婚姻经营不下去了怎么办 他不爱我,我该怎么办 没有我你怎么办钢琴版 没有你怎么办严爵歌词 没有羊毛戳针该怎么办 没有我你该怎么办歌词 雌激素低子宫内膜薄怎么办 没有我你怎么办的句子 没有我你怎么办百度云 何润东没有我你怎么办 想你了我该怎么办 没有你日子我该怎么办 没有我你怎么办mp3下载 如果没有你,我该怎么办 我该怎么办韩剧插曲 没有我你怎么办微盘 歌曲没有你我该怎么办 uc看小说收费了怎么办 发财树黄叶掉叶怎么办 5个月猫咪嘴巴臭怎么办 灿盛入伍柳岩怎么办 手机电用得快怎么办 身份证过期7个月怎么办 耳洞总是有臭味怎么办 口琴24孔吹不准怎么办 狗狗反胃吐白沫怎么办 2岁宝宝牙齿被腐蚀怎么办 2岁宝宝乳牙腐蚀怎么办 1岁宝宝门牙腐蚀怎么办 3岁宝宝有蛀牙怎么办 3岁宝宝乳牙腐蚀怎么办 三岁宝宝烂牙怎么办