不同编译器对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());
}
参考:知乎