punteros memoria matrices liberar funciones ejemplos dinamica con c pointers

memoria - punteros en c



Comprobando si un puntero tiene memoria asignada o no (18)

¿Podemos verificar si un puntero pasado a una función se asigna con memoria o no en C?

He escrito mi propia función en C que acepta un puntero de carácter - buf [puntero a un buffer] y tamaño - buf_siz [buffer size]. En realidad, antes de llamar a esta función, el usuario debe crear un búfer y asignarle memoria de buf_siz.

Como existe la posibilidad de que el usuario olvide asignar la memoria y simplemente pase el puntero a mi función, quiero verificar esto. Entonces, ¿hay alguna manera de verificar mi función para ver si el puntero pasado realmente está asignado con la cantidad de memoria buf_siz?

EDIT1: Parece que no hay una biblioteca estándar para comprobarlo ... pero ¿hay algún truco sucio para comprobarlo?

EDIT2: Sé que mi función será utilizada por un buen programador de C ... Pero quiero saber si podemos verificarla o no ... si podemos, me gustaría escucharla.

Conclusión: Por lo tanto, es imposible verificar si un puntero en particular está asignado con memoria o no dentro de una función


Casi nunca hay "nunca" en las computadoras. La plataforma cruzada está por encima de lo anticipado. Después de 25 años, he trabajado en cientos de proyectos que anticipan plataformas cruzadas y nunca se materializaron.

Obviamente, una variable en la pila, apuntaría a un área en la pila, que es casi lineal. Los recolectores de basura de la plataforma cruzada trabajan, marcando la parte superior o inferior de la pila, llamando a una pequeña función para verificar si la pila crece hacia arriba o hacia abajo y luego verificando el puntero de la pila para saber qué tan grande es la pila. Este es tu rango No conozco una máquina que no implemente una pila de esta manera (ya sea creciendo o descendiendo).

Simplemente verifica si la dirección de nuestro objeto o puntero se encuentra entre la parte superior e inferior de la pila. Así es como sabrías si es una variable de pila.

Demasiado simple. Oye, ¿es correcto c ++? No. ¿Es correcto lo importante? En 25 años he visto mucho más estimación de lo correcto. Bueno, pongámoslo de esta manera: si está pirateando, no está haciendo una programación real, probablemente solo está confirmando algo que ya se ha hecho.

¿Qué tan interesante es eso?


Como todos dijeron, no hay una forma estándar de hacerlo.

Hasta el momento, nadie más ha mencionado '' Escribir código sólido '' por Steve Maguire. Aunque criticado en algunos quarters , el libro tiene capítulos sobre el tema de la administración de la memoria y analiza cómo, con cuidado y control completo sobre toda la asignación de memoria en el programa, puedes hacer lo que pidas y determinar si un puntero que se te da es puntero válido a la memoria asignada dinámicamente. Sin embargo, si planea usar bibliotecas de terceros, encontrará que algunas de ellas le permiten cambiar las rutinas de asignación de memoria a las suyas, lo que complica enormemente dicho análisis.


El siguiente código es lo que he usado una vez para verificar si algún puntero intenta acceder a la memoria ilegal. El mecanismo es inducir un SIGSEGV. La señal SEGV se redirigió a una función privada anteriormente, que utiliza longjmp para volver al programa. Es una especie de truco, pero funciona.

El código se puede mejorar (use ''sigaction'' en lugar de ''signal'', etc.), pero es solo para dar una idea. También es portátil para otras versiones de Unix, para Windows no estoy seguro. Tenga en cuenta que la señal SIGSEGV no debe usarse en otro lugar de su programa.

#include <stdio.h> #include <stdlib.h> #include <setjmp.h> #include <signal.h> jmp_buf jump; void segv (int sig) { longjmp (jump, 1); } int memcheck (void *x) { volatile char c; int illegal = 0; signal (SIGSEGV, segv); if (!setjmp (jump)) c = *(char *) (x); else illegal = 1; signal (SIGSEGV, SIG_DFL); return (illegal); } int main (int argc, char *argv[]) { int *i, *j; i = malloc (1); if (memcheck (i)) printf ("i points to illegal memory/n"); if (memcheck (j)) printf ("j points to illegal memory/n"); free (i); return (0); }


Hay una manera simple de hacer esto. Cada vez que cree un puntero, escriba un contenedor alrededor de él. Por ejemplo, si su programador usa su biblioteca para crear una estructura.

struct struct_type struct_var;

asegúrese de que asigna memoria usando su función como

struct struct_type struct_var = init_struct_type()

si este struct_var contiene memoria que está asignada dinámicamente, por ejemplo,

si la definición de struct_type era

typedef struct struct_type { char *string; }struct_type;

luego en tu función init_struct_type (), haz esto,

init_struct_type() { struct struct_type *temp = (struct struct_type*)malloc(sizeof(struct_type)); temp->string = NULL; return temp; }

De esta forma, a menos que asigne la temp-> cadena a un valor, permanecerá NULO. Puede verificar las funciones que usan esta estructura, si la cadena es NULA o no.

Una cosa más, si el programador es tan malo, que no puede usar sus funciones, sino que accede directamente a la memoria no asignada, no merece usar su biblioteca. Solo asegúrese de que su documentación especifique todo.


No puede verificar con nada disponible en el estándar C. Incluso si su compilador específico fuera a proporcionar una función para hacerlo, todavía sería una mala idea. Aquí hay un ejemplo de por qué:

int YourFunc(char * buf, int buf_size); char str[COUNT]; result = YourFunc(str, COUNT);


No puede verificar, excepto algunos hacks específicos de implementación.

Los punteros no tienen información con ellos más allá de donde apuntan. Lo mejor que puede hacer es decir "Sé cómo esta versión particular del compilador asigna memoria, por lo que eliminaré la memoria, moveré el puntero a 4 bytes, veré el tamaño, me aseguraré de que coincida ..." y así sucesivamente. No puede hacerlo de forma estándar, ya que la asignación de memoria está definida por la implementación. Sin mencionar que podrían no haberlo asignado dinámicamente en absoluto.

Solo tiene que suponer que su cliente sabe cómo programar en C. La única solución que no se me ocurre sería asignarle la memoria usted mismo y devolverla, pero ese no es un pequeño cambio. (Es un cambio de diseño más grande)


No sé cómo hacerlo desde una llamada a la biblioteca, pero en Linux, puede ver /proc/<pid>/numa_maps . Mostrará todas las secciones de la memoria y la tercera columna dirá "montón" o "pila". Puede ver el valor del puntero sin procesar para ver dónde se alinea.

Ejemplo:

00400000 prefer:0 file=/usr/bin/bash mapped=163 mapmax=9 N0=3 N1=160 006dc000 prefer:0 file=/usr/bin/bash anon=1 dirty=1 N0=1 006dd000 prefer:0 file=/usr/bin/bash anon=9 dirty=9 N0=3 N1=6 006e6000 prefer:0 anon=6 dirty=6 N0=2 N1=4 01167000 prefer:0 heap anon=122 dirty=122 N0=25 N1=97 7f39904d2000 prefer:0 anon=1 dirty=1 N0=1 7f39904d3000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N0=1 7f39904d4000 prefer:0 file=/usr/lib64/ld-2.17.so anon=1 dirty=1 N1=1 7f39904d5000 prefer:0 anon=1 dirty=1 N0=1 7fffc2d6a000 prefer:0 stack anon=6 dirty=6 N0=3 N1=3 7fffc2dfe000 prefer:0

Por lo tanto, los punteros que están por encima de 0x01167000 pero por debajo de 0x7f39904d2000 se encuentran en el montón.


No, en general no hay forma de hacer esto.

Además, si su interfaz es simplemente "pasar un puntero a un búfer donde pondré cosas", entonces la persona que llama puede elegir no asignar memoria en absoluto, y en su lugar usar un búfer de tamaño fijo que está asignado estáticamente o una variable automática o algo así. O tal vez es un puntero en una parte de un objeto más grande en el montón.

Si su interfaz dice específicamente "pase un puntero a la memoria asignada (porque voy a desasignarlo)", entonces debe esperar que la persona que llama lo haga. No hacerlo no es algo que pueda detectar de manera confiable.


No, no puedes. Notará que no hay funciones en la biblioteca estándar ni en ningún otro lado que lo haga. Eso es porque no hay una forma estándar de decirlo. El código de llamada solo tiene que aceptar la responsabilidad de administrar correctamente la memoria.


Para una solución específica de plataforma, puede interesarle la función Win32 IsBadReadPtr (y otras similares). Esta función podrá (casi) predecir si obtendrá un error de segmentación al leer de un trozo particular de memoria.

Sin embargo, esto no lo protege en el caso general, porque el sistema operativo no sabe nada del administrador de montón de tiempo de ejecución de C, y si un llamante pasa en un búfer que no es tan grande como esperaba, entonces el resto del bloque de montón continuará siendo legible desde una perspectiva del sistema operativo.


Puede llamar a malloc_size(my_ptr) en malloc/malloc.h , devuelve el tamaño que malloc le asignó para su puntero y 0 si no se asignó el puntero. Tenga en cuenta que malloc redimensiona el tamaño de un bloque asignado para garantizar que la variable de tipo más restrictiva pueda ser eliminada de ese puntero y alinear la memoria. Entonces, si llama a malloc (1) (así como a malloc (0)) malloc realmente devuelve 16 bytes (en la mayoría de las máquinas) porque el tipo más restrictivo tiene un tamaño de 16 bytes


Sé que esta es una vieja pregunta, pero casi todo es posible en C. Aquí ya hay algunas soluciones hackish, pero una forma válida de determinar si la memoria se ha asignado correctamente es usar un oráculo para tomar el lugar de malloc , calloc , realloc y free . Esta es la misma forma en que los frameworks de prueba (como cmocka) pueden detectar problemas de memoria (fallas seg, no liberar memoria, etc.). Puede mantener una lista de direcciones de memoria asignadas a medida que se asignan y simplemente verifique esta lista cuando el usuario quiera usar su función. Implementé algo muy similar para mi propio marco de prueba. Un código de ejemplo:

typedef struct memory_ref { void *ptr; int bytes; memory_ref *next; } memory_ref *HEAD = NULL; void *__wrap_malloc(size_t bytes) { if(HEAD == NULL) { HEAD = __real_malloc(sizeof(memory_ref)); } void *tmpPtr = __real_malloc(bytes); memory_ref *previousRef = HEAD; memory_ref *currentRef = HEAD->next; while(current != NULL) { previousRef = currentRef; currentRef = currentRef->next; } memory_ref *newRef = (memory_ref *)__real_malloc(sizeof(memory_ref)); *newRef = (memory_ref){ .ptr = tmpPtr, .bytes = bytes, .next = NULL }; previousRef->next = newRef; return tmpPtr; }

Tendría funciones similares para calloc , realloc y free , cada envoltura con el prefijo __wrap_ . El malloc real está disponible mediante el uso de __real_malloc (similar para las otras funciones que está envolviendo). Siempre que desee verificar si la memoria está realmente asignada, simplemente memory_ref lista vinculada memory_ref y busque la dirección de la memoria. Si lo encuentras y es lo suficientemente grande, sabes con certeza que la dirección de la memoria no bloqueará tu programa; de lo contrario, devuelve un error. En el archivo de encabezado que usa su programa, debe agregar estas líneas:

extern void *__real_malloc (size_t); extern void *__wrap_malloc (size_t); extern void *__real_realloc (size_t); extern void *__wrap_realloc (size_t); // Declare all the other functions that will be wrapped...

Mis necesidades eran bastante simples, así que implementé una implementación muy básica, pero se puede imaginar cómo podría extenderse para tener un mejor sistema de seguimiento (por ejemplo, crear una struct que struct un seguimiento de la ubicación de la memoria además del tamaño). Entonces simplemente compila el código con

gcc src_files -o dest_file -Wl,-wrap,malloc -Wl,-wrap,calloc -Wl,-wrap,realloc -Wl,-wrap,free

La desventaja es que el usuario tiene que compilar su código fuente con las directivas anteriores; sin embargo, está lejos de lo peor que he visto. Hay cierta sobrecarga para asignar y liberar memoria, pero siempre hay algo de sobrecarga al agregar seguridad.


Siempre inicializo punteros a valores nulos. Por lo tanto, cuando asigno la memoria, cambiará. Cuando compruebo si la memoria ha sido asignada, ¡hago un pointer != NULL . Cuando desalojo la memoria, también configuré el puntero como nulo. No se me ocurre ninguna forma de saber si se asignó suficiente memoria.

Esto no resuelve su problema, pero debe confiar en que si alguien escribe los programas C, entonces es lo suficientemente hábil para hacerlo bien.


Un puntero no inicializado es exactamente eso, sin inicializar. Puede señalar cualquier cosa o simplemente ser una dirección no válida (es decir, una que no está asignada a la memoria física o virtual).

Una solución práctica es tener una firma de validez en los objetos apuntados. Cree un contenedor malloc () que asigne el tamaño de bloque solicitado más el tamaño de una estructura de firma, cree una estructura de firma al principio del bloque pero devuelva el puntero a la ubicación después de la firma. A continuación, puede crear una función de validación que tome el puntero, utilice un desplazamiento negativo para obtener la estructura de validez y la verifique. Por supuesto, necesitará una envoltura libre () correspondiente para invalidar el bloque sobrescribiendo la firma de validez y para realizar la operación gratuita desde el inicio verdadero del bloque asignado.

Como una estructura de validez, puede usar el tamaño del bloque y su complemento. De esta forma, no solo tendrá una forma de validar el bloque (XOR los dos valores y lo comparará con cero), sino que también tendrá información sobre el tamaño del bloque.


Un rastreador de puntero, rastrea y comprueba la validez de un puntero

uso:

crear memoria int * ptr = malloc (sizeof (int) * 10);

agregue la dirección del puntero al rastreador Ptr (& ptr);

comprobar si hay punteros defectuosos PtrCheck ();

y libera todos los rastreadores al final de tu código

PtrFree ();

#include <stdlib.h> #include <string.h> #include <stdio.h> #include <stdint.h> #include <stdbool.h> struct my_ptr_t { void ** ptr; size_t mem; struct my_ptr_t *next, *previous; }; static struct my_ptr_t * ptr = NULL; void Ptr(void * p){ struct my_ptr_t * tmp = (struct my_ptr_t*) malloc(sizeof(struct my_ptr_t)); printf("/t/tcreating Ptr tracker:"); if(ptr){ ptr->next = tmp; } tmp->previous = ptr; ptr = tmp; ptr->ptr = p; ptr->mem = **(size_t**) ptr->ptr; ptr->next = NULL; printf("%I64x/n", ptr); }; void PtrFree(void){ if(!ptr){ return; } /* if ptr->previous == NULL */ if(!ptr->previous){ if(*ptr->ptr){ free(ptr->ptr); ptr->ptr = NULL; } free(ptr); ptr = NULL; return; } struct my_ptr_t * tmp = ptr; for(;tmp != NULL; tmp = tmp->previous ){ if(*tmp->ptr){ if(**(size_t**)tmp->ptr == tmp->mem){ free(*tmp->ptr); *tmp->ptr = NULL; } } free(tmp); } return; }; void PtrCheck(void){ if(!ptr){ return; } if(!ptr->previous){ if(*(size_t*)ptr->ptr){ if(*ptr->ptr){ if(**(size_t**) ptr->ptr != ptr->mem){ printf("/tpointer %I64x points not to a valid memory address", ptr->mem); printf(" did you freed the memory and not NULL''ed the pointer or used arthmetric''s on pointer %I64x?/n", *ptr->ptr); return; } } return; } return; } struct my_ptr_t * tmp = ptr; for(;tmp->previous != NULL; tmp = tmp->previous){ if(*(size_t*)tmp->ptr){ if(*tmp->ptr){ if(**(size_t**) tmp->ptr != tmp->mem){ printf("/tpointer %I64x points not to a valid memory address", tmp->mem); printf(" did you freed the memory and not NULL''ed the pointer or used arthmetric''s on pointer %I64x?/n", *tmp->ptr); continue; } } continue; } } return; }; int main(void){ printf("/n/n/t *************** Test ******************** /n/n"); size_t i = 0; printf("/t *************** create tracker ********************/n"); int * ptr = malloc(sizeof(int) * 10); Ptr(&ptr); printf("/t *************** check tracker ********************/n"); PtrCheck(); printf("/t *************** free pointer ********************/n"); free(ptr); printf("/t *************** check tracker ********************/n"); PtrCheck(); printf("/t *************** set pointer NULL *******************/n"); ptr = NULL; printf("/t *************** check tracker ********************/n"); PtrCheck(); printf("/t *************** free tracker ********************/n"); PtrFree(); printf("/n/n/t *************** single check done *********** /n/n"); printf("/n/n/t *************** start multiple test *********** /n"); int * ptrs[10]; printf("/t *************** create trackers ********************/n"); for(; i < 10; i++){ ptrs[i] = malloc(sizeof(int) * 10 * i); Ptr(&ptrs[i]); } printf("/t *************** check trackers ********************/n"); PtrCheck(); printf("/t *************** free pointers but set not NULL *****/n"); for(i--; i > 0; i-- ){ free(ptrs[i]); } printf("/t *************** check trackers ********************/n"); PtrCheck(); printf("/t *************** set pointers NULL *****************/n"); for(i=0; i < 10; i++){ ptrs[i] = NULL; } printf("/t *************** check trackers ********************/n"); PtrCheck(); printf("/t *************** free trackers ********************/n"); PtrFree(); printf("/tdone"); return 0; }


Un truco que puedes probar es verificar si tu puntero apunta a apilar la memoria asignada. Esto no le ayudará en general, ya que el búfer asignado puede ser pequeño o el puntero apunta a alguna sección de memoria global (.bss, .const, ...).

Para realizar este truco, primero almacena la dirección de la primera variable en main (). Más tarde, puede comparar esta dirección con la dirección de una variable local en su rutina específica. Todas las direcciones entre ambas direcciones se encuentran en la pila.


Una vez usé un truco sucio en mi Solaris de 64 bits. En el modo de 64 bits, el montón comienza en 0x1 0000 0000. Al comparar el puntero, pude determinar si era un puntero en el segmento de datos o código p < (void*)0x100000000 , un puntero en el montón p > (void*)0x100000000 o un puntero en una región asignada de memoria (intptr_t)p < 0 (mmap devuelve las direcciones desde la parte superior del área direccionable). Esto permitió en mi programa mantener punteros asignados y mapeados en memoria en el mismo mapa, y hacer que mi módulo de mapas libere los punteros correctos.

Pero este tipo de truco es muy poco práctico y si su código se basa en algo así, es hora de reconsiderar la arquitectura de su código. Probablemente estás haciendo algo mal.


en general, los usuarios de lib son responsables de la comprobación y verificación de entrada. Puede ver ASSERT o algo en el código de lib y se usan solo para depuración. es una forma estándar al escribir C / C ++. mientras que a tantos codificadores les gusta hacer tal comprobación y verificar en su código de lib con mucho cuidado. realmente "MALOS" hábitos. Como se indica en IOP / IOD, las interfaces lib deben ser los contratos y dejar en claro qué hará o no la lib y qué debe hacer un usuario de lib y qué no será necesario.