Pregunta ¿Se define el comportamiento de restar dos punteros NULL?


Es la diferencia de dos variables de puntero no nulas definidas (por C99 y / o C ++ 98) si ambas son NULL ¿valorado?

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

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

Decir, ex.buf señala una matriz o alguna memoria malloc'ed. Si mi código siempre garantiza que pwrite y pread señalar dentro de esa matriz o una pasada, entonces estoy bastante seguro de que ex.pwrite - ex.pread siempre será definido. Sin embargo, ¿y si pwrite y pread son ambos NULL. ¿Puedo esperar que restar los dos se define como (ptrdiff_t)0 o ¿el código estrictamente compatible necesita probar los punteros para NULL? Tenga en cuenta que el único caso en el que estoy interesado es cuando ambos los punteros son NULL (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;
}

74
2017-11-14 21:11


origen


Respuestas:


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

7) A los efectos de estos operadores, un puntero a un objeto que no es un elemento de un   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.

[...]

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 matriz; el resultado es la diferencia de   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, el puntero resultante, llamado puntero nulo, está garantizado para comparar 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 pequeño cambio de std::ptrdiff_t en lugar de ptrdiff_t.


94
2017-11-14 21:28



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

Si dos punteros [...] ambos son nulos, y los dos punteros son   restado, el resultado se compara igual al valor 0 convertido a   escriba 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.


35
2017-11-14 21:22



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).


23
2017-11-14 21:16



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 producir n personajes comenzando en la dirección p está escrito 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, con sin efecto secundario, si p == NULL yn == 0, sin necesidad de un caso especial n == 0. En 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.


1
2018-04-12 07:25