在csdn回答别人的问题时,偶然间接触到sizeof求sting的内存容量大小的问题,经过测试,结果有些意外,引发自己的深度思考,探索一番做整理。
0:相关知识点
为了分析sizeof(string)的大小,涉及到一些知识点:
====》sizeof()是静态运算符,关注的是参数类型,编译时决定了。
====》类的长度实际是类中非静态成员变量长度,无成员变量是1,有虚函数要维持虚函数表(一个指针大小),有基类时要算基类的成员吧。
====》类模板(通用的类)和模板类(实例化的类)的概念
====》可以用using对复杂的结构重新命名。
====》string的内存分配,实际上还是类中定义一个指针,真正赋值的时候去new具体的大小。
====》typename的新用法:用在模板定义中,把相关名称识别为类型。
1. 问题描述(单纯记录引发我思考的起点):
有新手同学在使用string及数组的时候,有误用,引发我的思考:
//他的本意应该是想定义时确定一个数组的大小,故这样写了代码 string s; char c[strlen(s.c_str())]={0}; //都是不对的代码 int a[strlen(s.c_str())]={0};
我能明确看出他的不对,因此我期望用demo去给他验证,数组下标是0,这里就涉及字符串string的一个长度问题,我抛出:
//这是在vs 2019上运行的结果: int main() { string s; //这里我的本意是让他理解及去梳理strlen(),length(),以及sizeof()这几个求长度函数的差异,却让自己深思一下 cout << strlen(s.c_str()) << endl; //0 cout << s.length() << endl; //0 cout << sizeof(s) << endl; //28 cout << sizeof(string) << endl; //28 return 0; } //探索后,发现在linux上运行的结果 #include <stdio.h> #include <iostream> #include <string.h> using namespace std; int main() { string s; cout << strlen(s.c_str()) << endl; //0 cout << s.length() << endl; //0 cout << sizeof(s) << endl; //32 cout << sizeof(string) << endl; //32 return 0; }
检测测试发现:
VS2019上sizeof(string)的长度是28
linux上sizeof(string)的长度是32
VS2022上sizeof(string)的长度是40
2. 为什么sizeof(string)的长度是28,以及32,40
第一眼懵逼,问了一把身边同事,他随口回答是类似array的预分配内存
===>emmmm,有点涉及,但是依然不对,这是string的其他知识点
结合百度,明确以下信息:
====》用sizeof求string的结果,在不同的系统中结果是不同的。
====》用sizeof求string的大小,与string是否初始化也是无关的。
====》sizeof是静态运算符,在编译的时候获取到响应结果,而string对象的申请,一般都是在运行时动态分配。
反思到,sizeof所求,其实是类所占用的大小,可以跟踪代码进行探索。
3:跟踪一下string类的大小
这里我在vs2019上试图跟踪了一下string类。 其他的类似吧~
//string实际是 类模板 传入char using string = basic_string<char, char_traits<char>, allocator<char>>; //第一步: //这里实际是关注basic_string 中传入char时类所占内存的大小。 //实际测试: cout << sizeof(basic_string111<char, char_traits<char>, allocator<char>>) << endl; //28 //简化basic_string 相关的类中成员,最终类简化后占用内存的成员是: template <class _Elem, class _Traits = char_traits<_Elem>, class _Alloc = allocator<_Elem>> class basic_string111 { // null-terminated transparent array of elements private: using _Alty = _Rebind_alloc_t<_Alloc, _Elem>; using _Alty_traits = allocator_traits<_Alty>; using _Scary_val = _String_val<conditional_t<_Is_simple_alloc_v<_Alty>, _Simple_types<_Elem>, _String_iter_types<_Elem, typename _Alty_traits::size_type, typename _Alty_traits::difference_type, typename _Alty_traits::pointer, typename _Alty_traits::const_pointer, _Elem&, const _Elem&>>>; private: _Compressed_pair<_Alty, _Scary_val> _Mypair; //这是实际占用的 }; //第二步: //这里就需要研究成员变量的的大小了: _Compressed_pair<_Alty, _Scary_val> _Mypair; //实际测试: cout << sizeof(_Alty) << endl; //1 cout << sizeof(_Scary_val) << endl; //28 cout << sizeof(_Compressed_pair<_Alty, _Scary_val>) << endl; //28 //第三步 //接下来需要研究成员大小: _Compressed_pair<_Alty, _Scary_val>大小为什么是28 //首先获取到 _Compressed_pair 实际也是一个类模板 中间有个成员变量 实际是模板第二个参数 template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>> class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first public: _Ty2 _Myval2; using _Mybase = _Ty1; // for visualization }; //====>所以 _Compressed_pair 的大小,实际上可以确定是第二个模板参数 也就是_Scary_val 的大小。 //====>_Scary_val 实际又是 类模板 using _Scary_val = _String_val<conditional_t<_Is_simple_alloc_v<_Alty>, _Simple_types<_Elem>, _String_iter_types<_Elem, typename _Alty_traits::size_type, typename _Alty_traits::difference_type, typename _Alty_traits::pointer, typename _Alty_traits::const_pointer, _Elem&, const _Elem&>>>; // ====》也就是确定 这个类模板 对应传参实际大小 template <class _Val_types> class _String_val111 : public _Container_base { public: // length of internal buffer, [1, 16]: static constexpr size_type _BUF_SIZE = 16 / sizeof(char) < 1 ? 1 : 16 / sizeof(char); union _Bxty { // storage for small buffer or pointer to larger one _Bxty() noexcept {} // user-provided, for fancy pointers ~_Bxty() noexcept {} // user-provided, for fancy pointers value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // TRANSITION, ABI: _Alias is preserved for binary compatibility (especially /clr) } _Bx; typename size_t _Mysize; // 4个字节 typedef unsigned int size_t; typename _Val_types::size_type _Myres; // 4个字节 实际是模板参数传参对应的size_type 类型 实际还是size_t }; //第四步:实际上就是分析_String_val111 所占大小 //为了确定_Val_types::size_type 的类型及长度 跟踪一下模板参数:conditional_t _String_val111<conditional_t<_Is_simple_alloc_v<_Alty>, _Simple_types<char>, _String_iter_types<char, typename _Alty_traits::size_type, typename _Alty_traits::difference_type, typename _Alty_traits::pointer, typename _Alty_traits::const_pointer, char&, const char&>>> //可以确定他实际是 _Simple_types<char>中对应的size_type template <class _Value_type> struct _Simple_types1 { using value_type = _Value_type; using size_type = size_t; //===》实际是这个 也是unsigned int using difference_type = ptrdiff_t; using pointer = value_type*; using const_pointer = const value_type*; }; //分析后可以确定 //_String_val111 类中,成员变量的长度是 union (16) + size_t(4) +_Val_types::size_type(4) +基类成员中有一个指针(4) = 28
最终 跟踪vs2019中string相关类的定义,可以确定sizeof(string)所求长度就是类中成员变量的总长度 28
4:总结涉及到的知识点。
为了分析sizeof(string)的大小,涉及到一些知识点:
====》sizeof()是静态运算符,关注的是参数类型,编译时决定了。
====》类的长度实际是类中非静态成员变量长度,无成员变量是1,有虚函数要维持虚函数表(一个指针大小),有基类时要算基类的成员吧。
====》类模板(通用的类)和模板类(实例化的类)的概念
====》可以用using对复杂的结构重新命名。
====》string的内存分配,实际上还是类中定义一个指针,真正赋值的时候去new具体的大小。
====》typename的新用法:用在模板定义中,把相关名称识别为类型。