2009-08-15 13 views
43

¿Hay alguna manera en C para averiguar el tamaño de la memoria asignada dinámicamente?Determinar el tamaño de la memoria asignada dinámicamente en C

Por ejemplo, después

char* p = malloc (100); 

¿Hay una manera de averiguar el tamaño de la memoria asociado con p?

+2

'sizeof (char *) ... 'es redundante, ya que' char' se garantiza que tiene un tamaño de '1'. – mk12

+3

@ mk12 Aún deja más claro lo que está sucediendo. Especialmente cuando se escribe como 'malloc (100 * sizeof (char))', que sigue la convención usual de colocar unidades en el lado derecho de una cantidad. – DepressedDaniel

+2

En realidad, ahora prefiero escribir 'TYPE * ptr = malloc (100 * sizeof * ptr)', donde TYPE solo se escribe una vez. Esto garantiza que obtendrá una matriz de 100 elementos, incluso si cambia TYPE. – mk12

Respuesta

36

comp.lang.c FAQ list · Question 7.27 -

P. Entonces, ¿puedo consultar el paquete malloc para averiguar qué tan grande es un bloque asignado?

A. Lamentablemente, no existe una forma estándar o portátil. (Algunos compiladores proporcionan extensiones no estándar). Si necesita saberlo, tendrá que hacer un seguimiento de ello usted mismo. (Consulte también la pregunta 7.28.)

6

No, la biblioteca de tiempo de ejecución de C no proporciona esa función.

Algunas bibliotecas pueden proporcionar funciones específicas de plataforma o compilador que pueden obtener esta información, pero en general, la forma de realizar un seguimiento de esta información se encuentra en otra variable entera.

2

Espero que esto dependa de la implementación.
Si tiene la estructura de datos del encabezado, puede convertirla en el puntero y obtener el tamaño.

+0

¿Qué estructura de datos de encabezado? – nos

+0

@nos, si una implementación tallaba el tamaño requerido junto con un encabezado de mem-manager que devuelve la ubicación después del encabezado como el bloque de memoria asignado por tamaño. Tal encabezado es probable que almacene el tamaño en sí mismo. Si se conocía el encabezado typedef, el puntero asignado podría retroceder para acceder al encabezado y su campo. En una implementación de este tipo, el administrador de memoria implementaría la operación 'free' de la misma manera para bajo mantenimiento de contabilidad (y alto riesgo). – nik

+0

Dependiendo de cómo implementa el gestor de memoria la contabilidad de la operación 'libre', puede o no ser factible derivar el tamaño del puntero (sin embargo, es necesario conocer la implementación). Sin embargo, algunas implementaciones pueden elegir dar esta primitiva en la API (en cuyo caso no se requerirá conocimiento interno). – nik

1

Si utiliza malloc, entonces no puede obtener el tamaño.

Por otro lado, si utiliza la API del sistema operativo para asignar dinámicamente la memoria, como Windows heap functions, entonces es posible hacer eso.

37

No hay una forma estándar de encontrar esta información. Sin embargo, algunas implementaciones proporcionan funciones como msize para hacer esto. Por ejemplo:

Tenga en cuenta, sin embargo, que malloc asignará un mínimo del tamaño solicitado, por lo que debería comprobar si la variante msize para su implementación realmente devuelve el tamaño del objeto o la memoria realmente asignada en el montón.

3

Como todos los demás ya dijeron: No, no hay.

Además, siempre evitaría todas las funciones específicas del proveedor aquí, porque cuando descubra que realmente necesita usarlas, eso es generalmente una señal de que lo está haciendo mal. Debe almacenar el tamaño por separado, o no tener que saberlo en absoluto. Usar las funciones del proveedor es la forma más rápida de perder uno de los principales beneficios de escribir en C, la portabilidad.

+0

He visto algunos proyectos pero solo había uno, donde la idea de "esto debería estar funcionando en algo diferente a lo originalmente diseñado" ha ocurrido. Todos los proyectos involucrados en la empresa tuvieron la tarea de reescribir desde el suelo en ese punto. – Kobor42

8

La mentalidad C es proporcionar al programador las herramientas que lo ayuden con su trabajo, no proporcionar abstracciones que cambien la naturaleza de su trabajo. C también trata de evitar que las cosas sean más fáciles/seguras si esto sucede a expensas del límite de rendimiento.

Ciertas cosas que le gustaría hacer con una región de memoria solo requieren la ubicación del inicio de la región. Tales cosas incluyen trabajar con cadenas terminadas en nulo, manipular el primer n bytes de la región (si se sabe que la región es al menos así de grande), y así sucesivamente.

Básicamente, hacer un seguimiento de la longitud de una región es trabajo adicional, y si C lo hiciera automáticamente, a veces lo haría innecesariamente.

Muchas funciones de la biblioteca (por ejemplo fread()) requieren un puntero al inicio de una región, y también el tamaño de esta región. Si necesita el tamaño de una región, debe hacer un seguimiento de ello.

Sí, las implementaciones de malloc() suelen hacer un seguimiento del tamaño de una región, pero pueden hacerlo indirectamente, redondearlo o no conservarlo. Incluso si lo admiten, encontrar el tamaño de esta manera puede ser lento en comparación con el seguimiento de usted mismo.

Si necesita una estructura de datos que sepa cuán grande es cada región, C puede hacerlo por usted. Simplemente use una estructura que haga un seguimiento de qué tan grande es la región, así como un puntero a la región.

+0

Si bien esta respuesta no responde del todo la pregunta, agradezco la explicación de lo racional para que tal cosa no exista. – CoatedMoose

+2

Si el compilador no tiene ningún registro de qué tan grande bloqueó malloced, ¿qué hace en la coincidencia libre? –

+0

@AndrewLazarus La implementación de la biblioteca podría tener una forma indirecta de determinarlo, sin agregar nada al bloque directamente, y podría negarse a compartirlo :) –

-4

No estoy seguro, pero trato:

char **q = &p; 
int size = q[1] - q[0]; 
2

Este código es probable que funcione en la mayoría de las instalaciones de Windows:

template <class T> 
int get_allocated_bytes(T* ptr) 
{ 
return *((int*)ptr-4); 
} 

template <class T> 
int get_allocated_elements(T* ptr) 
{ 
return get_allocated_bytes(ptr)/sizeof(T); 
} 
+5

Nota: Esta es una respuesta en C++ a una pregunta en C. – chux

1

Esto puede funcionar, una pequeña actualización en el código:

void* inc = (void*) (++p) 
size=p-inc; 

Pero esto dará como resultado 1, es decir, la memoria asociada con p si es char*. Si es int*, el resultado será 4.

No hay forma de averiguar la asignación total.

-1

int *a; a=malloc(n*sizeof(int)); si malloc devuelve NULL significa que no ha recibido la memoria, de lo contrario lo que obtiene es la dirección base del bloque asignado, lo que significa (n*sizeof(int)) tamaño del bloque.

1

Bueno, ahora sé que esto no está respondiendo a su pregunta específica, sin embargo, pensando fuera de la caja, por así decirlo ... Se me ocurre que probablemente no necesita saber. Ok, vale, no, no me refiero a que tengas una implementación mala o poco ortodoxa ... quiero decir es que probablemente (sin mirar tu código solo estoy adivinando) probablemente quieras saber si tus datos pueden caber en la memoria asignada, si ese es el caso, entonces esta solución podría ser mejor. No debe ofrecer demasiada sobrecarga y va a resolver su problema "apropiado" si eso es precisamente lo que están manejando:

if (p != (tmp = realloc(p, required_size))) p = tmp; 

o si necesita para mantener el contenido antiguo:

if (p != (tmp = realloc(p, required_size))) memcpy(tmp, p = tmp, required_size); 

por supuesto simplemente podría usar:

p = realloc(p, required_size); 

y listo.

2

He aquí la mejor manera que he visto de crear un puntero etiquetado para almacenar el tamaño con la dirección.Todas las funciones de puntero todavía funcionaría como se esperaba:

robados de: https://stackoverflow.com/a/35326444/638848

También se puede aplicar una envoltura para malloc y libre de añadir etiquetas (como tamaño asignado y otra información meta) antes de que el puntero devuelto por malloc. Este es, de hecho, el método que un compilador de C++ etiqueta objetos con referencias a clases virtuales. Aquí está uno trabajando ejemplo:

#include <stdlib.h> 
#include <stdio.h> 

void * my_malloc(size_t s) 
{ 
    size_t * ret = malloc(sizeof(size_t) + s); 
    *ret = s; 
    return &ret[1]; 
} 

void my_free(void * ptr) 
{ 
    free((size_t*)ptr - 1); 
} 

size_t allocated_size(void * ptr) 
{ 
    return ((size_t*)ptr)[-1]; 
} 

int main(int argc, const char ** argv) { 
    int * array = my_malloc(sizeof(int) * 3); 
    printf("%u\n", allocated_size(array)); 
    my_free(array); 
    return 0; 
} 

La ventaja de este método sobre una estructura con el tamaño y el puntero

struct pointer 
{ 
    size_t size; 
    void *p; 
}; 

es que sólo se necesita reemplazar el malloc y llamadas gratuitas. Todas las demás operaciones de puntero no requieren refactorización.

+0

también debe redefinir realloc – user1251840

0

Todo el mundo diciendo que es imposible es técnicamente correcto (el mejor tipo de corrección).

Por razones de ingeniería, es una mala idea confiar en el subsistema malloc para decirle con precisión el tamaño de un bloque asignado. Para convencerse de esto, imagine que estaba escribiendo una aplicación grande , con varios asignadores de memoria diferentes; tal vez use raw libc malloc en una parte, pero C++ operator new en otra parte, y luego alguna API específica de Windows en otra parte . Entonces usted tiene todo tipo de void* volando alrededor. Escribir una función que pueda funcionar en de estos void* es imposible, a menos que de alguna manera pueda deducir a partir del valor del puntero de cuál de sus montones proviene.

Por lo que es posible que desee cerrar cada puntero en su programa con alguna convención que indique de dónde proviene el puntero (y dónde debe devolverse). Por ejemplo, en C++ llamamos a eso std::unique_ptr<void> (para punteros que deben ser operator delete 'd) o std::unique_ptr<void, D> (para punteros que deben devolverse a través de algún otro mecanismo D). Podrías hacer el mismo tipo de cosas en C si quisieras. Y una vez que esté terminando los punteros en objetos más grandes y seguros de todos modos, es solo un pequeño paso para struct SizedPtr { void *ptr; size_t size; } y nunca tendrá que preocuparse por el tamaño de una asignación nuevamente.

Sin embargo.

Hay también buenas razones por las que podría legítimamente quieren saber el tamaño subyacente real de una asignación. Por ejemplo, tal vez está escribiendo una herramienta de creación de perfiles para su aplicación que informará la cantidad real de memoria utilizada por cada subsistema, no solo la cantidad de memoria que el programador pensó que estaba usando. Si cada una de sus asignaciones de 10 bytes está secretamente usando 16 bytes debajo del capó, ¡es bueno saberlo! (Por supuesto, habrá otros gastos generales también, que no está midiendo de esta manera. Pero hay otras herramientas para que trabajo.) O tal vez solo está investigando el comportamiento de realloc en su plataforma. O tal vez le gustaría "redondear" la capacidad de una asignación creciente para evitar prematuras reasignaciones en el futuro.Ejemplo:

SizedPtr round_up(void *p) { 
    size_t sz = portable_ish_malloced_size(p); 
    void *q = realloc(p, sz); // for sanitizer-cleanliness 
    assert(q != NULL && portable_ish_malloced_size(q) == sz); 
    return (SizedPtr){q, sz}; 
} 
bool reserve(VectorOfChar *v, size_t newcap) { 
    if (v->sizedptr.size >= newcap) return true; 
    char *newdata = realloc(v->sizedptr.ptr, newcap); 
    if (newdata == NULL) return false; 
    v->sizedptr = round_up(newdata); 
    return true; 
} 

para obtener el tamaño de la asignación detrás de un puntero no nulo que ha sido devuelto directamente de malloc libc - no de un montón de encargo, y no apunta hacia el centro de un objeto - que puede utilizar las siguientes API específicas del sistema operativo, que he agrupado en una función de contenedor "portable-ish" para mayor comodidad. Si encuentra un sistema común donde este código no funciona, ¡deje un comentario y trataré de solucionarlo!

#if defined(__linux__) 
// https://linux.die.net/man/3/malloc_usable_size 
#include <malloc.h> 
size_t portable_ish_malloced_size(const void *p) { 
    return malloc_usable_size((void*)p); 
} 
#elif defined(__APPLE__) 
// https://www.unix.com/man-page/osx/3/malloc_size/ 
#include <malloc/malloc.h> 
size_t portable_ish_malloced_size(const void *p) { 
    return malloc_size(p); 
} 
#elif defined(_WIN32) 
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/msize 
#include <malloc.h> 
size_t portable_ish_malloced_size(const void *p) { 
    return _msize((void *)p); 
} 
#else 
#error "oops, I don't know this system" 
#endif 

#include <stdio.h> 
#include <stdlib.h> // for malloc itself 

int main() { 
    void *p = malloc(42); 
    size_t true_length = portable_ish_malloced_size(p); 
    printf("%zu\n", true_length); 
} 

probado en:

Cuestiones relacionadas