簡體   English   中英

C ++對象的內存消耗

[英]C++ objects memory consumption

首先:這個問題不是關於“如何使用刪除操作符”,而是關於“為什么許多小尺寸的類對象消耗大量內存”。 假設我們有這個代碼:

class Foo
{

};

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

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

空類Foo的大小是1個字節,但是當執行代碼時它消耗大約600Mb的內存。 那個怎么樣?

UPDATE。 我在Visual Studio 2010中對Win10 x64進行了測試。來自OS任務管理器的內存使用情況。

C ++堆管理器有4種不同的“模式”,它可以在對象周圍保留更少或更多的空間。 這些是

  1. 發布模式,正常運行
  2. 發布模式,在調試器下運行
  3. 調試模式,正常運行
  4. 調試模式,在調試器下運行

附加內存用於無域(0xFDFDFDFD),對齊到16字節邊界(0xBAADF00D),堆管理等。

我建議閱讀這篇文章並查看調試器中的4個場景,打開原始內存視圖。 你會學到很多東西。 對於情況1和3,插入一個暫停,您可以將調試器附加到正在運行的進程,而在情況2和4中,您應首先運行調試器,然后從那里啟動可執行文件。

我曾經在演示緩沖區溢出時演示了C ++堆的工作原理。 這是一個你可以使用的演示程序,不完美,但也許有用:

#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!
}

Foo大小可能是1個字節,但由於您分別分配了許多Foo ,它們可以(並且可能)在某些字節對齊的地址上分配,並且由於碎片消耗的內存比預期的多。

此外,還有內部存儲器管理系統使用的內存。

您必須知道,從OS的角度來看,進程所消耗的內存不僅與代碼中分配的對象有關。

這是一個嚴重依賴於實現的東西,但總的來說,出於性能原因,內存分配很少一對一地傳遞給操作系統,而是通過免費存儲的管理匯集 一般原則如下:

  • 在程序啟動時,您的C ++實現將為免費存儲和堆分配一些初始空間。
  • 當消耗此內存並且需要新內存時,內存管理將向操作系統請求更大的塊並使其在免費存儲中可用。
  • 從OS請求的塊的大小可能適應分配模式。

因此,從任務管理器中查看的600MB,可能只有一小部分被有效地分配給您的對象,而更大的部分實際上仍然是免費的,並且可以在免費商店中使用。

這就是說,消耗的內存將大於x個對象的大小:對於每個分配的對象,內存管理功能必須管理一些附加信息(如分配對象的大小)。 實際上,空閑內存池還需要指針(通常是鏈表)來跟蹤空閑塊(如果它們不是連續的)。

關於Windows的非常有趣的帖子。

為了比較,在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);
}

隨着輸出:

 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