C++中的字符串分配

6

当我在尝试重载new和delete操作符时,我注意到了一些奇怪的事情。

  1. I have:

    void* operator new(size_t size)
    {
        std::cout << "Allocating memory..." << std::endl;
    
        void* p = malloc(size);
    
        if (NULL == p)
        {
            throw std::bad_alloc();
        }
    
        return p;
    }
    
  2. When I do:

    int main()
    {
        int* x = new int(1);
        std::cout << *x << std::endl;
        delete x;
        return EXIT_SUCCESS;
    }
    

    Everything works as expected and I get:

    Allocating memory...
    1
    
  3. But when I do:

    int main()
    {
        std::string* s = new std::string("Hello world");
        std::cout << *s << std::endl;
        delete s;
        return EXIT_SUCCESS;
    }
    

    I get:

    Allocating memory...
    Allocating memory...
    Hello world
    
  4. In fact, when I do:

    int main()
    {
        std::string s = "Hello world";
        return EXIT_SUCCESS;
    }
    

    I still get Allocating memory...!

  5. Finally, I do:

    int main()
    {
        std::string s = "Hello world";
        std::cout << &s << std::endl;
        while (true);
    }
    

    To get something like:

    $ ./test &
    [1] 8979
    Allocating memory...
    0xbfc39a68
    $ cat /proc/8979/maps | grep stack
    bfc27000-bfc3c000 ... [stack]
    

    So now I'm sure the s variable is allocated on the stack... but then, what's calling the new operator? My best guess would be it has something to do with the memory allocation for the actual literal, "Hello world"... but it's supposed to be static memory, and new is all about dynamic memory.

发生了什么?

更新

在阅读评论并自行调试示例后,我想总结一下:确实,一旦调用字符串构造函数,它会为其内部实现在堆上分配内存。这可以通过跟踪new调用来看到:

(gdb) b 13 // that's the std::cout << "Allocating memory..." << std::endl; line
(gdb) r
... Breakpoing 1, operator new (size=16) at test.cpp:13 ...
(gdb) backtrace
#0 operator new (size=16) at main.cpp:13
#1 std::string::_Rep::_S_create(unsigned int, unsigned int, std::allocator<char> const&) () from /usr/lib/libstdc++.so.6
...

阅读 std::string(也就是 basic_string.tcc)源代码:

template<typename _CharT, typename _Traits, typename _Alloc>
typename basic_string<_CharT, _Traits, _Alloc>::_Rep*
basic_string<_CharT, _Traits, _Alloc>::_Rep::
_S_create(size_type __capacity, size_type __old_capacity,
          const _Alloc& __alloc)
{

   ...

    void* __place = _Raw_bytes_alloc(__alloc).allocate(__size);
   _Rep *__p = new (__place) _Rep; // Bingo!
   __p->_M_capacity = __capacity;

   ...

}

所以,编程很酷。

注意:malloc/freenew/delete是一对。 - Grijesh Chauhan
1
为什么不使用调试器并在您的 operator new 中断点,以查看是谁在调用它?流也会分配内存。 - user405725
2
std::string 的整个意义在于它封装了一个 char* 并为您拥有/管理它。因此,当您创建一个 std::string 时,它会分配一些内存来存储实际的字符。构造函数甚至可以使用自定义分配器。 - BoBTFish
@GrijeshChauhan:如果你的意思是我也通过free实现了delete,那么是的,但这与问题无关,所以我省略了它。 - Dan Gittik
2
在第三段代码中,它不应该是 std::string *s 吗? - phoxis
显示剩余4条评论
5个回答

5
当你写代码时
std::string s = "Hello world";

你正在调用构造函数string (const char* s);,该构造函数的规定是复制由s指向的以null结尾的字符序列(C字符串)。 因此,构造函数会分配内存以存储复制品。

最后一句话中的“you”让人感到困惑,最好改为“因此s需要分配内存来存储副本”,但我建议您更改构造函数参数名称,因为它也是s - Matthieu M.

3
例子3:(首先注意,您必须使用std :: string * s 而不是std :: string s ,因为new 返回指针)。
使用gdb,在std :: cout行处设置断点并回溯两个调用:
第一个是您在代码中编写的new。 第二个恰好是从libstdc ++。so.6中调用的(在我的情况下):
(gdb) backtrace
#0  operator new (size=36) at test.cpp:6
#1  0x00007ffff7b78a89 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x00007ffff7b7a495 in char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7b7a5e3 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x0000000000400da1 in main () at test.cpp:20

不仅回答问题,还提供了调查的提示,这是一个加分项。 - Matthieu M.

1
  std::string s = "Hello world";

右侧是静态内存,s 构造函数分配新的内存并复制 "Hello world"。

0

std::string是一种封装C风格字符串的结构。

首先为字符串容器结构进行第一次分配。
其次,在字符串构造中进行字符串副本的第二次分配。


0

string对象本身使用new为实际字符分配空间。这是在字符串的构造函数中完成的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接