¿Hay alguna forma de determinar (programáticamente, por supuesto) si un puntero dado es "válido"? Verificar NULL es fácil, pero ¿qué pasa con cosas como 0x00001234? Cuando se intenta eliminar la referencia a este tipo de puntero, se produce una excepción / bloqueo.
Se prefiere un método multiplataforma, pero la plataforma específica (para Windows y Linux) también está bien.
Actualización para aclarar: el problema no es con punteros obsoletos / liberados / no inicializados; en su lugar, estoy implementando una API que toma punteros de la persona que llama (como un puntero a una cadena, un identificador de archivo, etc.). La persona que llama puede enviar (a propósito o por error) un valor no válido como puntero. ¿Cómo evito un accidente?
Respuestas:
No puedes hacer ese cheque. Simplemente no hay forma de comprobar si un puntero es "válido". Tienes que confiar en que cuando las personas usan una función que toma un puntero, esas personas saben lo que están haciendo. Si le pasan 0x4211 como valor de puntero, entonces debe confiar en que apunta a la dirección 0x4211. Y si chocan "accidentalmente" con un objeto, incluso si usaras alguna función aterradora del sistema operativo (IsValidPtr o lo que sea), seguirías cometiendo un error y no fallarías rápidamente.
Comience a usar punteros nulos para señalar este tipo de cosas y dígale al usuario de su biblioteca que no deben usar punteros si tienden a pasar accidentalmente punteros inválidos, en serio :)
fuente
Aquí hay tres formas sencillas para que un programa en C bajo Linux se vuelva introspectivo sobre el estado de la memoria en la que se está ejecutando y por qué la pregunta tiene respuestas sofisticadas apropiadas en algunos contextos.
En Microsoft Windows existe la función QueryWorkingSetEx que está documentada en la API de estado del proceso (también en la API de NUMA). Como corolario de la programación sofisticada de la API de NUMA, esta función también le permitirá realizar un trabajo simple de "punteros de prueba para la validez (C / C ++)", por lo que es poco probable que esté obsoleto durante al menos 15 años.
fuente
Evitar un bloqueo causado por la persona que llama enviando un puntero no válido es una buena manera de crear errores silenciosos que son difíciles de encontrar.
¿No es mejor para el programador que usa su API obtener un mensaje claro de que su código es falso al bloquearlo en lugar de ocultarlo?
fuente
En Win32 / 64 hay una forma de hacer esto. Intente leer el puntero y detectar la excepción SEH resultante que se lanzará en caso de error. Si no lanza, entonces es un puntero válido.
Sin embargo, el problema con este método es que solo devuelve si puede leer o no los datos del puntero. No ofrece ninguna garantía sobre la seguridad del tipo o cualquier otro invariante. En general, este método sirve para poco más que decir "sí, puedo leer ese lugar en particular en la memoria en un momento que ya pasó".
En resumen, no hagas esto;)
Raymond Chen tiene una publicación de blog sobre este tema: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
fuente
AFAIK no hay manera. Debe intentar evitar esta situación estableciendo siempre los punteros en NULL después de liberar memoria.
fuente
Eche un vistazo a esta y esta pregunta. También eche un vistazo a los punteros inteligentes .
fuente
Con respecto a la respuesta un poco arriba en este hilo:
Mi consejo es mantenerse alejado de ellos, alguien ya ha publicado este: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
Otra publicación sobre el mismo tema y del mismo autor (creo) es esta: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr realmente debería llamarse CrashProgramRandomly ").
Si los usuarios de su API envían datos incorrectos, deje que se bloquee. Si el problema es que los datos pasados no se utilizan hasta más tarde (y eso hace que sea más difícil encontrar la causa), agregue un modo de depuración en el que las cadenas, etc., se registren en la entrada. Si son malos, será obvio (y probablemente fallará). Si está sucediendo con demasiada frecuencia, podría valer la pena sacar su API del proceso y dejar que bloqueen el proceso de la API en lugar del proceso principal.
fuente
En primer lugar, no veo ningún sentido en tratar de protegerse de la persona que llama que intenta deliberadamente provocar un accidente. Podrían hacer esto fácilmente intentando acceder a través de un puntero inválido ellos mismos. Hay muchas otras formas: podrían simplemente sobrescribir su memoria o la pila. Si necesita protegerse contra este tipo de cosas, debe ejecutar un proceso separado utilizando sockets o algún otro IPC para la comunicación.
Escribimos una gran cantidad de software que permite a los socios / clientes / usuarios ampliar la funcionalidad. Inevitablemente, se nos informa primero de cualquier error, por lo que es útil poder mostrar fácilmente que el problema está en el código del complemento. Además, existen problemas de seguridad y algunos usuarios son más confiables que otros.
Utilizamos varios métodos diferentes según los requisitos de rendimiento / rendimiento y la confiabilidad. De los más preferidos:
procesos separados usando sockets (a menudo pasando datos como texto).
Procesos separados usando memoria compartida (si se pasan grandes cantidades de datos).
El mismo proceso separa hilos a través de la cola de mensajes (si son mensajes cortos frecuentes).
El mismo proceso separa los hilos de todos los datos pasados asignados desde un grupo de memoria.
mismo proceso a través de una llamada a procedimiento directo: todos los datos pasados asignados desde un grupo de memoria.
Tratamos de no recurrir nunca a lo que está tratando de hacer cuando se trata de software de terceros, especialmente cuando nos dan los complementos / biblioteca como código binario en lugar de código fuente.
El uso de un grupo de memoria es bastante fácil en la mayoría de las circunstancias y no tiene por qué ser ineficaz. Si USTED asigna los datos en primer lugar, entonces es trivial comparar los indicadores con los valores que asignó. También puede almacenar la longitud asignada y agregar valores "mágicos" antes y después de los datos para verificar el tipo de datos válido y los desbordamientos de datos.
fuente
En Unix, debería poder utilizar una llamada al sistema del kernel que compruebe el puntero y devuelva EFAULT, como por ejemplo:
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <stdbool.h> bool isPointerBad( void * p ) { int fh = open( p, 0, 0 ); int e = errno; if ( -1 == fh && e == EFAULT ) { printf( "bad pointer: %p\n", p ); return true; } else if ( fh != -1 ) { close( fh ); } printf( "good pointer: %p\n", p ); return false; } int main() { int good = 4; isPointerBad( (void *)3 ); isPointerBad( &good ); isPointerBad( "/tmp/blah" ); return 0; }
regresando:
bad pointer: 0x3 good pointer: 0x7fff375fd49c good pointer: 0x400793
Probablemente haya una mejor llamada al sistema para usar que open () [quizás acceso], ya que existe la posibilidad de que esto conduzca a una ruta de código de creación de archivos real y un requisito de cierre posterior.
fuente
Siento mucha simpatía por su pregunta, ya que yo mismo estoy en una posición casi idéntica. Aprecio lo que dicen muchas de las respuestas, y son correctas: la rutina que proporciona el puntero debería proporcionar un puntero válido. En mi caso, es casi inconcebible que pudieran haber corrompido el puntero, pero si lo hubieran logrado, sería MI software el que falla y YO el que tendría la culpa :-(
Mi requisito no es que continúe después de una falla de segmentación, eso sería peligroso, solo quiero informarle al cliente lo que sucedió antes de terminar para que pueda corregir su código en lugar de culparme.
Así es como encontré hacerlo (en Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/
Para dar una sinopsis:
#include <signal.h> using namespace std; void terminate(int param) /// Function executed if a segmentation fault is encountered during the cast to an instance. { cerr << "\nThe function received a corrupted reference - please check the user-supplied dll.\n"; cerr << "Terminating program...\n"; exit(1); } ... void MyFunction() { void (*previous_sigsegv_function)(int); previous_sigsegv_function = signal(SIGSEGV, terminate); <-- insert risky stuff here --> signal(SIGSEGV, previous_sigsegv_function); }
Ahora parece que esto se comporta como espero (imprime el mensaje de error y luego finaliza el programa), pero si alguien puede detectar una falla, ¡hágamelo saber!
fuente
exit()
, elude RAII y por lo tanto puede causar fugas de recursos.exit()
y mi campana de alarma portátil C ++ comenzó a sonar. Debería estar bien en esta situación específica de Linux, donde su programa saldría de todos modos, perdón por el ruido.man 2 signal
en Linux tiene un párrafo que explica por qué.No hay disposiciones en C ++ para probar la validez de un puntero como caso general. Obviamente, se puede suponer que NULL (0x00000000) es malo, y a varios compiladores y bibliotecas les gusta usar "valores especiales" aquí y allá para facilitar la depuración (por ejemplo, si alguna vez veo que un puntero aparece como 0xCECECECE en Visual Studio, sé Hice algo mal) pero la verdad es que dado que un puntero es solo un índice en la memoria, es casi imposible saber con solo mirar el puntero si es el índice "correcto".
Hay varios trucos que puede hacer con dynamic_cast y RTTI para asegurarse de que el objeto al que se apunta sea del tipo que desea, pero todos requieren que apunte a algo válido en primer lugar.
Si desea asegurarse de que su programa puede detectar punteros "no válidos", mi consejo es el siguiente: configure cada puntero que declare en NULL o en una dirección válida inmediatamente después de la creación y configúrelo en NULL inmediatamente después de liberar la memoria a la que apunta. Si es diligente con esta práctica, entonces todo lo que necesita es verificar NULL.
fuente
No hay ninguna forma portátil de hacer esto, y hacerlo para plataformas específicas puede ser entre difícil e imposible. En cualquier caso, nunca debe escribir código que dependa de dicha verificación; no permita que los punteros adopten valores no válidos en primer lugar.
fuente
Establecer el puntero en NULL antes y después de usarlo es una buena técnica. Esto es fácil de hacer en C ++ si administra punteros dentro de una clase, por ejemplo (una cadena):
class SomeClass { public: SomeClass(); ~SomeClass(); void SetText( const char *text); char *GetText() const { return MyText; } void Clear(); private: char * MyText; }; SomeClass::SomeClass() { MyText = NULL; } SomeClass::~SomeClass() { Clear(); } void SomeClass::Clear() { if (MyText) free( MyText); MyText = NULL; } void SomeClass::Settext( const char *text) { Clear(); MyText = malloc( strlen(text)); if (MyText) strcpy( MyText, text); }
fuente
No es una buena política aceptar punteros arbitrarios como parámetros de entrada en una API pública. Es mejor tener tipos de "datos sin formato" como un entero, una cadena o una estructura (me refiero a una estructura clásica con datos sin formato dentro, por supuesto; oficialmente, cualquier cosa puede ser una estructura).
¿Por qué? Bueno, porque, como dicen otros, no existe una forma estándar de saber si le han dado un puntero válido o uno que apunta a basura.
Pero a veces no tienes la opción: tu API debe aceptar un puntero.
En estos casos, es deber de la persona que llama pasar un buen puntero. NULL puede aceptarse como valor, pero no como puntero a basura.
¿Puede verificarlo de alguna manera? Bueno, lo que hice en un caso como ese fue definir un invariante para el tipo al que apunta el puntero y llamarlo cuando lo obtenga (en modo de depuración). Al menos si el invariante falla (o falla), sabrá que se le pasó un valor incorrecto.
// API that does not allow NULL void PublicApiFunction1(Person* in_person) { assert(in_person != NULL); assert(in_person->Invariant()); // Actual code... } // API that allows NULL void PublicApiFunction2(Person* in_person) { assert(in_person == NULL || in_person->Invariant()); // Actual code (must keep in mind that in_person may be NULL) }
fuente
Como han dicho otros, no puede detectar de manera confiable un puntero no válido. Considere algunas de las formas que podría adoptar un puntero no válido:
Podría tener un puntero nulo. Ese es uno que podría verificar fácilmente y hacer algo al respecto.
Podría tener un puntero a algún lugar fuera de la memoria válida. Lo que constituye una memoria válida varía según cómo el entorno de tiempo de ejecución de su sistema configure el espacio de direcciones. En los sistemas Unix, generalmente es un espacio de direcciones virtuales que comienza en 0 y llega a una gran cantidad de megabytes. En los sistemas integrados, podría ser bastante pequeño. En cualquier caso, es posible que no comience en 0. Si su aplicación se ejecuta en modo supervisor o equivalente, entonces su puntero puede hacer referencia a una dirección real, que puede o no tener una copia de seguridad con memoria real.
Podría tener un puntero a algún lugar dentro de su memoria válida, incluso dentro de su segmento de datos, bss, pila o montón, pero sin apuntar a un objeto válido. Una variante de esto es un puntero que solía apuntar a un objeto válido, antes de que le sucediera algo malo. Las cosas malas en este contexto incluyen la desasignación, la corrupción de la memoria o la corrupción del puntero.
Podría tener un puntero ilegal plano, como un puntero con alineación ilegal para la cosa a la que se hace referencia.
El problema empeora aún más cuando se consideran arquitecturas basadas en segmentos / desplazamientos y otras implementaciones de punteros extraños. Este tipo de cosas normalmente se ocultan al desarrollador por los buenos compiladores y el uso juicioso de los tipos, pero si quieres traspasar el velo e intentar burlar a los desarrolladores del sistema operativo y del compilador, bueno, puedes, pero no hay una forma genérica. para hacerlo que manejará todos los problemas que pueda encontrar.
Lo mejor que puede hacer es permitir el bloqueo y publicar buena información de diagnóstico.
fuente
Este artículo MEM10-C. Definir y usar una función de validación de puntero dice que es posible hacer una verificación hasta cierto punto, especialmente en el sistema operativo Linux.
fuente
En general, es imposible de hacer. Aquí hay un caso particularmente desagradable:
struct Point2d { int x; int y; }; struct Point3d { int x; int y; int z; }; void dump(Point3 *p) { printf("[%d %d %d]\n", p->x, p->y, p->z); } Point2d points[2] = { {0, 1}, {2, 3} }; Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]); dump(p3);
En muchas plataformas, esto se imprimirá:
[0 1 2]
Estás obligando al sistema de tiempo de ejecución a interpretar incorrectamente los bits de memoria, pero en este caso no se bloqueará, porque todos los bits tienen sentido. Esto es parte del diseño de la lengua (ver polimorfismo C-estilo con
struct inaddr
,inaddr_in
,inaddr_in6
), por lo que no puede proteger de forma fiable contra ella en cualquier plataforma.fuente
Es increíble la cantidad de información engañosa que puede leer en los artículos anteriores ...
E incluso en la documentación de microsoft msdn, se afirma que IsBadPtr está prohibido. Bueno, prefiero la aplicación en funcionamiento en lugar de fallar. Incluso si el trabajo temporal puede estar funcionando incorrectamente (siempre que el usuario final pueda continuar con la aplicación).
Al buscar en Google, no encontré ningún ejemplo útil para Windows: encontré una solución para aplicaciones de 32 bits,
http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver%2Fsrc%t2FDetectDriver%2Fsrcpp%t2FDbD88 = 2
pero también necesito admitir aplicaciones de 64 bits, por lo que esta solución no funcionó para mí.
Pero he cosechado los códigos fuente de Wine y me las arreglé para cocinar un tipo de código similar que también funcionaría para aplicaciones de 64 bits, adjuntando el código aquí:
#include <typeinfo.h> typedef void (*v_table_ptr)(); typedef struct _cpp_object { v_table_ptr* vtable; } cpp_object; #ifndef _WIN64 typedef struct _rtti_object_locator { unsigned int signature; int base_class_offset; unsigned int flags; const type_info *type_descriptor; //const rtti_object_hierarchy *type_hierarchy; } rtti_object_locator; #else typedef struct { unsigned int signature; int base_class_offset; unsigned int flags; unsigned int type_descriptor; unsigned int type_hierarchy; unsigned int object_locator; } rtti_object_locator; #endif /* Get type info from an object (internal) */ static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr) { cpp_object* cppobj = (cpp_object*) inptr; const rtti_object_locator* obj_locator = 0; if (!IsBadReadPtr(cppobj, sizeof(void*)) && !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) && !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator))) { obj_locator = (rtti_object_locator*) cppobj->vtable[-1]; } return obj_locator; }
Y el siguiente código puede detectar si el puntero es válido o no, probablemente necesite agregar alguna verificación NULL:
CTest* t = new CTest(); //t = (CTest*) 0; //t = (CTest*) 0x12345678; const rtti_object_locator* ptr = RTTI_GetObjectLocator(t); #ifdef _WIN64 char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator; const type_info *td = (const type_info*)(base + ptr->type_descriptor); #else const type_info *td = ptr->type_descriptor; #endif const char* n =td->name();
Esto obtiene el nombre de la clase del puntero; creo que debería ser suficiente para sus necesidades.
Una cosa que todavía me temo es que el rendimiento de la comprobación del puntero (en el fragmento de código anterior ya se están realizando 3-4 llamadas a la API) podría ser excesivo para las aplicaciones de tiempo crítico.
Sería bueno si alguien pudiera medir la sobrecarga de la comprobación del puntero en comparación, por ejemplo, con las llamadas C # / administradas en C ++.
fuente
De hecho, se podría hacer algo en una ocasión específica: por ejemplo, si desea verificar si una cadena de puntero de cadena es válida, usar write (fd, buf, szie) syscall puede ayudarlo a hacer la magia: deje que fd sea un descriptor de archivo de temporal archivo que crea para la prueba, y buf apuntando a la cadena que está probando, si el puntero no es válido, write () devolvería -1 y errno establecido en EFAULT, lo que indica que buf está fuera de su espacio de direcciones accesible.
fuente
Lo siguiente funciona en Windows (alguien lo sugirió antes):
static void copy(void * target, const void* source, int size) { __try { CopyMemory(target, source, size); } __except(EXCEPTION_EXECUTE_HANDLER) { doSomething(--whatever--); } }
La función tiene que ser un método estático, independiente o estático de alguna clase. Para probar en solo lectura, copie los datos en el búfer local. Para probar la escritura sin modificar los contenidos, escríbalos. Solo puede probar la primera / última dirección. Si el puntero no es válido, el control se pasará a 'hacer algo' y luego fuera de los corchetes. Simplemente no use nada que requiera destructores, como CString.
fuente
En Windows uso este código:
void * G_pPointer = NULL; const char * G_szPointerName = NULL; void CheckPointerIternal() { char cTest = *((char *)G_pPointer); } bool CheckPointerIternalExt() { bool bRet = false; __try { CheckPointerIternal(); bRet = true; } __except (EXCEPTION_EXECUTE_HANDLER) { } return bRet; } void CheckPointer(void * A_pPointer, const char * A_szPointerName) { G_pPointer = A_pPointer; G_szPointerName = A_szPointerName; if (!CheckPointerIternalExt()) throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!"); }
Uso:
unsigned long * pTest = (unsigned long *) 0x12345; CheckPointer(pTest, "pTest"); //throws exception
fuente
IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () para Windows.
Estos toman un tiempo proporcional a la longitud del bloque, por lo que para verificar la cordura, simplemente verifico la dirección de inicio.
fuente
He visto que varias bibliotecas usan algún método para verificar la memoria sin referencia y tal. Creo que simplemente "anulan" los métodos de asignación de memoria y desasignación (malloc / free), que tiene alguna lógica que realiza un seguimiento de los punteros. Supongo que esto es excesivo para su caso de uso, pero sería una forma de hacerlo.
fuente
Técnicamente, puede anular el operador nuevo (y eliminar ) y recopilar información sobre toda la memoria asignada, por lo que puede tener un método para verificar si la memoria del montón es válida. pero:
todavía necesita una forma de verificar si el puntero está asignado en la pila ()
deberá definir qué es el puntero 'válido':
a) se asigna memoria en esa dirección
b) la memoria en esa dirección es la dirección de inicio del objeto (por ejemplo, la dirección no está en el medio de una gran matriz)
c) la memoria en esa dirección es la dirección de inicio del objeto del tipo esperado
En pocas palabras: el enfoque en cuestión no es C ++, necesita definir algunas reglas que aseguren que la función reciba punteros válidos.
fuente
No hay forma de realizar esa verificación en C ++. ¿Qué debe hacer si otro código le pasa un puntero no válido? Deberías estrellarte. ¿Por qué? Consulte este enlace: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
fuente
Anexo a la respuesta o respuestas aceptadas:
Suponga que su puntero solo puede contener tres valores: 0, 1 y -1 donde 1 significa un puntero válido, -1 uno no válido y 0 otro no válido. ¿Cuál es la probabilidad de que su puntero sea NULL, si todos los valores son igualmente probables? 1/3. Ahora, elimine el caso válido, de modo que para cada caso no válido, tenga una proporción de 50:50 para detectar todos los errores. Se ve bien, ¿verdad? Escale esto para un puntero de 4 bytes. Hay 2 ^ 32 o 4294967294 valores posibles. De estos, solo UN valor es correcto, uno es NULL y todavía le quedan 4294967292 otros casos no válidos. Recalcular: tiene una prueba para 1 de (4294967292+ 1) casos no válidos. Una probabilidad de 2.xe-10 o 0 para la mayoría de los propósitos prácticos. Tal es la inutilidad de la verificación NULL.
fuente
Ya sabes, un nuevo controlador (al menos en Linux) que sea capaz de esto probablemente no sería tan difícil de escribir.
Por otro lado, sería una locura construir sus programas de esta manera. A menos que tenga un uso realmente específico y único para tal cosa, no lo recomendaría. Si creó una aplicación grande cargada con constantes comprobaciones de validez de puntero, probablemente sería terriblemente lenta.
fuente
Si no funcionan, ¿la próxima actualización de Windows lo solucionará? Si no funcionan a nivel de concepto, la función probablemente se eliminará de la API de Windows por completo.
La documentación de MSDN afirma que están prohibidos, y la razón de esto probablemente sea una falla en el diseño posterior de la aplicación (por ejemplo, generalmente no debe comer punteros no válidos en silencio, si está a cargo del diseño de toda la aplicación, por supuesto) y el rendimiento / tiempo de comprobación de puntero.
Pero no debes afirmar que no funcionan por culpa de algún blog. En mi aplicación de prueba he verificado que funcionan.
fuente
estos enlaces pueden ser útiles
_CrtIsValidPointer Verifica que un rango de memoria especificado sea válido para lectura y escritura (solo versión de depuración). http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx
_CrtCheckMemory Confirma la integridad de los bloques de memoria asignados en el montón de depuración (solo versión de depuración). http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx
fuente