Puntero a uno antes del primer elemento de la matriz

8

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-1en 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.

aafulei
fuente
2
(1) Puede ser legal escribirlo, pero el resultado es un comportamiento indefinido si lo ejecuta. Con arquitecturas segmentadas (Intel 80286, 80386, etc.), el resultado podría ser completamente confuso. (2) Ídem. (3) N / A, pero la respuesta es no. Para su hardware y su o / s, es posible que esté seguro, pero el estándar C no lo garantiza.
Jonathan Leffler
1
¿Responde esto a tu pregunta? ¿Cuáles son todos los comportamientos indefinidos comunes que un programador de C ++ debe conocer? Busque específicamente en la sección de punteros.
Isaías

Respuestas:

6

(1) ¿Es legal escribir --p?

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, --pes equivalente a p = p - 1(excepto pque solo se evalúa una vez). Entonces:

C17 6.5.6 / 8

Si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno pasado el último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido.

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:

Cuando se restan dos punteros, ambos apuntarán a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz;

Si su código viola un "debe" en el estándar ISO, invoca un comportamiento indefinido.

(2) ¿Es legal escribir p-1 en una expresión?

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.

Lundin
fuente
¿Podría por favor proporcionar algunos comentarios al código de ejemplo en la pregunta? ¿Es el cheque necesario / apropiado / suficiente?
aafulei
Es posible que *p == '\0'al principio. Esta verificación tiene la intención de evitar p--el bucle for.
aafulei
@aafulei Sí, me di cuenta. Necesita la verificación adicional debido al bucle mal escrito. La solución correcta es reescribir el ciclo, por ejemplo: godbolt.org/z/R4TuwT
Lundin
Gracias por tu código. Es inteligente. Te sugiero que lo pongas en tu respuesta para que más personas lo vean. Lo único es que cuando hay un número impar de caracteres en la cadena (sin contar '\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.
aafulei
Si p-1en una expresión no es válida, p=p-1sería inválida. Y p--es p=p-1. ¿Argumentaría que disminuir un puntero no es válido?
Harper
-2

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:

  • NO está bien usar p [-1] con reverse(a); ;
  • está bien (-ish) usarlo reverse(a+1);, porque permanece dentro de la matriz.
virolino
fuente