Limitaciones de tamaño de almacenamiento inusual en VS2003 C++
visual-c++ malloc (1)
Tengo una aplicación C ++ que usa grandes conjuntos de datos, y me he dado cuenta al probar que se está quedando sin memoria, mientras todavía hay mucha memoria disponible. Reduje el código a un caso de prueba de ejemplo de la siguiente manera;
void MemTest()
{
size_t Size = 500*1024*1024; // 512mb
if (Size > _HEAP_MAXREQ)
TRACE("Invalid Size");
void * mem = malloc(Size);
if (mem == NULL)
TRACE("allocation failed");
}
Si creo un nuevo proyecto MFC, incluya esta función y ejecútela desde InitInstance, funciona bien en modo de depuración (memoria asignada como se esperaba), pero falla en el modo de lanzamiento (malloc devuelve NULL). Un solo paso a través de la liberación en los tiempos de ejecución C, mi función se inline obtengo el siguiente
// malloc.c
void * __cdecl _malloc_base (size_t size)
{
void *res = _nh_malloc_base(size, _newmode);
RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));
return res;
}
Llamar a _nh_malloc_base
void * __cdecl _nh_malloc_base (size_t size, int nhFlag)
{
void * pvReturn;
// validate size
if (size > _HEAP_MAXREQ)
return NULL;
''
''
Y (tamaño> _HEAP_MAXREQ) devuelve verdadero y por lo tanto mi memoria no se asigna. Poner un reloj en el tamaño vuelve con los 512MB exptected, lo que sugiere que el programa está enlazando en una biblioteca de tiempo de ejecución diferente con un _HEAP_MAXREQ mucho más pequeño. Al expandir las carpetas de VC ++ para _HEAP_MAXREQ se muestra el esperado 0xFFFFFFE0, por lo que no puedo entender qué está sucediendo aquí. ¿Alguien sabe de algún cambio CRT o versiones que causarían este problema, o me estoy perdiendo algo mucho más obvio?
Editar: Según lo sugerido por Andreas, al mirar esto debajo de esta vista de ensamblaje se muestra lo siguiente;
--- f:/vs70builds/3077/vc/crtbld/crt/src/malloc.c ------------------------------
_heap_alloc:
0040B0E5 push 0Ch
0040B0E7 push 4280B0h
0040B0EC call __SEH_prolog (40CFF8h)
0040B0F1 mov esi,dword ptr [size]
0040B0F4 cmp dword ptr [___active_heap (434660h)],3
0040B0FB jne $L19917+7 (40B12Bh)
0040B0FD cmp esi,dword ptr [___sbh_threshold (43464Ch)]
0040B103 ja $L19917+7 (40B12Bh)
0040B105 push 4
0040B107 call _lock (40DE73h)
0040B10C pop ecx
0040B10D and dword ptr [ebp-4],0
0040B111 push esi
0040B112 call __sbh_alloc_block (40E736h)
0040B117 pop ecx
0040B118 mov dword ptr [pvReturn],eax
0040B11B or dword ptr [ebp-4],0FFFFFFFFh
0040B11F call $L19916 (40B157h)
$L19917:
0040B124 mov eax,dword ptr [pvReturn]
0040B127 test eax,eax
0040B129 jne $L19917+2Ah (40B14Eh)
0040B12B test esi,esi
0040B12D jne $L19917+0Ch (40B130h)
0040B12F inc esi
0040B130 cmp dword ptr [___active_heap (434660h)],1
0040B137 je $L19917+1Bh (40B13Fh)
0040B139 add esi,0Fh
0040B13C and esi,0FFFFFFF0h
0040B13F push esi
0040B140 push 0
0040B142 push dword ptr [__crtheap (43465Ch)]
0040B148 call dword ptr [__imp__HeapAlloc@12 (425144h)]
0040B14E call __SEH_epilog (40D033h)
0040B153 ret
$L19914:
0040B154 mov esi,dword ptr [ebp+8]
$L19916:
0040B157 push 4
0040B159 call _unlock (40DDBEh)
0040B15E pop ecx
$L19929:
0040B15F ret
_nh_malloc:
0040B160 cmp dword ptr [esp+4],0FFFFFFE0h
0040B165 ja _nh_malloc+29h (40B189h)
Con los registros de la siguiente manera;
EAX = 009C8AF0 EBX = FFFFFFFF ECX = 009C8A88 EDX = 00747365 ESI = 00430F80 EDI = 00430F80 EIP = 0040B160 ESP = 0013FDF4 EBP = 0013FFC0 EFL = 00000206
Entonces, la comparación parece estar en contra de la constante correcta, es decir, @ 040B160 cmp dword ptr [esp + 4], 0FFFFFFE0h, también esp + 4 = 0013FDF8 = 1F400000 (mis 512mb)
Segunda edición: el problema estaba realmente en HeapAlloc, según la publicación de Andreas. Cambiar a un nuevo montón separado para objetos grandes, usando HeapCreate & HeapAlloc, no ayudó a aliviar el problema, ni intentó usar VirtualAlloc con varios parámetros. Algunos experimentos adicionales han demostrado que cuando la asignación de una gran sección de la memoria contigua falla, dos bloques más pequeños que producen la misma memoria total están bien. por ejemplo, donde falla un malloc de 300MB, 2 mallocs de 150MB funcionan bien. Por lo tanto, parece que necesitaré una nueva clase de matriz que pueda vivir en varios fragmentos de memoria grandes en lugar de un único bloque contiguo. No es un problema importante, pero hubiera esperado un poco más de Win32 en este día y edad.
Última edición: Lo siguiente arrojó 1.875GB de espacio, aunque no contiguo
#define TenMB 1024*1024*10
void SmallerAllocs()
{
size_t Total = 0;
LPVOID p[200];
for (int i = 0; i < 200; i++)
{
p[i] = malloc(TenMB);
if (p[i])
Total += TenMB; else
break;
}
CString Msg;
Msg.Format("Allocated %0.3lfGB",Total/(1024.0*1024.0*1024.0));
AfxMessageBox(Msg,MB_OK);
}
¿Puede ser que el depurador te está jugando un truco en el modo de lanzamiento? Ni el paso único ni los valores de las variables son confiables en el modo de lanzamiento.
Probé tu ejemplo en VS2003 en modo de lanzamiento, y cuando solo paso, al principio parece que el código aterriza en la línea return NULL
, pero cuando continúo pisando continúa eventualmente en HeapAlloc
, supongo que es esta función la que está fallando , mirando el desmontaje if (size > _HEAP_MAXREQ)
revela lo siguiente:
00401078 cmp dword ptr [esp+4],0FFFFFFE0h
así que no creo que sea un problema con _HEAP_MAXREQ
.