¿Está definida la diferencia de dos variables de puntero no nulas (según C99 y / o C ++ 98) si ambas están NULL
valoradas?
Por ejemplo, digamos que tengo una estructura de búfer que se parece a esto:
struct buf {
char *buf;
char *pwrite;
char *pread;
} ex;
Digamos, ex.buf
apunta a una matriz o alguna memoria mal ubicada. Si mi código siempre asegura eso pwrite
y pread
apunta dentro de esa matriz o una más allá, entonces estoy bastante seguro de que ex.pwrite - ex.pread
siempre estará definido. Sin embargo, ¿qué pasa si pwrite
y pread
ambos son NULL? ¿Puedo esperar que restar los dos esté definido como (ptrdiff_t)0
o el código estrictamente compatible necesita probar los punteros para NULL? Tenga en cuenta que el único caso que me interesa es cuando ambos punteros son NULL (lo que representa un caso de búfer 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;
}
near
puntero nulofar
calificado de uno calificado, pero ¿usaría múltiples representaciones parafar
punteros nulos ? Si unnear
puntero nulo se convierte en unfar
puntero que a su vez se compara con unfar
puntero nulo cuando, por ejemplo, DS es igual a 0x1234, qué sucede: (1) 0x0000 se convierte en 0x0000: 0x0000; (2) 0x0000 se convierte a 0x1234: 0x0000, pero el operador de comparación comprueba el caso de ambos segmentos-cero, o (3) 0x0000 se convierte a 0x1234: 0x0000, que compara desigual con 0x0000: 0x0000.Respuestas:
En C99, es un comportamiento técnicamente indefinido. C99 §6.5.6 dice:
Y §6.3.2.3 / 3 dice:
Entonces, dado que un puntero nulo no es igual a cualquier 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 a 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:
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 deptrdiff_t
.fuente
Encontré esto en el estándar C ++ (5.7 [expr.add] / 7):
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.
fuente
Editar : esta respuesta solo es válida para C, no vi la etiqueta C ++ cuando respondí.
No, la aritmética de punteros solo está permitida para punteros que apuntan dentro del mismo objeto. Dado que, por definición de 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
eso, pero quién sabe).fuente
std::ptrdiff_t
".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:z
que sea igual a cero, el puntero valora(p+z)
y(p-z)
será equivalente en todos los sentidos ap
, excepto que solo serán constantes si ambosp
yz
son constantes.q
que sea equivalente ap
, las expresionesp-q
yq-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ónp
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.
fuente