g++ std标准库对std::string的实现分析

不同编译器对C++标准库的实现都有各自的逻辑和风格,对于字符串类型,gnu c++

template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string {
  struct _Alloc_hider : allocator_type  // TODO check __is_final
  {
    _Alloc_hider(pointer __dat, const _Alloc &__a)
        : allocator_type(__a), _M_p(__dat) {}

    _Alloc_hider(pointer __dat, _Alloc &&__a = _Alloc())
        : allocator_type(std::move(__a)), _M_p(__dat) {}

    pointer _M_p;  // The actual data.
  };

  _Alloc_hider _M_dataplus;
  size_type _M_string_length;

  enum { _S_local_capacity = 15 / sizeof(_CharT) };

  union {
    _CharT _M_local_buf[_S_local_capacity + 1];
    size_type _M_allocated_capacity;
  };
};
_Alloc_hider _M_dataplus;     // 指向真实数据的指针
size_type _M_string_length;   // 字符串长度
union {
  _CharT _M_local_buf[_S_local_capacity + 1];
  size_type _M_allocated_capacity;
};							  // 联合体:存储栈上开辟的存放字符数组,或者保存堆上开辟字符数组大小

gnu c++制定string的方法中,常会判断字符串当前保存在不在栈上,具体方式通过:

// 返回堆上开辟内存首地址
pointer _M_data() const { return _M_dataplus._M_p; }

// 返回栈上数组首地址
const_pointer _M_local_data() const {
#if __cplusplus >= 201103L
	return std::pointer_traits<const_pointer>::pointer_to(*_M_local_buf);
#else
	return const_pointer(_M_local_buf);
#endif
}

// 比较地址是否相等,相等则表示字符串在栈上
bool _M_is_local() const { return _M_data() == _M_local_data(); }

当string长度小于等于_S_local_capacity时,将每个字符存在_M_local_buf指向的栈上。 当string长度大于_S_local_capacity时,在堆上分配。

string和vector很像,他的capacity方法返回值均为预占空间大小

size_type capacity() const _GLIBCXX_NOEXCEPT {
	return _M_is_local() ? size_type(_S_local_capacity) : _M_allocated_capacity;
}

从源码可以看到,首先判断字符串当前保存在不在栈上,是则返回_S_local_capacity,不是则返回堆上开辟的字符串大小,他们均不包含尾部\0

另外C++11后涉及到移动,可以预料到栈上15个字节是通过数组拷贝,而大于15字节后,将内存移入堆上,堆上的移动,就是指针的交换了。

basic_string(basic_string&& __str) noexcept
    : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
  if (__str._M_is_local()) {
    traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
  } else {
    _M_data(__str._M_data());
    _M_capacity(__str._M_allocated_capacity);
  }

  // Must use _M_length() here not _M_set_length() because
  // basic_stringbuf relies on writing into unallocated capacity so
  // we mess up the contents if we put a '\0' in the string.
  _M_length(__str.length());
  __str._M_data(__str._M_local_data());
  __str._M_set_length(0);
}

短字符使用栈内存存放的优势得益于栈的快捷,在性能上略高于操作堆上数据。

可使用一下方式,在linux和windows上面分别使用g++或者llvm,或者msvc查看的输出结果,需要指出一定要编译器支持或者打开C++11

#include <cstdio>
#include <cstdlib>
#include <new>
#include <string>

std::size_t allocated = 0;

void* operator new(size_t sz) {
  void* p = std::malloc(sz);
  allocated += sz;
  return p;
}

void operator delete(void* p) noexcept { return std::free(p); }

auto main() -> int {
  allocated = 0;
  std::string s("11111111111111111");  // 改变字符串长度,以15为界
  std::printf(
      "stack space = %zu, heap space = %zu, capacity = %zu, size = %zu\n",
      sizeof(s), allocated, s.capacity(), s.size());

  std::string ss;
  ss.swap(s);
  std::printf(
      "stack space = %zu, heap space = %zu, capacity = %zu, size = %zu\n",
      sizeof(s), allocated, s.capacity(), s.size());
}

参考:知乎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歪锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值