El Estándar C no impone ningún requisito sobre el comportamiento en este caso, pero muchas implementaciones especifican el comportamiento de la aritmética de punteros en muchos casos más allá de los mínimos requeridos por el Estándar, incluido este.
En cualquier implementación de C conforme, y casi todas (si no todas) las implementaciones de dialectos similares a C, las siguientes garantías serán válidas para cualquier puntero p
tal que identifique *p
o *(p-1)
identifique algún objeto:
- Para cualquier valor entero
z
que sea igual a cero, el puntero valora (p+z)
y (p-z)
será equivalente en todos los sentidos a p
, excepto que solo serán constantes si ambos p
y z
son constantes.
- Para cualquiera
q
que sea equivalente a p
, las expresiones p-q
y q-p
ambos darán cero.
Tener tales garantías válidas para todos los valores de puntero, incluido el nulo, puede eliminar la necesidad de algunas comprobaciones nulas en el código de usuario. Además, en la mayoría de las plataformas, generar código que mantenga dichas garantías para todos los valores de puntero sin tener en cuenta si son nulos sería más sencillo y económico que tratar los nulos de forma especial. Algunas plataformas, sin embargo, pueden atrapar intentos de realizar aritmética de punteros con punteros nulos, incluso al sumar o restar cero. En tales plataformas, la cantidad de verificaciones nulas generadas por el compilador que tendrían que agregarse a las operaciones de puntero para mantener la garantía excedería en muchos casos la cantidad de verificaciones nulas generadas por el usuario que podrían omitirse como resultado.
Si hubiera una implementación donde el costo de mantener las garantías sería grande, pero pocos programas recibirían algún beneficio de ellos, tendría sentido permitirle atrapar cálculos "nulos + cero" y requerir ese código de usuario para tal implementación incluye los controles manuales nulos que las garantías podrían haber hecho innecesarias. No se esperaba que dicha provisión afectara al 99,44% restante de las implementaciones, donde el valor de mantener las garantías excedería el costo. Tales implementaciones deberían mantener tales garantías, pero sus autores no deberían necesitar que los autores del Estándar les digan eso.
Los autores de C ++ han decidido que las implementaciones conformes deben mantener las garantías anteriores a cualquier costo, incluso en plataformas donde podrían degradar sustancialmente el rendimiento de la aritmética de punteros. Juzgaron que el valor de las garantías, incluso en plataformas donde sería costoso mantenerlas, excedería el costo. Tal actitud puede haber sido afectada por el deseo de tratar C ++ como un lenguaje de nivel superior que C. Se podría esperar que el programador de CA sepa cuándo una plataforma de destino en particular manejaría casos como (nulo + cero) de manera inusual, pero los programadores de C ++ no se esperaba que se preocuparan por esas cosas. Por lo tanto, se consideró que valía la pena el costo de garantizar un modelo de comportamiento coherente.
Por supuesto, hoy en día las preguntas sobre lo que está "definido" rara vez tienen algo que ver con los comportamientos que puede soportar una plataforma. En cambio, ahora está de moda que los compiladores, en nombre de la "optimización", requieran que los programadores escriban código manualmente para manejar casos de esquina que las plataformas anteriormente habrían manejado correctamente. Por ejemplo, si el código que se supone que genera n
caracteres que comienzan 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 no generaría de manera confiable nada, sin efectos secundarios, si p == NULL yn == 0, sin necesidad de un caso especial n == 0. Sin embargo, en compiladores más nuevos, habría que 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++);
}
}
del cual un optimizador puede o no puede deshacerse. Si no se incluye el código adicional, algunos compiladores pueden pensar que, dado que p "no puede ser nulo", es posible que se omitan las comprobaciones posteriores de puntero nulo, lo que provocará que el código se rompa en un lugar no relacionado con el "problema" real.