2009-11-16 13 views
7

Estoy programando en C en Visual Studio 2005. Tengo un programa de subprocesos múltiples, pero eso no es especialmente importante aquí.Determinación del espacio de pila con Visual Studio

¿Cómo puedo determinar (aproximadamente) cuánto espacio de pila usan mis hilos?

La técnica que estaba planeando utilizar es establecer la memoria de la pila en un valor predeterminado, digamos 0xDEADBEEF, ejecutando el programa durante un tiempo prolongado, pausando el programa e investigando la pila.

¿Cómo leo y escribo la memoria de la pila con Visual Studio?

EDITAR: Ver, por ejemplo, "How to determine maximum stack usage." Esa pregunta se refiere a un sistema integrado, pero aquí estoy tratando de determinar la respuesta en una PC normal.

Respuesta

1

La pila no funciona de la manera que usted espera. La pila es una secuencia lineal de páginas, la última (arriba) una de las cuales está marcada con un bit de protección de página. Cuando se toca esta página, se retira la punta protectora y se puede usar la página. Para un mayor crecimiento, se asigna una nueva página de guardia.

Por lo tanto, la respuesta que desea es dónde se asigna la página gaurd. Pero la técnica que propones tocaría la página en cuestión y, como resultado, invalidaría lo que estás tratando de medir.

La forma no invasiva de determinar si una página (pila) tiene el bit de protección es a través de VirtualQuery().

+1

Tu comentario no es exactamente verdad. Tocar la página en cuestión está bien, de verdad. La técnica es escribir toda la memoria relevante con un valor específico, y luego de un largo tiempo de operación, ver cuánta memoria ya no tiene ese valor. – JXG

+0

Qupting Microsoft: "Un intento de leer o escribir en una página de protección hace que el sistema genere una excepción STATUS_ACCESS_VIOLATION y apague el estado de la página de protección. Las páginas de guardia actúan como una alarma de acceso de una sola vez". No, la lectura no está exenta. – MSalters

+0

Creo que estamos hablando el uno del otro. – JXG

7

Puede hacer uso de la información en el Win32 Thread Information Block

Cuando se desea en un hilo para averiguar la cantidad de espacio de pila que utiliza puede hacer algo como esto:

#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

de Windows no se compromete la memoria de pila inmediatamente; en su lugar, reserva el espacio de direcciones para él y lo compromete página por página cuando se accede a él. Lee this page para obtener más información.

Como resultado, el espacio de dirección de pila consta de tres regiones contiguas:

  • Reservado pero la memoria no comprometidos que puede ser utilizado para el crecimiento pila (pero nunca se ha accedido todavía);
  • Página de protección, a la que nunca se ha accedido todavía, y sirve para activar el crecimiento de la pila cuando se accede a ella;
  • Memoria comprometida, es decir, memoria de pila a la que alguna vez tuvo acceso el hilo.

Esto nos permite construir una función que obtiene tamaño de la pila (con tamaño de página granularidad):

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; 
} 

Una cosa a tener en cuenta: CreateThread permite especificar pila inicial cometer tamaño (a través de dwStackSize parámetro, cuando STACK_SIZE_PARAM_IS_A_RESERVATION indicador no está configurado). Si este parámetro es distinto de cero, nuestra función devolverá el valor correcto solo cuando el uso de la pila sea mayor que el valor dwStackSize.

+0

¿La pila no crece? ¿Por qué está agregando RegionSize a la dirección base en lugar de sustraerla? – Philip

+2

@Philip: la pila crece (al menos en x86). Estoy agregando porque 'VirtualQuery' devuelve la dirección base de la región de asignación de memoria - la dirección del último byte utilizable (teóricamente) de una pila que crece hacia abajo. En una plataforma con pila ascendente, la primera llamada 'VirtualQuery' habría dado el resultado necesario. Creo que podría ilustrarlo con una imagen; Probablemente lo haga más tarde cuando tenga más tiempo. – atzz

+0

@atzz Tengo una ligera preocupación acerca de esta solución (que es bastante útil). ¿Cómo sabemos que al ejecutar esta función, o una de las llamadas de VirtualQuery que realiza, no nos topamos con la página de protección y por lo tanto hacemos que el estado de la pila real cambie debajo de nosotros? ¿No se pudo mover la página de guardia? – acm

-1

Puede utilizar la función GetThreadContext() para determinar el puntero de pila actual de la secuencia. Luego use VirtualQuery() para encontrar la base de la pila para este puntero. Al sustraer esos dos indicadores, obtendrá el tamaño de la pila para un hilo dado.

Cuestiones relacionadas