una tener longitud descripcion debe cuantos caracteres c pointers language-lawyer

tener - ¿Comprueba C si un puntero está fuera de límite sin haber desreferenciado el puntero?



cuantos caracteres debe tener una meta descripcion (9)

"Comportamiento indefinido" significa que "cualquier cosa puede suceder". Los valores comunes de "cualquier cosa" son "nada malo sucede en absoluto" y "su código falla". Otros valores comunes de "cualquier cosa" son "ocurren cosas malas cuando se activa la optimización", o "ocurren cosas malas cuando no se ejecuta el código en desarrollo pero un cliente lo está ejecutando", y aún otros valores son "su código". hace algo inesperado "y" su código hace algo que no debería poder hacer ".

Entonces, si dices "suena ilógico que C verifique si un puntero está fuera de límite sin que se use el puntero", estás en territorio muy, muy, muy peligroso. Toma este código:

int a = 0; int b [2] = { 1, 2 }; int* p = &a; p - 1; printf ("%d/n", *p);

El compilador puede asumir que no hay un comportamiento indefinido. p - 1 fue evaluado. El compilador concluye (legalmente) que o bien p = & a [1], p = & b [1] o p = & b [2], ya que en todos los demás casos hay un comportamiento indefinido al evaluar p o al evaluar p-1. El compilador asume entonces que * p no es un comportamiento indefinido, por lo que concluye (legalmente) que p = & b [1] e imprime el valor 2. No esperaba eso, ¿verdad?

Eso es legal, y sucede . Entonces la lección es: NO invocar un comportamiento indefinido.

Tuve esta discusión con algunas personas que decían que los punteros C fuera de límite causan un comportamiento indefinido incluso si no están siendo desreferenciados. ejemplo:

int a; int *p = &a; p = p - 1;

la tercera línea aquí causará un comportamiento indefinido incluso si p nunca es desreferenciada ( *p nunca se usa).

En mi opinión, parece ilógico que C verifique si un puntero está fuera de límite sin que se use el puntero (es como si alguien inspeccionara a las personas en la calle para ver si llevan armas en caso de que entren a su casa). Donde lo ideal es inspeccionar a las personas cuando están a punto de entrar a la casa). Creo que si C busca eso, se producirá una gran sobrecarga de tiempo de ejecución.

Además, si C realmente busca punteros OOB, entonces por qué esto no causará UB:

int *p; // uninitialized thus pointing to a random adress

en este caso, por qué no ocurre nada, incluso si la probabilidad de que apunte a una dirección OOB es alta.

AÑADIR:

int a; int *p = &a; p = p - 1;

say &a es 1000. El valor de p después de evaluar la tercera línea será:

  • 996 pero todavía un comportamiento indefinido porque p podría ser desreferenciado en otro lugar y causar el problema real.
  • valor indefinido y ese es el comportamiento indefinido.

porque creo que "la tercera línea fue llamada a ser de comportamiento indefinido" en primer lugar fue por el uso futuro potencial de ese puntero OOB (desreferencia) y las personas, con el tiempo, lo tomaron como un comportamiento indefinido en sí mismo. Ahora, ¿el valor de p será 100% 996 y ese comportamiento aún indefinido o su valor no estará definido?


Algunas plataformas tratan los punteros como enteros y procesan la aritmética del puntero de la misma manera que la aritmética de enteros, pero con ciertos valores aumentados o reducidos según el tamaño de los objetos. En dichas plataformas, esto efectivamente definirá un resultado "natural" de todas las operaciones aritméticas del puntero excepto la resta de punteros cuya diferencia no es un múltiplo del tamaño del tipo de objetivo del puntero.

Otras plataformas pueden representar punteros de otras maneras, y la adición o sustracción de ciertas combinaciones de punteros puede causar resultados impredecibles.

Los autores de C Standard no querían mostrar favoritismo hacia ningún tipo de plataforma, por lo que no imponen requisitos sobre lo que puede suceder si los punteros se manipulan de forma tal que causarían problemas en algunas plataformas. Antes de C Standard, y durante algunos años después, los programadores podían esperar razonablemente que las implementaciones de propósito general para plataformas que trataban la aritmética de punteros como la aritmética de enteros escalados trataran también la aritmética de punteros, pero las implementaciones para plataformas que trataban la aritmética de punteros de manera diferente probablemente lo tratarían de manera diferente ellos mismos.

Sin embargo, en la última década, en busca de la "optimización", los escritores de compilación han decidido arrojar por la ventana el Principio del menor asombro. Incluso en los casos en que un programador sabría cuál sería el efecto de ciertas operaciones de puntero a las representaciones de un puntero natural de una plataforma, no hay garantía de que los compiladores generen código que se comporte de la manera en que se comportarían las representaciones de puntero natural. El hecho de que el estándar diga que el comportamiento no está definido se interpreta como una invitación para que los compiladores impongan "optimizaciones" que obligan a los programadores a escribir código más lento y complicado de lo que debería ser en implementaciones que simplemente se comportan de manera consistente con el documento comportamientos del entorno subyacente (uno de los tres tratamientos que los autores del C89 señalaron explícitamente como algo común).

Por lo tanto, a menos que uno sepa que uno está usando un compilador que no tiene habilitadas "optimizaciones" extravagantes, el hecho de que un paso intermedio en una secuencia de cómputos de puntero invoca un comportamiento indefinido hace que sea imposible razonar sobre eso, sin importar qué tan fuertemente sentido común implicaría que las implementaciones de calidad para una plataforma particular se comporten de una manera particular.


C no verifica si un puntero está fuera de límites . Pero el hardware subyacente puede comportarse de maneras extrañas cuando se calcula una dirección que cae fuera de los límites del objeto, señalando justo después de que el final de un objeto sea una excepción. El Estándar C explícitamente describe esto como causante de un comportamiento indefinido.

Para la mayoría de los entornos actuales, el código anterior no plantea un problema, pero situaciones similares podrían causar fallas de segmentación en el modo protegido x86 de 16 bits, hace unos 25 años.

En el lenguaje del Estándar, dicho valor podría ser un valor de trampa , algo que no se puede manipular sin invocar un comportamiento indefinido.

La sección pertinente del Estándar C11 es:

6.5.6 Operadores de aditivos

  1. Cuando una expresión que tiene un tipo de entero se agrega o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un desplazamiento de elemento desde el elemento original tal que la diferencia de los subíndices de los elementos de matriz resultante y original es igual a la expresión entera. [...] Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento no está definido. Si el resultado señala uno pasado el último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Un ejemplo similar de comportamiento indefinido es este:

char *p; char *q = p;

El mero hecho de cargar el valor del puntero p no inicializado invoca un comportamiento indefinido, incluso si nunca se desreferencia.

EDIT: es un punto discutible tratando de discutir sobre esto. El estándar dice que la computación de dicha dirección invoca un comportamiento indefinido, por lo que lo hace. El hecho de que algunas implementaciones solo puedan calcular algún valor y almacenarlo o no es irrelevante. No confíe en ninguna suposición sobre el comportamiento indefinido: el compilador podría aprovechar su naturaleza inherentemente impredecible para realizar optimizaciones que no puede imaginar.

Por ejemplo, este ciclo:

for (int i = 1; i != 0; i++) { ... }

podría compilarse en un bucle infinito sin ninguna prueba en absoluto: i++ invoca comportamiento indefinido si i INT_MAX , por lo que el análisis del compilador es este:

  • el valor inicial de i es > 0 .
  • para cualquier valor positivo de i < INT_MAX , i++ es aún > 0
  • para i = INT_MAX , i++ invoca un comportamiento indefinido, por lo que podemos suponer i > 0 porque podemos asumir cualquier cosa que nos plazca.

Por lo tanto, siempre es > 0 y el código de prueba puede eliminarse.


Cuando las especificaciones dicen que algo no está definido, eso puede ser bastante confuso.

Significa, en esa circunstancia, la implementación de la especificación es libre de hacer lo que quiera. En algunos casos, hará algo que aparezca, intuitivamente, correcto. En otros casos, no lo hará.

Para las especificaciones de límite de dirección, sé que mi intuición proviene de mis suposiciones sobre un modelo de memoria plana uniforme. Pero hay otros modelos de memoria.

La palabra "indefinido" nunca aparece en una especificación completa involuntariamente. Los comités de estándares generalmente deciden usar la palabra cuando conocen diferentes implementaciones de la necesidad estándar de hacer cosas diferentes. En muchos casos, la razón de las diferentes cosas es el rendimiento. Así que: la aparición de la palabra en la especificación es una señal de alerta roja acerca de que para nosotros simples mortales, usuarios de la especificación, nuestra intuición puede estar equivocada.

Este tipo de especificación de "lo que sea que quiera" molestó a la empresa famosa hace unos años. Entonces, hizo algunas versiones de su Gnu Compiler Collection (gcc) tratando de jugar un juego de computadora cuando encontró algo indefinido.

IBM usó la palabra impredecible en sus especificaciones en los 360/370 días. Esa es una mejor palabra. Hace que el resultado suene más aleatorio y más peligroso. Dentro del alcance del comportamiento "impredecible" se encuentran resultados tan problemáticos como "detenerse y prenderse fuego".

Aquí está la cosa, sin embargo. "Aleatorio" es una mala forma de describir este tipo de comportamiento impredecible, porque "aleatorio" implica que el sistema puede hacer algo diferente cada vez que encuentra el problema. Si hace algo diferente cada vez, tienes la oportunidad de detectar el problema en la prueba. En el mundo del comportamiento "indefinido" / "impredecible", el sistema hace lo mismo cada vez, hasta que no lo hace. Y, sabes que no será años después de que pienses que has terminado de probar tus cosas.

Entonces, cuando la especificación dice que algo no está definido, no hagas eso. A menos que seas amigo de Murphy . ¿OKAY?


De hecho, el comportamiento de un programa C no está definido si intenta calcular un valor a través de la aritmética del puntero que no da como resultado un puntero a un elemento, o uno más allá del final del mismo elemento de la matriz. De C11 6.5.6 / 8:

Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto del arreglo, o uno más allá del último elemento del objeto del arreglo, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento no está definido.

(A los fines de esta descripción, la dirección de un objeto de un tipo T puede tratarse como la dirección del primer elemento de un conjunto T[1] ).


La parte de la pregunta relacionada con el comportamiento indefinido es muy clara, la respuesta es "Bueno, sí, ciertamente es un comportamiento indefinido".

Interpretaré la frase "Does C check ..." como las siguientes dos:

  1. ¿Comprueba el compilador de C ...?
  2. ¿Comprueba mi programa compilado ...?

(C en sí es una especificación de idioma, no verifica, o no hace nada)

La respuesta a la primera pregunta es: sí, pero no de manera confiable, y no de la manera que usted desea. Los compiladores modernos son bastante inteligentes, a veces más inteligentes de lo que te gustaría. El compilador, en algunos casos, podrá diagnosticar el uso ilegítimo de los punteros. Dado que esto por definición invoca un comportamiento indefinido y, por lo tanto, el lenguaje ya no requiere que el compilador haga nada en particular, el compilador a menudo optimizará de una manera impredecible. Esto puede dar como resultado un código que es muy diferente de lo que originalmente pretendía. No se sorprenda si un alcance completo o incluso la función completa se pone a prueba. Esto es cierto para muchas "optimizaciones sorpresa" indeseables en relación con un comportamiento indefinido.
Lectura obligatoria: Lo que todo programador de C debe saber sobre el comportamiento indefinido .

La Respuesta a la segunda pregunta es: No, excepto si utiliza un compilador que admita comprobaciones de límites y si compila con comprobaciones de límites de tiempo de ejecución habilitadas, lo que implica una sobrecarga de tiempo de ejecución bastante no trivial.
En la práctica, esto significa que si su programa "sobrevivió" al compilador optimizando el comportamiento indefinido, entonces obstinadamente hará lo que le indicó que haga, con resultados impredecibles, generalmente valores de basura que se leen, o su programa provocando una segmentación culpa.


Para aclarar, "Comportamiento no definido" significa que el resultado del código en cuestión no está definido en las normas que rigen el idioma. El resultado real depende de la forma en que se implemente el compilador, y puede variar desde nada en absoluto hasta un bloqueo completo y todo lo que se encuentra en el medio.

Los estándares no especifican que deba realizarse una verificación de punteros en rango. Pero en relación con su ejemplo específico, esto es lo que dicen:

Cuando una expresión que tiene un tipo entero se agrega o se resta de un puntero ... Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto del arreglo, o uno más allá del último elemento del objeto del arreglo, la evaluación no deberá producir un desbordamiento; de lo contrario, el comportamiento no está definido. Si el resultado señala uno pasado el último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

La cita anterior es de C99 §6.5.6 Para 8 (la última versión que tengo a mano).

Tenga en cuenta que lo anterior también se aplica a los punteros que no son de matriz, ya que en la cláusula anterior dice:

Para los propósitos de estos operadores, un puntero a un objeto que no es un elemento de una matriz se comporta de la misma manera que un puntero al primer elemento de una matriz de longitud uno con el tipo de objeto como su tipo de elemento.

Por lo tanto, si realiza la aritmética del puntero y el resultado está dentro de los límites o apunta a uno más allá del final del objeto, obtendrá un resultado válido, de lo contrario obtendrá un comportamiento indefinido. Ese comportamiento puede ser que termines con un puntero extraviado, pero podría ser otra cosa.


Pero, ¿qué es un comportamiento indefinido? Eso simplemente significa que nadie está dispuesto a decir lo que sucederá.

Soy un viejo perro de mainframe de años atrás, y me gusta la frase de IBM sobre lo mismo: los resultados son impredecibles .

Por cierto: me gusta la idea de NO verificar los límites de la matriz. Por ejemplo, si tengo un puntero en una cadena y quiero ver lo que está justo antes de señalar el byte, puedo usar:

pointer[-1]

para mirarlo.