tutorialspoint pointer ejemplo c++ c pointers null

ejemplo - pointers in c++



0xDEADBEEF vs. NULL (10)

A lo largo de varios códigos, he visto asignación de memoria en compilaciones de depuración con NULL ...

memset(ptr,NULL,size);

O con 0xDEADBEEF ...

memset(ptr,0xDEADBEEF,size);

  1. ¿Cuáles son las ventajas de usar cada uno, y cuál es la forma generalmente preferida de lograr esto en C / C ++?
  2. Si a un puntero se le asignó un valor de 0xDEADBEEF , ¿no podría deferencia a datos válidos?

  1. Usar memset(ptr, NULL, size) o memset(ptr, 0xDEADBEEF, size) es una clara indicación del hecho de que el autor no entendió lo que estaban haciendo.

    En primer lugar, memset(ptr, NULL, size) hecho pondrá a cero un bloque de memoria en C y C ++ si NULL se define como un cero integral.

    Sin embargo, usar NULL para representar el valor cero en este contexto no es una práctica aceptable. NULL es una macro introducida específicamente para contextos de puntero. El segundo parámetro de memset es un número entero, no un puntero. La forma correcta de poner a cero un bloque de memoria sería memset(ptr, 0, size) . Nota: 0 no NULL . Yo diría que incluso memset(ptr, ''/0'', size) ve mejor que memset(ptr, NULL, size) .

    Además, el estándar de C ++ más reciente (actualmente) - C ++ 11 - permite definir NULL como nullptr . nullptr valor nullptr no es implícitamente convertible a tipo int , lo que significa que no se garantiza que el código anterior se compile en C ++ 11 y posterior.

    En el lenguaje C (y su pregunta también está etiquetada C), la macro NULL puede expandirse a (void *) 0 . Incluso en C (void *) 0 no es implícitamente convertible a tipo int , lo que significa que, en general, memset(ptr, NULL, size) es simplemente código inválido en C.

    En segundo lugar, aunque el segundo parámetro de memset tiene tipo int , la función lo interpreta como un valor de unsigned char . Significa que solo se utiliza un byte inferior del valor para llenar el bloque de memoria de destino. Por este motivo, memset(ptr, 0xDEADBEEF, size) se compilará, pero no llenará la región de memoria de destino con valores de 0xDEADBEEF , como el autor del código probablemente esperaba ingenuamente. memset(ptr, 0xDEADBEEF, size) es equivalente a memset(ptr, 0xEF, size) (suponiendo caracteres de 8 bits). Si bien esto es probablemente lo suficientemente bueno como para llenar una región de memoria con "basura" intencional, elementos como memset(ptr, NULL, size) o memset(ptr, 0xDEADBEEF, size) aún traicionan la gran falta de profesionalismo por parte del autor.

    Nuevamente, como ya se ha mencionado otra respuesta, la idea aquí es llenar la memoria no utilizada con un valor de "basura". Cero ciertamente no es una buena idea en este caso, ya que no es suficiente "garbagy". Al usar memset , está limitado a valores de un byte, como 0xAB o 0xEF . Si esto es lo suficientemente bueno para sus propósitos, use memset . Si desea un valor basura más expresivo y único, como 0xDEDABEEF o 0xBAADFOOD , no podrá usar memset con él. Tendrá que escribir una función dedicada que pueda llenar la región de la memoria con un patrón de 4 bytes.

  2. A un puntero en C y C ++ no se le puede asignar un valor entero arbitrario (que no sea una constante de puntero nulo, es decir, cero). Tal asignación solo se puede lograr forzando el valor integral en el puntero con un lanzamiento explícito. Formalmente hablando, el resultado de tal lanzamiento es la implementación definida. El valor resultante ciertamente puede apuntar a datos válidos.



Definitivamente recomendaría 0xDEADBEEF. Identifica claramente las variables no inicializadas y los accesos a punteros no inicializados.

Siendo extraño, la eliminación de referencias a un puntero 0xdeadbeef se bloqueará definitivamente en la arquitectura PowerPC al cargar una palabra, y muy probablemente se bloqueará en otras arquitecturas ya que es probable que la memoria esté fuera del espacio de direcciones del proceso.

Poner a cero la memoria es una ventaja ya que muchas estructuras / clases tienen variables miembro que usan 0 como valor inicial, pero recomiendo inicializar cada miembro en el constructor en lugar de utilizar el relleno de memoria predeterminado. Realmente querrás estar al tanto de si has inicializado tus variables correctamente o no.


Escribir 0xDEADBEEF u otro patrón de bits distinto de cero es una buena idea para poder capturar los usos de escritura después de eliminación y lectura después de eliminación.

1) Escribir después de borrar

Al escribir un patrón específico, puede verificar si un bloque que ya ha sido desasignado fue escrito más tarde por código defectuoso; en nuestro administrador de memoria de depuración utilizamos una lista gratuita de bloques y antes de reciclar un bloque de memoria, verificamos que nuestro patrón personalizado todavía esté escrito en todo el bloque. Por supuesto, es una especie de "retraso" cuando descubrimos el problema, pero aún mucho antes de cuando se descubriera que no se estaría haciendo el control. También tenemos una función especial que se llama de forma periódica y que también se puede invocar bajo demanda que simplemente pasa por la lista de todos los bloques de memoria liberados y verifica su coherencia, por lo que podemos llamar a esta función a menudo cuando persigamos un error. Usar 0x00000000 como valor no sería tan efectivo porque posiblemente cero sea exactamente el valor que el código erróneo quiere escribir en el bloque ya desasignado, por ejemplo, poner a cero un campo o establecer un puntero a NULL (es más improbable que el código con errores quiera escriba 0xDEADBEEF ).

2) Leer después de borrar

Dejar intacto el contenido de un bloque desasignado o incluso escribir solo ceros aumentará la posibilidad de que alguien leyendo el contenido de un bloque de memoria muerto aún encuentre los valores razonables y compatibles con invariantes (por ejemplo, un puntero NULL como en muchas arquitecturas NULL es simplemente binario ceros, o el entero 0, el NUL ASCII NUL o un valor doble 0.0). Al escribir en su lugar patrones "extraños" como 0xDEADBEEF mayor parte del código que accederá en modo de lectura esos bytes probablemente encontrarán valores extraños irrazonables (por ejemplo, el entero -559038737 o un doble con valor -1.1885959257070704e + 148), con suerte desencadenando otra autoconsistencia verificar la afirmación.

Por supuesto, nada es realmente específico del patrón de bits 0xDEADBEEF , en realidad utilizamos patrones diferentes para bloques liberados, área antes del bloque, área posterior al bloque y también nuestro administrador de memoria escribe otro patrón de bits específico (dependiente de la dirección) en la parte del contenido de cualquier bloque de memoria antes de dárselo a la aplicación (esto es para ayudar a encontrar usos de la memoria no inicializada).


Me gustaría ir a NULL porque es mucho más fácil poner a cero la memoria en masa que pasar más tarde y establecer todos los punteros a 0xDEADBEEF. Además, no hay nada que impida que 0xDEADBEEF sea una dirección de memoria válida en x86, es cierto, sería inusual, pero no imposible. NULL es más confiable.

En última instancia, look- NULL es la convención de lenguaje. 0xDEADBEEF solo se ve bonito y eso es todo. No ganas nada por eso. Las bibliotecas verificarán los punteros NULL , no verifican los punteros 0xDEADBEEF. En C ++, la idea del puntero cero ni siquiera está vinculada a un valor cero, simplemente se indica con el cero literal, y en C ++ 0x hay un nullptr y un nullptr_t .


Personalmente, recomendaría usar NULL (o 0x0), ya que representa el NULL como se esperaba y es útil a la hora de comparar. Imagina que estás usando char * y en medio en DEADBEEF por alguna razón (no sé por qué), entonces al menos tu depurador será muy útil para decirte que es 0x0.


Tenga en cuenta que el segundo argumento en memset se supone que es un byte, es decir, se memset implícitamente en un char o similar. 0xDEADBEEF para la mayoría de las plataformas convertir a 0xEF (y algo más para alguna plataforma extraña).

También tenga en cuenta que se supone que el segundo argumento es formalmente un int que NULL no es.

Ahora, para la ventaja de hacer este tipo de inicialización. Primero, por supuesto, el comportamiento sería más determinista (incluso si por esto termina en un comportamiento indefinido, el comportamiento en la práctica sería consistente).

Tener un comportamiento determinista significará que la depuración será más fácil, cuando encuentre un error, "solo" tendrá que proporcionar la misma entrada y la falla se manifestará.

Ahora, cuando selecciona qué valor usaría, debe seleccionar un valor que muy probablemente resultará en un mal comportamiento, lo que significa que el uso de datos no inicializados probablemente resulte en la observación de una falla. Esto significa que debería usar algún conocimiento de la plataforma en cuestión (sin embargo, muchos de ellos se comportan de manera similar).

Si la memoria se utiliza para contener punteros, entonces, habiendo borrado la memoria, significará que obtendrá un puntero NULL y normalmente desreferenciando que dará como resultado un error de segmentación (que se observará como un error). Sin embargo, si lo usa de otra manera, por ejemplo como un tipo aritmético, obtendrá 0 y para muchas aplicaciones que no es ese número impar.

Si en su lugar usa 0xDEADBEEF obtendrá un entero bastante grande, también cuando interprete los datos como coma flotante también será un número bastante grande (IIRC). Si lo interpreta como texto, será muy largo y no contiene caracteres ASCII, y si utiliza la codificación UTF-8, es probable que no sea válido. Ahora, si se utiliza como un puntero en alguna plataforma, fallarían los requisitos de alineación para algunos tipos; también en algunas plataformas esa región de memoria podría estar mapeada de todos modos (tenga en cuenta que en x86_64 el valor del puntero sería 0xDEADBEEFDEADBEEF que está fuera de rango para Una dirección).

Tenga en cuenta que si llena con 0xEF tendrá propiedades similares, si desea llenar la memoria con 0xDEADBEEF , necesitará usar una función personalizada ya que memset no funciona.


Una razón por la que anula el búfer o lo establece en un valor especial es que puede determinar fácilmente si el contenido del búfer es válido o no en el depurador .

La eliminación de un puntero de valor " 0xDEADBEEF " casi siempre es peligroso (probablemente bloquee su programa / sistema) porque en la mayoría de los casos no tiene idea de qué se almacena allí.


Vótame si esto es demasiado de opinión para , pero creo que toda esta discusión es un síntoma de un agujero evidente en la cadena de herramientas que utilizamos para hacer software.

La detección de variables no inicializadas inicializando la memoria con los valores "garabage-y" detecta solo algunos tipos de errores en algunos tipos de datos.

Y la detección de variables no inicializadas en las compilaciones de depuración pero no en las versiones de lanzamiento es como seguir los procedimientos de seguridad solo cuando se prueba una aeronave y se le dice al público volador que se conforme con "bueno, probó bien".

NECESITAMOS SOPORTE DE HARDWARE para detectar variables no inicializadas. Como en algo así como un bit "inválido" que acompaña a todas las entidades de memoria de direccionamiento (= byte en la mayoría de nuestras máquinas) y que es configurado por el SO en cada byte VirtualAlloc () (et al, o equivalentes en otros SO) manos a las aplicaciones y que se borra automáticamente cuando se escribe el byte, pero que causa una excepción si se lee primero.

La memoria es lo suficientemente barata para esto y los procesadores son lo suficientemente rápidos para esto. Este fin de confiar en patrones "divertidos" nos mantiene a todos honestos para comenzar.


http://en.wikipedia.org/wiki/Hexspeak

Estos números "mágicos" son una herramienta de depuración para identificar punteros malos, memoria no inicializada, etc. Desea un valor que no es probable que ocurra durante la ejecución normal y algo que es visible al realizar volcados de memoria o inspeccionar variables. Inicializar a cero es menos útil en este sentido. Supongo que cuando veas que la gente se inicializa a cero es porque necesitan tener ese valor en cero. Un puntero con un valor de 0xDEADBEEF podría apuntar a una ubicación de memoria válida, por lo que es una mala idea usar eso como una alternativa a NULL.