En C se dice que cuando los punteros se refieren a la misma matriz o un elemento más allá del final de esa matriz, la aritmética y las comparaciones están bien definidas. Entonces, ¿qué pasa con uno antes del primer elemento de la matriz? ¿Está bien siempre que no lo desreferencia?
Dado
int a[10], *p;
p = a;
(1) ¿Es legal escribir --p
?
(2) ¿Es legal escribir p-1
en una expresión?
(3) Si (2) está bien, ¿puedo afirmar eso p-1 < a
?
Hay cierta preocupación práctica por esto. Considere una reverse()
función que invierte una cadena en C que termina con '\0'
.
#include <stdio.h>
void reverse(char *p)
{
char *b, t;
b = p;
while (*p != '\0')
p++;
if (p == b) /* Do I really need */
return; /* these two lines? */
for (p--; b < p; b++, p--)
t = *b, *b = *p, *p = t;
}
int main(void)
{
char a[] = "Hello";
reverse(a);
printf("%s\n", a);
return 0;
}
¿Realmente necesito hacer la verificación en el código?
Comparta sus ideas desde el punto de vista del abogado del idioma / perspectivas prácticas, y cómo enfrentaría tales situaciones.
c
language-lawyer
pointer-arithmetic
aafulei
fuente
fuente
Respuestas:
Es "legal" como lo permite la sintaxis en C, pero invoca un comportamiento indefinido. Para el propósito de encontrar la sección relevante en el estándar,
--p
es equivalente ap = p - 1
(exceptop
que solo se evalúa una vez). Entonces:La evaluación invoca un comportamiento indefinido, lo que significa que no importa si des referencia el puntero o no, ya invocó un comportamiento indefinido.
Además:
C17 6.5.6 / 9:
Si su código viola un "debe" en el estándar ISO, invoca un comportamiento indefinido.
Igual que (1), comportamiento indefinido.
En cuanto a ejemplos de cómo esto podría causar problemas en la práctica: imagine que la matriz se coloca al comienzo de una página de memoria válida. Cuando disminuye fuera de esa página, puede haber una excepción de hardware o una representación de captura de puntero. Este no es un escenario completamente improbable para los microcontroladores, particularmente cuando usan mapas de memoria segmentados.
fuente
*p == '\0'
al principio. Esta verificación tiene la intención de evitarp--
el bucle for.'\0'
), habrá un intercambio automático (intercambio consigo mismo) al final. Pero eso está bien. También tenga paciencia conmigo un poco más para la validación cruzada antes de que pueda hacer el tic.p-1
en una expresión no es válida,p=p-1
sería inválida. Yp--
esp=p-1
. ¿Argumentaría que disminuir un puntero no es válido?El uso de ese tipo de aritmética de puntero es una mala práctica de codificación, ya que podría conducir a una gran cantidad de problemas difíciles de depurar.
Solo tuve que usar este tipo de cosas una vez en más de 20 años. Estaba escribiendo una función de devolución de llamada, pero no tenía acceso a los datos adecuados. La función de llamada proporcionó un puntero dentro de una matriz adecuada, y necesitaba el byte justo antes de ese puntero.
Teniendo en cuenta que tenía acceso a todo el código fuente, y verifiqué el comportamiento varias veces para demostrar que obtengo lo que necesito, y otros colegas lo revisaron, decidí que estaba bien dejarlo ir a producción.
La solución adecuada habría sido cambiar la función de la persona que llama para devolver el puntero adecuado, pero eso no era factible, considerando el tiempo y el dinero (esa parte del software fue licenciada por un tercero).
Por lo tanto,
a[-1]
es posible, pero debe usarse SOLAMENTE con muy buen cuidado en situaciones muy particulares. De lo contrario, no hay una buena razón para hacer ese tipo de vudú autolesivo.Nota: en un análisis adecuado, en mi ejemplo, es obvio que no accedí a un elemento antes del comienzo de una matriz adecuada, sino al elemento antes de un puntero, que se garantizó que estaría dentro de la misma matriz.
Refiriéndose al código provisto:
reverse(a);
;reverse(a+1);
, porque permanece dentro de la matriz.fuente