punteros puntero matrices lenguaje declarar declaracion como arreglos aritmetica apuntadores c++ c c99 c89

c++ - matrices - punteros y arreglos



¿Se define el comportamiento de restar dos punteros NULL? (4)

¿La diferencia de dos variables de puntero no nulas está definida (por C99 y / o C ++ 98) si ambas tienen valor NULL ?

Por ejemplo, supongamos que tengo una estructura de búfer que se ve así:

struct buf { char *buf; char *pwrite; char *pread; } ex;

Digamos que ex.buf apunta a una matriz o memoria mallocida. Si mi código siempre garantiza que pwrite y pwrite punto dentro de ese conjunto o uno más allá, entonces estoy bastante seguro de que ex.pwrite - ex.pread siempre se definirá. Sin embargo, ¿qué pwrite si pwrite y pwrite son ambos NULL? ¿Puedo esperar que restar los dos se defina como (ptrdiff_t)0 o que el código estrictamente compatible necesite probar los punteros para NULL? Tenga en cuenta que el único caso en el que estoy interesado es cuando ambos punteros son NULL (lo que representa un buffer no inicializado). La razón tiene que ver con una función "disponible" totalmente compatible dado que se cumplen los supuestos anteriores:

size_t buf_avail(const struct s_buf *b) { return b->pwrite - b->pread; }


El estándar C no impone ningún requisito sobre el comportamiento, sin embargo, muchas implementaciones especifican el comportamiento de la aritmética del puntero en muchos casos más allá de los mínimos requeridos por el estándar, incluido el comportamiento de restar un puntero nulo de otro. Hay muy pocas arquitecturas donde alguna vez haya alguna razón para que una sustracción haga otra cosa que ceder el cero, pero desafortunadamente ahora está de moda que los compiladores, en nombre de la "optimización", exijan que los programadores escriban códigos manualmente. para manejar casos de esquina que las plataformas anteriormente habrían manejado correctamente. Por ejemplo, si el código que se supone debe generar n caracteres comenzando en la dirección p se escribe como:

void out_characters(unsigned char *p, int n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); }

los compiladores más antiguos generarían código que arrojaría de manera confiable nada, sin efectos secundarios, si p == NULL yn == 0, sin necesidad de casos especiales n == 0. En los compiladores más nuevos, sin embargo, uno debería agregar código adicional:

void out_characters(unsigned char *p, int n) { if (n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } }

que un optimizador puede o no ser capaz de eliminar. Si no se incluye el código adicional, algunos compiladores pueden darse cuenta de que, dado que p "no puede ser nulo", se pueden omitir las siguientes comprobaciones nulas del puntero, lo que hace que el código se rompa en un punto no relacionado con el "problema" real.


En C99, es un comportamiento técnicamente indefinido. C99 §6.5.6 dice:

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

[...]

9) Cuando se restan dos punteros, ambos señalarán elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz; el resultado es la diferencia de los subíndices de los dos elementos de la matriz. [...]

Y §6.3.2.3 / 3 dice:

Una expresión constante entera con el valor 0, o una expresión de este tipo para escribir void * , se llama constante de puntero nulo. 55) Si una constante de puntero nulo se convierte en un tipo de puntero , se garantiza que el puntero resultante, llamado puntero nulo , se compare desigual a un puntero a cualquier objeto o función.

Entonces, como un puntero nulo no es igual para ningún objeto, viola las condiciones previas de 6.5.6 / 9, por lo que es un comportamiento indefinido. Pero en la práctica, estaría dispuesto a apostar que casi todos los compiladores devolverán un resultado de 0 sin efectos secundarios negativos.

En C89, también es un comportamiento indefinido, aunque la redacción del estándar es ligeramente diferente.

C ++ 03, por otro lado, tiene un comportamiento definido en esta instancia. El estándar hace una excepción especial para restar dos punteros nulos. C ++ 03 §5.7 / 7 dice:

Si el valor 0 se agrega o se resta de un valor de puntero, el resultado se compara con el valor del puntero original. Si dos punteros apuntan al mismo objeto o ambos apuntan uno más allá del final de la misma matriz o ambos son nulos, y los dos punteros se restan, el resultado se compara con el valor 0 convertido al tipo ptrdiff_t .

C ++ 11 (así como el último borrador de C ++ 14, n3690) tienen una redacción idéntica a C ++ 03, con solo el cambio menor de std::ptrdiff_t en lugar de ptrdiff_t .


Encontré esto en el estándar C ++ (5.7 [expr.add] / 7):

Si dos punteros [...] son ​​nulos y los dos punteros se restan, el resultado se compara con el valor 0 convertido al tipo std :: ptrdiff_t

Como han dicho otros, C99 requiere que la suma / resta entre 2 punteros sea del mismo objeto de matriz. NULL no apunta a un objeto válido por lo que no puede usarlo en la resta.


Editar : Esta respuesta solo es válida para C, no vi la etiqueta C ++ cuando respondí.

No, la aritmética del puntero solo se permite para los punteros que apuntan dentro del mismo objeto. Dado que, por definición, los punteros nulos estándar de C no apuntan a ningún objeto, este es un comportamiento indefinido.

(Aunque, supongo que cualquier compilador razonable devolverá solo 0 en él, pero quién sabe).