手把手教你调试STL容器(上)

资料出处:http://www.wuzesheng.com/?p=1686

众所周知,调试(Debugging)是每个程序员所要必备的基本的技术素养,尤其是对C/C++的程序员来说。对于在linux下用C/C++开发的朋友,相信对GDB不会陌生,当程序有bug或者是出现core dump的时候,GDB是我们最好的朋友。STL是C++相较于C而言,增加的非常强有力的工具,它从某种程度上把C/C++程序员从繁琐的基本数据结构中解放了出来。不过,STL虽然用起来十分方便,但是,用GDB调试过C/C++程序的朋友都有这样痛苦的经历,在GDB状态下,要知道某个STL对象(比如容器)中的数据内容,并不是那么直接、简单。本文的主要内容就是介绍STL中大家比较常用到的容器的基本组成,帮助大家能够在调试的时候更好的驾驭它们。

  • 1. string

    string是STL中最为常用的类型,它是模板类basic_string用char类型特化后的结果,下面我们来看一下string类型的基本组成:

    01 typedef basic_string<char>    string;
    02  
    03 struct _Rep_base
    04 {
    05     size_type     _M_length;
    06     size_type     _M_capacity;
    07     _Atomic_word  _M_refcount;
    08 };
    09  
    10 struct _Alloc_hider
    11 {
    12     _CharT* _M_p;
    13 };
    14  
    15 mutable _Alloc_hider  _M_dataplus;
    16 _M_data() const return  _M_dataplus._M_p; }
    17 _Rep* _M_rep() const return &((reinterpret_cast<_rep *> (_M_data()))[-1]); }

    从上面来看,string只有一个成员_M_dataplus。但是这里需要注意的是,string类在实现的时候用了比较巧的方法,在_M_dataplus._M_p中保存了用户的数据,在_M_dataplus._M_p的第一个元素前面的位置,保存了string类本身所需要的一些信息rep。这样做的好处,一方面不增加string类的额外开销,另一方面可以保证用户在调试器(GDB)中用_M_dataplus._M_p查看数据内容的时候,不受干扰。用户可以通过reinterpret_cast<_rep *> (_M_data()))[-1])来查看rep相关的信息,也可以调用_M_rep()函数来查看。

  • 2. vector

    vector同样也是stl中最为常用类型,下面我们来看一下vector类型的基本组成:

    1 struct _Vector_impl
    2 {
    3     _Tp*           _M_start;
    4     _Tp*           _M_finish;
    5     _Tp*           _M_end_of_storage;
    6 };
    7  
    8 _Vector_impl _M_impl;

    vector本身很简单,就是一个动态数组。它只有一个数组成员_M_impl,用户可以通过_M_impl来查看vector内容的数据成员,具体包括动态数组的超始地址_M_impl._M_start,结束地址_M_impl._M_end_of_storage,数组内容的结束地址_M_impl._M_finish

  • 3. list

    list是STL中的双向链表结构,也是大家经常用来的,下面我们一起来看一下list的基本组成:

    01 struct _List_node_base
    02 {
    03     _List_node_base* _M_next;  
    04     _List_node_base* _M_prev;  
    05 };
    06  
    07 template<typename _Tp>
    08 struct _List_node : public _List_node_base
    09 {
    10     _Tp _M_data;              
    11 };
    12  
    13 struct _List_impl
    14 {
    15     _List_node_base _M_node;
    16 };
    17  
    18 _List_impl _M_impl;

    通过上面的代码,我们可以看出,list本身只保留了一个空结点_M_impl._M_node,用来标示list的header,新加入的第一个结点,是直接链到_M_imp._M_node._M_next上的,后面的依次类推。我们可以把_M_impl._M_node.m_next强制转型成_List_node类型,然后通过_M_data来查看具体的数据内容,即((_List_node<T> *)(_M_impl._M_node->M_next))->M_data (T是数据的类型)。
    我们知道,string和vector都是线型结构,只要知道数据的起始地址和容器大小,便可获取到所以的元素内容。但是list是非线型结构,这时候就需要iterator来帮忙了,下面我们来看下list的iterator的基本组成:

    1 template<typename _Tp>
    2 struct _List_iterator
    3 {
    4      _List_node_base* _M_node;
    5 };

    从上面的代码,我们可以看出,list的iterator只是保存了一个list结点的指针,有此足矣,通过它我们便可以获取到iter里面的数据内容:((_List_node<T> *)(iter->_M_node)->M_data (T是数据的类型)

  • 4. deque

    deque是STL中提供一个队列结构,它同时兼有vector和list的基本特点,因此实现要复杂一些,下面我们来看一下它的基本组成:

    01 template<typename _Tp>
    02 struct _Deque_iterator
    03 {
    04     typedef _Tp**   _Map_pointer;
    05  
    06     _Tp* _M_cur;
    07     _Tp* _M_first;
    08     _Tp* _M_last;
    09     _Map_pointer _M_node;
    10 };
    11  
    12 typedef _Deque_iterator<_tp , _Tp&, _Tp*>  iterator;
    13 struct _Deque_impl
    14     public _Tp_alloc_type
    15 {
    16     _Tp**    _M_map;
    17     size_t   _M_map_size;
    18     iterator _M_start;
    19     iterator _M_finish;
    20 };
    21 _Deque_impl _M_impl;

    从上面的代码,及STL的源码,我们可以了解到,deque是由多块连续buffer,通过一个中控的数组_M_map来链在一起,实现的。我们可以通过_M_map,获取到每一个块的起始地址,在每一块内的数据,地址都是连续的,可以像数组一起取出对应的数据内容。
    另外,我们还可以通过iterator来访问deque的元素,上面的代码中,iterator的_M_cur是指向当前元素的指针,_M_first是当前块的起始地址,_M_last当前块的结束地址。
    本篇是〈手把手教你调试STL容器〉的上篇,主要介绍了string, vector, list和deque这些基本的容器,下篇将介绍map/set/multimap/multiset, hash_map/hash_set/hash_multimap/hash_multiset,敬请期待!


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值