简体   繁体   English

C ++对象的内存消耗

[英]C++ objects memory consumption

First of all: this question is not about "how to use delete operator", it is about "why many class objects of small size consumes lots of memory". 首先:这个问题不是关于“如何使用删除操作符”,而是关于“为什么许多小尺寸的类对象消耗大量内存”。 Let's say we have this code: 假设我们有这个代码:

class Foo
{

};

void FooTest()
{
    int sizeOfFoo = sizeof(Foo);

    for (int i = 0; i < 10000000; i++)
        new Foo();
}

Size of empty class Foo is 1 byte, but when the code is executed it consumes about 600Mb memory. 空类Foo的大小是1个字节,但是当执行代码时它消耗大约600Mb的内存。 How is that? 那个怎么样?

UPDATE. UPDATE。 I've tested this on Win10 x64 in Visual Studio 2010. Memory usage from OS task manager. 我在Visual Studio 2010中对Win10 x64进行了测试。来自OS任务管理器的内存使用情况。

The C++ heap manager has 4 different "modes" in which it reserves less or more space around an object. C ++堆管理器有4种不同的“模式”,它可以在对象周围保留更少或更多的空间。 These are 这些是

  1. Release mode, running normally 发布模式,正常运行
  2. Release mode, running under the debugger 发布模式,在调试器下运行
  3. Debug mode, running normally 调试模式,正常运行
  4. Debug mode, running under the debugger 调试模式,在调试器下运行

The additional memory is used for no mans land (0xFDFDFDFD), alignment to 16 byte boundaries (0xBAADF00D), heap management etc. 附加内存用于无域(0xFDFDFDFD),对齐到16字节边界(0xBAADF00D),堆管理等。

I suggest reading this post and looking at the 4 scenarios in a debugger, opening the raw memory view. 我建议阅读这篇文章并查看调试器中的4个场景,打开原始内存视图。 You'll learn a lot. 你会学到很多东西。 For case 1 and 3, insert a pause where you can attach the debugger to the running process, while in case 2 and 4 you should run the debugger first and then start the executable from there. 对于情况1和3,插入一个暂停,您可以将调试器附加到正在运行的进程,而在情况2和4中,您应首先运行调试器,然后从那里启动可执行文件。

I used to demonstrate how the C++ heap works when explaining the buffer overrun. 我曾经在演示缓冲区溢出时演示了C ++堆的工作原理。 Here's a demo program you can use, not perfect, but maybe useful: 这是一个你可以使用的演示程序,不完美,但也许有用:

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <stdio.h>
void SimpleBufferOverrunDemo( int argc, _TCHAR* argv[] ) ;

int _tmain(int argc, _TCHAR* argv[])
{
    SimpleBufferOverrunDemo(argc, argv);

    getchar();
    return 0;
}

void SimpleBufferOverrunDemo( int argc, _TCHAR* argv[] ) 
{
    if (argc != 2)
    {
        std::cout << "You have to provide an argument!\n";
        return;
    }

    // Allocate 5 bytes
    byte* overrunBuffer = new byte[5];

    // Demo 1: How does the memory look after delete? Uncomment the following to demonstrate
    //delete [] overrunBuffer; //0xfeeefeee in debug mode.
    //DebugBreak();

    // Demo 2: Comment Demo 1 again. 
    // Provide a 5 byte sequence as argument
    // Attach with WinDbg and examine the overrunBuffer.

    // 2.1. How many heaps do we have?
    // !heap -s

    // 2.2. How to find the heap block and how large is it?
    // !heap -x [searchAddress]
    // !heap -i [blockAddress] -> Wow 72 bytes block size for 5 allocated bytes!

    // 2.3. Show that _HEAP_ENTRY does not work any more.

    // Demo 3: Write behind the 5 allocated bytes.
    // Provide more than 5 bytes as argument, depending on how what you want to destroy
    // 3.1 Write into the no mans land.
    // 3.2 Write into the guard bytes.
    // 3.3 Write into the meta data section of the following heap block! -> When does it crash?

    std::wstring arg = argv[1];

    for (size_t i = 0; i < arg.size(); i++)
    {
        overrunBuffer[i] = (byte)arg[i];
    }

    // Crash happens not where it was caused(!) This is important!
    std::cout << "Now we do a plenty of other work ...";
    ::Sleep(5000);

    delete[] overrunBuffer;

    // Demo 4: Demonstrate page heap / application verifier!
}

Class Foo might be 1 byte in size, but since you allocate many Foo s individually, they can (and probably do) get allocated on some byte aligned addresses and due to fragmentation consume more memory than you expected. Foo大小可能是1个字节,但由于您分别分配了许多Foo ,它们可以(并且可能)在某些字节对齐的地址上分配,并且由于碎片消耗的内存比预期的多。

Additionally, there's the memory used by the internal memory management system. 此外,还有内部存储器管理系统使用的内存。

You have to know that the memory consumed by a process from the OS point of view is not only related to the objects that are allocated in your code. 您必须知道,从OS的角度来看,进程所消耗的内存不仅与代码中分配的对象有关。

It's something that is heavily implementation dependent, but in general, for performance reasons, memory allocation is rarely passed one to one to the OS, but pooled via the management of the free store. 这是一个严重依赖于实现的东西,但总的来说,出于性能原因,内存分配很少一对一地传递给操作系统,而是通过免费存储的管理汇集 The general principle is as follows: 一般原则如下:

  • At startup of the program, your C++ implementation will allocate some initial space for the free store and the heap. 在程序启动时,您的C ++实现将为免费存储和堆分配一些初始空间。
  • When this memory is consumed, and new memory is needed, the memory management will request larger chunks to the OS and make these available in the free store. 当消耗此内存并且需要新内存时,内存管理将向操作系统请求更大的块并使其在免费存储中可用。
  • It is possible that the size of chunks requested from OS could adapt themselves to the allocation patterns. 从OS请求的块的大小可能适应分配模式。

So from the 600MB viewed in the task manager, it is possible that only a small part is effectively allocated to your objects, and a bigger part is in fact still free and available in the free store. 因此,从任务管理器中查看的600MB,可能只有一小部分被有效地分配给您的对象,而更大的部分实际上仍然是免费的,并且可以在免费商店中使用。

This being said, the consumed memory will be larger than size x number of objects: for each allocated object, the memory management functions have to manage some additional information (like the size of the allocated object). 这就是说,消耗的内存将大于x个对象的大小:对于每个分配的对象,内存管理功能必须管理一些附加信息(如分配对象的大小)。 SImilarly, the free memory pool also requires pointer (generally a linked list) to keep track of the free blocks if these are not contiguous. 实际上,空闲内存池还需要指针(通常是链表)来跟踪空闲块(如果它们不是连续的)。

Very interesting post about Windows. 关于Windows的非常有趣的帖子。

For comparison, on Ubuntu 15.10(64): 为了比较,在Ubuntu 15.10(64)上:

int t407(void)
{
   std::cout << "\nsizeof(Foo): " << sizeof(Foo) << std::endl;

   std::cout << "\nsizeof(Foo*): " << sizeof(Foo*) << std::endl;

   std::vector<Foo>  fooVec;
   fooVec.reserve(10000000);
   for (size_t i=0; i<10000000; ++i)
   {
      Foo t;
      fooVec.push_back(t);
   }
   std::cout << "\nfooVec.size(): " << fooVec.size() 
             << " elements" << std::endl
             << "fooVec.size() * sizeof(Foo): " 
             << fooVec.size() * sizeof(Foo) << " bytes" << std::endl
             << "sizeof(fooVec): " << sizeof(fooVec) 
             << " bytes (on stack)" << std::endl;

   return(0);
}

With output: 随着输出:

 sizeof(Foo): 1

 sizeof(Foo*): 8

 fooVec.size(): 10000000 elements 
 fooVec.size() * sizeof(Foo): 10000000 bytes
 sizeof(fooVec): 24 bytes (on stack)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM