2009-11-16 12 views
7

Ich programmiere in C in Visual Studio 2005. Ich habe ein Multi-Thread-Programm, aber das ist hier nicht besonders wichtig.Ermitteln des Stack-Speicherplatzes mit Visual Studio

Wie kann ich (ungefähr) ermitteln, wie viel Stapelspeicherplatz meine Threads verwenden?

Die Technik, die ich verwenden wollte, setzt den Stapelspeicher auf einen bestimmten Wert, sagen 0xDEADBEEF, das Programm für eine lange Zeit ausgeführt wird, das Programm pausiert und den Stapel untersucht.

Wie kann ich Stapelspeicher mit Visual Studio lesen und schreiben?

EDIT: Siehe zum Beispiel "How to determine maximum stack usage." Diese Frage spricht über ein eingebettetes System, aber hier versuche ich, die Antwort auf einem normalen PC zu bestimmen.

Antwort

1

Der Stapel funktioniert nicht so, wie Sie es erwarten. Der Stapel ist eine lineare Folge von Seiten, deren letzte (oberste) mit einem Seitenschutz-Bit markiert ist. Wenn diese Seite berührt wird, wird das Schutzbit entfernt und die Seite kann verwendet werden. Für weiteres Wachstum wird eine neue Wachseite vergeben.

Daher ist die Antwort, die Sie wollen, wo die Gaurd-Seite zugeordnet ist. Aber die Technik, die Sie vorschlagen, würde die fragliche Seite berühren, und als Ergebnis würde es genau die Sache ungültig machen, die Sie versuchen zu messen.

Die nicht-invasive Möglichkeit zu bestimmen, ob eine (Stapel-) Seite das Schutzbit hat, ist über VirtualQuery().

+1

Ihr Kommentar stimmt nicht genau. Das Berühren der betreffenden Seite ist in Ordnung, wirklich. Die Technik besteht darin, den gesamten relevanten Speicher mit einem bestimmten Wert zu schreiben und nach einer langen Betriebsdauer zu sehen, wie viel Speicher diesen Wert dort nicht mehr hat. – JXG

+0

Microsoft wird geupdated: "Ein Versuch, von einer Wächter-Seite zu lesen oder auf diese zu schreiben, führt dazu, dass das System eine STATUS_ACCESS_VIOLATION-Ausnahme auslöst und den Schutzseiten-Status deaktiviert. Die Wachen-Seiten fungieren somit als One-Shot-Zugriffsalarm." Nein, Lesen ist nicht ausgenommen. – MSalters

+0

Ich denke wir reden aneinander vorbei. – JXG

7

Sie können Win32 Thread Information Block

bei der Nutzung von Informationen, wenn Sie in einem Thread wollen herausfinden, wie viel Stapelspeicher nutzt man so etwas tun kann:

#include <windows.h> 
#include <winnt.h> 
#include <intrin.h> 

inline NT_TIB* getTib() 
{ 
    return (NT_TIB*)__readfsdword(0x18); 
} 
inline size_t get_allocated_stack_size() 
{ 
    return (size_t)getTib()->StackBase - (size_t)getTib()->StackLimit; 
} 

void somewhere_in_your_thread() 
{ 
    // ... 
    size_t sp_value = 0; 
    _asm { mov [sp_value], esp } 
    size_t used_stack_size = (size_t)getTib()->StackBase - sp_value; 

    printf("Number of bytes on stack used by this thread: %u\n", 
      used_stack_size); 
    printf("Number of allocated bytes on stack for this thread : %u\n", 
      get_allocated_stack_size());  
    // ... 
} 
15

Windows nicht begehen der Stapelspeicher sofort; Stattdessen reserviert es den Adressraum dafür und bestätigt es Seite für Seite, wenn auf es zugegriffen wird. Lesen Sie this page für weitere Informationen.

Als Ergebnis Stapel Adreßraum besteht aus drei zusammenhängenden Bereichen:

  • reserviert, aber nicht gebundenen Speicher, der für das Stapelwachstum verwendet werden kann (jedoch wurde noch nie zugegriffen wird);
  • Guard-Seite, auf die noch nie zugegriffen wurde, und dazu dient, das Stack-Wachstum auszulösen, wenn auf sie zugegriffen wird;
  • Engagierter Speicher, d.h. Stapelspeicher, auf den jemals vom Thread zugegriffen wurde.

Dies ermöglicht es uns, eine Funktion zu erstellen, die Stapelgröße erhält (mit Seitengröße Auflösung):

static size_t GetStackUsage() 
{ 
    MEMORY_BASIC_INFORMATION mbi; 
    VirtualQuery(&mbi, &mbi, sizeof(mbi)); 
    // now mbi.AllocationBase = reserved stack memory base address 

    VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack 
    // skip it 

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe the guard page 
    // skip it 

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack 

    return mbi.RegionSize; 
} 

Eine Sache zu prüfen: CreateThread erlaubt anfängliche Stapel angeben Größe commit (über dwStackSize Parameter, wenn STACK_SIZE_PARAM_IS_A_RESERVATION Flag ist nicht gesetzt). Wenn dieser Parameter ungleich Null ist, gibt unsere Funktion nur dann den richtigen Wert zurück, wenn die Stack-Nutzung größer als ist.

+0

Fällt der Stack nicht nach unten? Warum fügen Sie der Basisadresse die RegionSize hinzu, statt sie zu subtrahieren? – Philip

+2

@Philip - Der Stack wächst (auf mindestens x86). Ich füge hinzu, weil 'VirtualQuery' die Basisadresse des Speicherzuordnungsbereichs zurückgibt - die Adresse des letzten (theoretisch) verwendbaren Bytes eines nach unten wachsenden Stapels. Auf einer Plattform mit einem aufsteigenden Stack hätte der erste "VirtualQuery" -Aufruf das erforderliche Ergebnis geliefert. Ich denke, ich könnte es mit einem Bild illustrieren; Ich werde es wahrscheinlich später tun, wenn ich mehr Zeit habe. – atzz

+0

@atzzz Ich mache mir Sorgen um diese Lösung (was sehr hilfreich ist). Woher wissen wir, dass wir während der Ausführung dieser Funktion oder eines der von ihr ausgeführten VirtualQuery-Aufrufe nicht auf die Guard-Seite gelangen und dadurch bewirken, dass sich der tatsächliche Stack-Status unter uns ändert? Konnte sich die Wachseite nicht bewegen? – acm

-1

Mit der GetThreadContext() - Funktion können Sie den aktuellen Stapelzeiger des Threads ermitteln. Verwenden Sie dann VirtualQuery(), um Stapelbasis für diesen Zeiger zu suchen. Durch das Subtrahieren dieser beiden Zeiger erhalten Sie die Stapelgröße für den angegebenen Thread.

Verwandte Themen