Comprobando si un puntero tiene memoria asignada o no

11 minutos de lectura

avatar de usuario
fanático de la codificación

¿Podemos verificar si un puntero pasado a una función está asignado con memoria o no en C?

He escrito mi propia función en C que acepta un puntero de carácter: buf [pointer to a 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.

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

EDITAR1: Parece que no hay una biblioteca estándar para verificarlo… pero ¿hay algún truco sucio para verificarlo?

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

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

  • Realmente no lo creo, pero no me siento lo suficientemente seguro como para publicar como respuesta.

    – Ibrahim

    16 de octubre de 2009 a las 5:48

  • No hay forma de verificar, a menos que use un administrador de memoria o haga un rollo propio.

    –Michael Foukarakis

    16 de octubre de 2009 a las 5:55

  • Si es un puntero de carácter, podemos hacer strlen() o sizeof() y verificar cuánta memoria se asigna (por supuesto, si la cadena termina en NULL). Para otros tipos, no estoy seguro de si hay alguna forma.

    – Sandeep

    18 de febrero de 2014 a las 5:33

  • Sé que esta es una vieja pregunta, pero es posible realizar un seguimiento de la memoria asignada sin usar hacks. Mi código a continuación ofrece algunos fragmentos para que pueda comenzar.

    – c1moore

    25 de abril de 2016 a las 0:30

  • La conclusión que se debe sacar es que Ud. no debería comprobar incluso si era posible. Este artículo explica el problema. Si bien está escrito en términos de Windows, el problema no es específico de Windows.

    – ikegami

    17 de mayo de 2019 a las 0:21

avatar de usuario
GManNickG

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

Los punteros no tienen otra información con ellos que no sea hacia dónde apuntan. Lo mejor que puede hacer es decir “Sé cómo esta versión particular del compilador asigna memoria, así que eliminaré la referencia de la memoria, moveré el puntero hacia atrás 4 bytes, verificaré el tamaño, me aseguraré de que coincida…” y así sucesivamente. No puede hacerlo de manera estándar, ya que la asignación de memoria está definida por la implementación. Sin mencionar que es posible que no lo hayan asignado dinámicamente en absoluto.

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

  • Un puntero podría no ser nulo pero aún no tener asignados bytes buf_siz. No creo que haya realmente ninguna forma de verificar lo que quiere el autor de la pregunta.

    – Ibrahim

    16 de octubre de 2009 a las 5:49

  • Bien, ¿qué tal esto? Dado que esto es C, el cliente probablemente usó malloc que devuelve un NULL puntero si no pudiera asignar memoria. Entonces… en malloc ¿confiamos?

    – jacob

    16 de octubre de 2009 a las 5:56

  • Depende del cliente asegurarse de que malloc funcionó antes de llamar a la función, si eso es lo que está diciendo.

    – GManNickG

    16 de octubre de 2009 a las 5:57

  • @jacob: sé que podemos verificar en malloc … pero si el cliente se olvida de hacer malloc, se produce una falla de segmentación … y quiero evitarlo.

    – fanático de la codificación

    16 de octubre de 2009 a las 6:01

  • Sí. La conclusión final es que su función debe hacer una cosa y solo una cosa. Imagine la sobrecarga si cada función se asegurara de que la memoria a la que accede desde los parámetros fuera válida. Solo haz que tu función haga lo que se supone que debe hacer.

    – GManNickG

    16 de octubre de 2009 a las 6:04

avatar de usuario
Pedro

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ó anteriormente a una función privada, que usa 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 portable a otras versiones de Unix, para Windows no estoy seguro. Tenga en cuenta que la señal SIGSEGV no debe usarse en ningún 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);
}

  • @Saco i = malloc(1); es un código C válido y preferible a i = (int*) malloc(1);. Quizás estés pensando en otro idioma.

    – chux – Reincorporar a Monica

    19/09/2017 a las 23:05


  • Nota bajo POSIX, setjmp() y longjmp() probablemente debería ser reemplazado por sigsetjmp() y siglongjmp(). Ver stackoverflow.com/questions/20755260/…

    –Andrew Henle

    1 de abril de 2019 a las 14:24

  • En mi humilde opinión, no hay garantía de que un acceso de memoria no válido cause un SEGV: su c = *(char *)(x); podría pasar bien, aunque x no apunta a un área asignada. SEGV solo se activa si el puntero apunta dentro de un segmento de memoria que no es accesible, pero los segmentos tienen varios kB de tamaño, por lo tanto, si asigna 4 bytes en10los cambios son esa dirección mem 20 a pesar de estar fuera de un área asignada, todavía está en el mismo segmento que la dirección 10por lo tanto, aunque no esté asignado, podrá acceder a la dirección 20 sin SEGV.

    – Miguel Cerveza

    18 de junio de 2020 a las 12:39


  • Es por eso que siempre debe establecer punteros no utilizados para NULLporque este valor es garantizado para causar un SEGV si intenta desreferenciarlo… No está garantizado para ninguna otra dirección de memoria.

    – Miguel Cerveza

    18 de junio de 2020 a las 12:45

  • @Michael Beer: “no hay garantía de que un acceso a la memoria no válido cause un SEGV”, correcto, pero la verificación sigue siendo válida. Si no hay SEGV entonces puedes acceder a la memoria.

    – Pedro

    19 de enero de 2021 a las 19:32


Para una solución específica de la plataforma, puede estar interesado en la función Win32 IsBadReadPtr (y a otros les gusta). Esta función podrá (casi) predecir si obtendrá una falla de segmentación al leer de un fragmento de memoria en particular.

Sin embargo, esto hace no protegerlo 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 una persona que llama pasa en un búfer que no es tan grande como esperaba, entonces el resto del bloque de montón seguirá siendo legible desde la perspectiva del sistema operativo.

  • @Greg: lamento decir que no estoy muy interesado en las funciones de WIN32 … si es posible, un truco sucio que funcione bien también está bien, ya que NO hay una función C estándar

    – fanático de la codificación

    16 de octubre de 2009 a las 7:32

  • De acuerdo, no especificaste qué plataforma están interesado. Especificar la plataforma y el compilador puede brindarle una respuesta más específica.

    – Greg Hewgill

    16 de octubre de 2009 a las 7:53

  • IsBadXxxPtr realmente debería llamarse CrashProgramRandomly.

    – Alexei Frunze

    13 de abril de 2012 a las 11:02

Siempre inicializo punteros a valor nulo. Por lo tanto, cuando asigne memoria, cambiará. Cuando compruebo si se ha asignado memoria, lo hago pointer != NULL. Cuando desasigno la memoria, también configuro el puntero en nulo. No puedo pensar en ninguna forma de saber si había suficiente memoria asignada.

Esto no resuelve su problema, pero debe confiar en que si alguien escribe programas en C, entonces tiene la habilidad suficiente para hacerlo bien.

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*)0x100000000un puntero en el montón p > (void*)0x100000000 o un puntero en una región mapeada en memoria (intptr_t)p < 0 (mmap devuelve direcciones desde la parte superior del área direccionable). Esto permitió que mi programa mantuviera punteros asignados y mapeados en memoria en el mismo mapa, y que mi módulo de mapa liberara los punteros correctos.

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

avatar de usuario
greg hewgill

No, en general no hay manera 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 para 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 sea un puntero en una parte de un objeto más grande en el montón.

Si su interfaz dice específicamente “pasar 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.

avatar de usuario
c1moore

Sé que esta es una vieja pregunta, pero casi cualquier cosa es posible en C. Ya hay algunas soluciones hackish aquí, 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, reallocy free. Esta es la misma forma en que los marcos de prueba (como cmocka) pueden detectar problemas de memoria (fallas de segmentación, no liberar memoria, etc.). Puede mantener una lista de direcciones de memoria asignadas a medida que se asignan y simplemente verificar esta lista cuando el usuario quiera usar su función. Implementé algo muy similar para mi propio marco de prueba. Algún 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ías funciones similares para calloc, reallocy freecada envoltorio tiene el prefijo __wrap_. El Real malloc 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 itere sobre el enlace memory_ref list y busque la dirección de memoria. Si lo encuentra y es lo suficientemente grande, sabe con certeza que la dirección de memoria no bloqueará su programa; de lo contrario, devuelve un error. En el archivo de encabezado que usa su programa, agregaría 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 puede imaginarse cómo podría extenderse para tener un mejor sistema de seguimiento (por ejemplo, crear un struct que realiza un seguimiento de la ubicación de la memoria además del tamaño). Luego simplemente compilas 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 ser lo peor que he visto. Hay algo de sobrecarga para asignar y liberar memoria, pero siempre hay algo de sobrecarga al agregar seguridad.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad