que - ¿Cómo puedo obtener el tamaño de una matriz desde un puntero en C?
punteros a cadenas (13)
He asignado una "matriz" de mystruct
de tamaño n
como esta:
if (NULL == (p = calloc(sizeof(struct mystruct) * n,1))) {
/* handle error */
}
Más tarde, solo tengo acceso a p
, y ya no tengo n
. ¿Hay alguna forma de determinar la longitud de la matriz dada solo con el puntero p
?
Me imagino que debe ser posible, ya que free(p)
hace exactamente eso. Sé que malloc()
realiza un seguimiento de la cantidad de memoria que ha asignado, y es por eso que conoce la longitud; tal vez hay una forma de consultar esta información? Algo como...
int length = askMallocLibraryHowMuchMemoryWasAlloced(p) / sizeof(mystruct)
Sé que debería volver a trabajar el código para que yo sepa n
, pero preferiría no hacerlo si es posible. ¿Algunas ideas?
¿Puedo recomendar una forma terrible de hacerlo?
Asigne todas sus matrices de la siguiente manera:
void *blockOfMem = malloc(sizeof(mystruct)*n + sizeof(int));
((int *)blockofMem)[0] = n;
mystruct *structs = (mystruct *)(((int *)blockOfMem) + 1);
Entonces siempre puedes convertir tus arrays en int *
y acceder al -1er elemento.
Asegúrese de free
ese puntero, ¡y no el puntero del array en sí!
Además, es probable que esto cause errores terribles que te dejarán rasgado. Quizás puedas envolver los funcs de alloc en llamadas API o algo así.
Algunos compiladores proporcionan msize () o funciones similares (_msize () etc), que le permiten hacer exactamente eso
En uClibc , hay una macro malloc.h
en malloc.h
:
/* The size of a malloc allocation is stored in a size_t word
MALLOC_HEADER_SIZE bytes prior to the start address of the allocation:
+--------+---------+-------------------+
| SIZE |(unused) | allocation ... |
+--------+---------+-------------------+
^ BASE ^ ADDR
^ ADDR - MALLOC_HEADER_SIZE
*/
/* The amount of extra space used by the malloc header. */
#define MALLOC_HEADER_SIZE /
(MALLOC_ALIGNMENT < sizeof (size_t) /
? sizeof (size_t) /
: MALLOC_ALIGNMENT)
/* Set up the malloc header, and return the user address of a malloc block. */
#define MALLOC_SETUP(base, size) /
(MALLOC_SET_SIZE (base, size), (void *)((char *)base + MALLOC_HEADER_SIZE))
/* Set the size of a malloc allocation, given the base address. */
#define MALLOC_SET_SIZE(base, size) (*(size_t *)(base) = (size))
/* Return base-address of a malloc allocation, given the user address. */
#define MALLOC_BASE(addr) ((void *)((char *)addr - MALLOC_HEADER_SIZE))
/* Return the size of a malloc allocation, given the user address. */
#define MALLOC_SIZE(addr) (*(size_t *)MALLOC_BASE(addr))
Esta es una prueba de mi rutina de clasificación. Configura 7 variables para mantener valores flotantes, luego los asigna a una matriz, que se usa para encontrar el valor máximo.
La magia está en la llamada a myMax:
float mmax = myMax ((float *) & arr, (int) sizeof (arr) / sizeof (arr [0]));
Y eso fue mágico, ¿no?
myMax espera un puntero de matriz flotante (float *), por lo que utilizo & arr para obtener la dirección de la matriz y lanzarla como un puntero flotante.
myMax también espera la cantidad de elementos en la matriz como int. Obtengo ese valor usando sizeof () para darme tamaños de bytes de la matriz y el primer elemento de la matriz, luego divido el total de bytes por el número de bytes en cada elemento. (No debemos adivinar o codificar el tamaño de una int porque tiene 2 bytes en algún sistema y 4 en algunos como mi OS X Mac, y podría ser algo más en otros).
NOTA: Todo esto es importante cuando sus datos pueden tener un número variable de muestras.
Aquí está el código de prueba:
#include <stdio.h>
float a, b, c, d, e, f, g;
float myMax(float *apa,int soa){
int i;
float max = apa[0];
for(i=0; i< soa; i++){
if (apa[i]>max){max=apa[i];}
printf("on i=%d val is %0.2f max is %0.2f, soa=%d/n",i,apa[i],max,soa);
}
return max;
}
int main(void)
{
a = 2.0;
b = 1.0;
c = 4.0;
d = 3.0;
e = 7.0;
f = 9.0;
g = 5.0;
float arr[] = {a,b,c,d,e,f,g};
float mmax = myMax((float *)&arr,(int) sizeof(arr)/sizeof(arr[0]));
printf("mmax = %0.2f/n",mmax);
return 0;
}
Malloc devolverá un bloque de memoria al menos tan grande como usted solicitó, pero posiblemente más grande. Entonces, incluso si pudieras consultar el tamaño del bloque, esto no te daría confiadamente el tamaño de tu matriz. Así que solo tendrá que modificar su código para realizar un seguimiento de usted mismo.
No estoy al tanto de una manera, pero me imagino que se ocuparía de rebuscar en las partes internas de Malloc, lo que generalmente es una idea muy, muy mala.
¿Por qué no puede almacenar el tamaño de memoria que asignó?
EDITAR: Si sabe que debe volver a trabajar el código para que sepa n, bueno, hágalo. Sí, podría ser rápido y fácil intentar sondear malloc, pero saber con certeza minimizaría la confusión y fortalecería el diseño.
No, no hay forma de obtener esta información sin depender fuertemente de los detalles de implementación de malloc
. En particular, malloc
puede asignar más bytes de los que usted solicita (por ejemplo, para la eficiencia en una arquitectura de memoria particular). Sería mucho mejor rediseñar su código para que pueda hacer un seguimiento de n
explícitamente. La alternativa es al menos tanto rediseño y un enfoque mucho más peligroso (dado que no es estándar, abusa de la semántica de los indicadores, y será una pesadilla de mantenimiento para los que vienen detrás de ti): almacena la longitud n
en el malloc '' d dirección, seguido de la matriz. La asignación sería entonces:
void *p = calloc(sizeof(struct mystruct) * n + sizeof(unsigned long int),1));
*((unsigned long int*)p) = n;
n
ahora está almacenado en *((unsigned long int*)p)
y el inicio de su matriz es ahora
void *arr = p+sizeof(unsigned long int);
Editar: solo para jugar al abogado del diablo ... sé que estas "soluciones" requieren rediseños, pero demostrémonos. Por supuesto, la solución presentada anteriormente es solo una implementación hacky de una estructura (bien empaquetada). También podrías definir:
typedef struct {
unsigned int n;
void *arr;
} arrInfo;
y pase arrInfo
s en lugar de punteros sin arrInfo
.
Ahora estamos cocinando Pero mientras estés rediseñando, ¿por qué parar aquí? Lo que realmente quieres es un tipo de datos abstractos (ADT). Cualquier texto introductorio para una clase de algoritmos y estructuras de datos lo haría. Un ADT define la interfaz pública de un tipo de datos pero oculta la implementación de ese tipo de datos. Por lo tanto, públicamente un ADT para una matriz podría verse como
typedef void* arrayInfo;
(arrayInfo)newArrayInfo(unsignd int n, unsigned int itemSize);
(void)deleteArrayInfo(arrayInfo);
(unsigned int)arrayLength(arrayInfo);
(void*)arrayPtr(arrayInfo);
...
En otras palabras, una ADT es una forma de encapsulación de datos y comportamiento ... en otras palabras, es lo más cerca posible de la Programación Orientada a Objetos usando la C. Directa. A menos que estés atrapado en una plataforma que no lo hace tener un compilador de C ++, también puedes usar un STL std::vector
.
Ahí, tomamos una pregunta simple sobre C y terminamos en C ++. Dios nos ayuda a todos.
Otros han discutido los límites de los punteros plain c y las implementaciones stdlib.h
de malloc()
. Algunas implementaciones proporcionan extensiones que devuelven el tamaño de bloque asignado que puede ser mayor que el tamaño solicitado.
Si debe tener este comportamiento, puede usar o escribir un asignador de memoria especializado. Lo más simple sería implementar un contenedor alrededor de las funciones stdlib.h
. Algo como:
void* my_malloc(size_t s); /* Calls malloc(s), and if successful stores
(p,s) in a list of handled blocks */
void my_free(void* p); /* Removes list entry and calls free(p) */
size_t my_block_size(void* p); /* Looks up p, and returns the stored size */
...
Para una variedad de punteros puede usar una matriz terminada en NULL. La longitud puede determinarse como si estuviera hecha con cuerdas. En su ejemplo, puede usar un atributo de estructura para marcar y luego finalizar. Por supuesto, eso depende de si hay un miembro que no puede ser NULL. Así que digamos que tiene un nombre de atributo, que debe establecerse para cada estructura en su matriz, luego puede consultar el tamaño de la siguiente manera:
int size;
struct mystruct *cur;
for (cur = myarray; cur->name != NULL; cur++)
;
size = cur - myarray;
Por cierto, debería ser calloc (n, sizeof (struct mystruct)) en tu ejemplo.
Realmente su pregunta es: "¿Puedo averiguar el tamaño de un bloque de datos malloc''d (o calloc''d)". Y como otros han dicho: no, no de una manera estándar.
Sin embargo, hay implementaciones malloc personalizadas que lo hacen, por ejemplo http://dmalloc.com/
Solo para confirmar las respuestas anteriores: no hay forma de saber, solo estudiando un puntero, cuánta memoria fue asignada por un malloc que devolvió este puntero.
¿Qué pasa si funciona?
Un ejemplo de por qué esto no es posible. Imaginemos el código con una función hipotética llamada get_size (void *) que devuelve la memoria asignada para un puntero:
typedef struct MyStructTag
{ /* etc. */ } MyStruct ;
void doSomething(MyStruct * p)
{
/* well... extract the memory allocated? */
size_t i = get_size(p) ;
initializeMyStructArray(p, i) ;
}
void doSomethingElse()
{
MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
doSomething(s) ;
}
¿Por qué, incluso si funcionaba, no funcionaría de todos modos?
Pero el problema de este enfoque es que, en C, puedes jugar con aritmética de puntero. Vamos a reescribir doSomethingElse ():
void doSomethingElse()
{
MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
MyStruct * s2 = s + 5 ; /* s2 points to the 5th item */
doSomething(s2) ; /* Oops */
}
Cómo se supone que get_size funciona, ya que envió a la función un puntero válido, pero no el devuelto por malloc. E incluso si get_size pasó por todos los problemas para encontrar el tamaño (es decir, de forma ineficiente), devolvería, en este caso, un valor que sería incorrecto en su contexto.
Conclusión
Siempre hay formas de evitar este problema, y en C, siempre puedes escribir tu propio asignador, pero nuevamente, es quizás demasiado problema cuando todo lo que necesitas es recordar cuánta memoria se asignó.
Una de las razones por las que no se puede preguntar a la biblioteca malloc qué tan grande es un bloque, es que el asignador generalmente redondeará el tamaño de su solicitud para cumplir con un requisito mínimo de granularidad (por ejemplo, 16 bytes). Entonces, si pide 5 bytes, obtendrá un bloque de tamaño 16 de vuelta. Si tomas 16 y lo divides por 5, obtendrías tres elementos cuando realmente solo asignas uno. Se necesitaría más espacio para que la biblioteca de Malloc realice un seguimiento de la cantidad de bytes que pidió en primer lugar, por lo que es mejor que realice un seguimiento de eso usted mismo.
realice un seguimiento del tamaño de la matriz usted mismo; free usa la cadena malloc para liberar el bloque asignado, que no tiene necesariamente el mismo tamaño que la matriz que solicitaste