sirven que punteros puntero para los ejemplos declaracion cadenas aritmetica c pointers memory-management misra

que - punteros c++ pdf



¿Por qué MISRA C indica que una copia de los punteros puede causar una excepción de memoria? (8)

De acuerdo con el Estándar, copiando el puntero q = p; , es un comportamiento indefinido.

Lectura J.2 Estados de comportamiento no definidos:

Se usa el valor de un puntero a un objeto cuya vida ha finalizado (6.2.4).

Yendo a ese capítulo vemos que:

6.2.4 Duraciones de almacenamiento de los objetos

La duración de un objeto es la porción de ejecución del programa durante la cual se garantiza que el almacenamiento está reservado para él. Un objeto existe, tiene una dirección constante, 33) y conserva su último valor almacenado a lo largo de su vida útil. 34) Si se hace referencia a un objeto fuera de su duración, el comportamiento no está definido. El valor de un puntero se vuelve indeterminado cuando el objeto al que apunta (o simplemente pasado) alcanza el final de su duración.

Lo que es indeterminado:

3.19.2 valor indeterminado : un valor no especificado o una representación de trampa

La directiva 4.12 de MISRA C 2012 es "No se debe usar la asignación de memoria dinámica".

Como ejemplo, el documento proporciona esta muestra de código:

char *p = (char *) malloc(10); char *q; free(p); q = p; /* Undefined behaviour - value of p is indeterminate */

Y el documento establece que:

Aunque el valor almacenado en el puntero no se modifica después de la llamada para liberar, es posible, en algunos destinos, que la memoria a la que apunta ya no exista y el acto de copiar ese puntero podría causar una excepción de memoria .

Estoy bien con casi todas las frases pero al final. Como p y q están ambos asignados en la pila, ¿cómo puede la copia de los punteros causar una excepción de memoria?


El valor de p no se puede utilizar como tal una vez que se ha liberado la memoria a la que apunta. De manera más general, el valor de un puntero no inicializado tiene el mismo estado: incluso solo lo lee con el propósito de copiar para invocar un comportamiento indefinido.

La razón de esta sorprendente restricción es la posibilidad de representaciones de trampas. Liberar la memoria apuntada por p puede hacer que su valor se convierta en una representación trampa.

Recuerdo uno de esos objetivos, a principios de la década de 1990, que se comportó de esta manera. No es un objetivo incrustado entonces y más bien de uso generalizado, entonces: Windows 2.x. Utilizó la arquitectura Intel en modo protegido de 16 bits, donde los punteros eran de 32 bits de ancho, con un selector de 16 bits y un desplazamiento de 16 bits. Para acceder a la memoria, los punteros se cargaron en un par de registros (un registro de segmento y un registro de dirección) con una instrucción específica:

LES BX,[BP+4] ; load pointer into ES:BX

Cargar la parte del selector del valor del puntero en un registro de segmento tenía el efecto secundario de validar el valor del selector: si el selector no apuntaba a un segmento de memoria válido, se disparaba una excepción.

Compilando la declaración de aspecto inocente q = p; podría compilarse de muchas maneras diferentes:

MOV AX,[BP+4] ; loading via DX:AX registers: no side effects MOV DX,[BP+6] MOV [BP-6],AX MOV [BP-4],DX

o

LES BX,[BP+4] ; loading via ES:BX registers: side effects MOV [BP-6],BX MOV [BP-4],ES

La segunda opción tiene 2 ventajas:

  • El código es más compacto, 1 instrucción menos

  • El valor del puntero se carga en los registros que se pueden usar directamente para eliminar la referencia de la memoria, lo que puede dar como resultado menos instrucciones generadas para declaraciones posteriores.

Liberar la memoria puede desasignar el segmento y hacer que el selector no sea válido. El valor se convierte en un valor de captura y lo carga en ES:BX activa una excepción, también llamada trampa en algunas arquitecturas.

No todos los compiladores usarían la instrucción LES para simplemente copiar los valores del puntero porque era más lento, pero algunos lo hicieron cuando se les ordenó generar un código compacto, una opción común, ya que la memoria era bastante cara y escasa.

El estándar C permite esto y describe una forma de comportamiento indefinido en el que el código:

Se usa el valor de un puntero a un objeto cuya vida ha finalizado (6.2.4).

porque este valor se ha vuelto indeterminado como se define de esta manera:

3.19.2 valor indeterminado: un valor no especificado o una representación de trampa

Sin embargo, tenga en cuenta que aún puede manipular el valor mediante alias a través de un tipo de carácter:

/* dumping the value of the free''d pointer */ unsigned char *pc = (unsigned char*)&p; size_t i; for (i = 0; i < sizeof(p); i++) printf("%02X", pc[i]); /* no problem here */ /* copying the value of the free''d pointer */ memcpy(&q, &p, sizeof(p)); /* no problem either */


Hay dos razones por las que el código que examina un puntero después de liberarlo es problemático incluso si el puntero nunca es desreferenciado:

  1. Los autores de C Standard no quisieron interferir con las implementaciones del lenguaje en plataformas donde los punteros contienen información sobre los bloques de memoria circundantes, y que podrían validar dichos punteros cada vez que se hace algo con ellos, ya sea que se eliminen o no. Si existen tales plataformas, el código que utiliza punteros en violación del Estándar podría no funcionar con ellos.

  2. Algunos compiladores operan bajo la presunción de que un programa nunca recibirá ninguna combinación de insumos que invocaría a UB, y por lo tanto, cualquier combinación de insumos que produciría UB debería presumirse imposible. Como consecuencia de esto, incluso las formas de UB que no tendrían ningún efecto perjudicial en la plataforma objetivo si un compilador simplemente las ignorara, podrían terminar teniendo efectos secundarios arbitrarios e ilimitados.

En mi humilde opinión, no hay ninguna razón para que los operadores de igualdad, relacional o diferencia de puntero en punteros liberados tengan un efecto adverso en cualquier sistema moderno, sino porque está de moda que los compiladores apliquen "optimizaciones" locas, constructos útiles que deberían ser utilizables en plataformas comunes se han vuelto peligrosas.


La mala redacción en el código de muestra te está tirando.

Dice que "el valor de p es indeterminado", pero no es el valor de p el que es indeterminado, porque p sigue teniendo el mismo valor (la dirección de un bloque de memoria que se ha liberado).

La llamada libre (p) no cambia p - p solo se cambia una vez que abandona el alcance en el que se define p.

En cambio, es el valor de lo que p señala que es indeterminado , ya que el bloque de memoria se ha liberado, y también puede ser desasignado por el sistema operativo. Acceder a él mediante p o mediante un puntero alias (q) puede provocar una infracción de acceso.


Mientras que p y q son ambas variables de puntero en la pila, la dirección de memoria devuelta por malloc() no está en la pila.

Una vez que se libera un área de memoria que se ha desbloqueado correctamente, en ese momento no se sabe quién puede estar utilizando el área de memoria o la disposición del área de memoria.

Entonces, una vez que free() se usa para liberar un área de memoria previamente obtenida usando malloc() un intento de usar el área de memoria es un tipo de acción indefinido. Puede tener suerte y funcionará. Puede que tengas mala suerte y no será así. Una vez que free() un área de memoria, ya no eres el propietario, algo más lo hace.

El problema aquí parece ser qué código de máquina está involucrado al copiar un valor de una ubicación de memoria a otra. Recuerde que MISRA se enfoca en el desarrollo de software integrado por lo que la pregunta es siempre qué tipo de procesadores funky están por ahí que hacen algo especial con una copia.

Los estándares MISRA tienen que ver con la solidez, la confiabilidad y la eliminación del riesgo de falla del software. Ellos son bastante exigentes.


Primero, algo de historia ...

Cuando ISO / IEC JTC1 / SC22 / WG14 primero comenzó a formalizar el lenguaje C (para producir lo que ahora es ISO / IEC 9899: 2011) tuvieron un problema.

Muchos proveedores de compiladores han interpretado las cosas de diferentes maneras.

Al principio, tomaron la decisión de no romper ninguna funcionalidad existente ... por lo que cuando las implementaciones del compilador eran divergentes, el estándar ofrece comportamientos undefined unspecified e undefined .

MISRA C intenta atrapar las caídas de pozo que desencadenarán estos comportamientos. Hasta aquí la teoría...

-

Ahora a lo específico de esta pregunta:

Dado que el punto de free () es liberar la memoria dinámica al montón, había tres posibles implementaciones, todas las cuales estaban "en la naturaleza":

  1. restablecer el puntero a NULL
  2. deja el puntero como estaba
  3. destruir el puntero

El estándar no pudo ordenar ninguno de estos, por lo que formalmente deja el comportamiento como undefined : su implementación puede seguir un camino, pero un compilador diferente podría hacer otra cosa ... no puede suponer, y es peligroso confiar en un método.

Personalmente, prefiero que el estándar sea específico y requiera free () para establecer el puntero a NULL, pero esa es solo mi opinión.

-

Entonces el TL; DR; La respuesta es, desafortunadamente: ¡porque lo es!


Un concepto importante para internalizar es el significado de comportamiento "indeterminado" o "indefinido". Es exactamente eso: desconocido e incognoscible. A menudo les decíamos a los alumnos: "Es perfectamente legítimo que su computadora se derrita en una masa amorfa o que el disco se vaya volando a Marte". Mientras leía la documentación original incluida, no vi ningún lugar donde dijera que no usara malloc. Simplemente señala que un programa erróneo fracasará. En realidad, hacer que el programa tome una excepción de memoria es una buena cosa, porque le dice inmediatamente que su programa es defectuoso. Por qué el documento sugiere que esto podría ser una mala cosa se me escapa. Lo que es malo es que en la mayoría de las arquitecturas, NO tomará una excepción de memoria. Seguir utilizando ese puntero producirá valores erróneos, potencialmente inutilizará el montículo y, si ese mismo bloque de almacenamiento se asigna para un uso diferente, corromperá los datos válidos de ese uso o interpretará sus valores como propios. En pocas palabras: ¡no use punteros "obsoletos"! O, para decirlo de otra manera, escribir código defectuoso significa que no funcionará.

Además, el acto de asignar p a q es decididamente NO "indefinido". Los bits almacenados en la variable p, que son absurdos sin sentido, se copian con bastante facilidad y correctamente a q. Todo lo que esto significa ahora es que cualquier valor al que p acceda ahora también se puede acceder por q, y dado que p es un sinsentido indefinido, q ahora no tiene ningún sentido. Entonces, usar cualquiera de ellos para leer o escribir producirá resultados "indefinidos". Si tiene la suerte de estar ejecutando en una arquitectura que puede causar una falla de memoria, detectará fácilmente el uso incorrecto. De lo contrario, usar cualquiera de los dos punteros significa que su programa está defectuoso. Planea pasar muchas horas encontrándolo.


Una vez que libera un objeto a través del puntero, todos los punteros a esa memoria se vuelven indeterminados. (Par) reading memoria indeterminada es un comportamiento indefinido (UB). Lo siguiente es UB:

char *p = malloc(5); free(p); if(p == NULL) // UB: even just reading value of p as here, is UB { }