2011-02-01 20 views
17

Siempre me he preguntado: ¿no se supone que ptrdiff_t puede mantener la diferencia de dos punteros por definición? ¿Cómo es que falla cuando los dos indicadores están demasiado lejos? (No estoy apuntando en cualquier idioma en particular ... Me refiero a todos los idiomas que tienen este tipo.)ptrdiff_t ¿demasiado pequeño?

(por ejemplo, restar el puntero con la dirección de 1 desde el puntero byte con la dirección 0xFFFFFFFF cuando se tiene 32- punteros de bits, y desborda el bit de signo ...)

Respuesta

28

No, no lo es.

$ 5,7 [expr.add] (de N3225 - C++ 0x FCD)
Cuando se restan dos punteros a elementos del mismo objeto de matriz, el resultado es la diferencia de los subíndices de los dos elementos de matriz. El tipo de resultado es un tipo integral con signo definido por la implementación; este tipo debe ser del mismo tipo que se define como std::ptrdiff_t en el encabezado <cstddef> (18.2). Como con cualquier otro desbordamiento aritmético, si el resultado no encaja en el espacio provisto, el comportamiento no está definido. En otras palabras, si las expresiones P y Q punto a, respectivamente, la i -ésimo y j elementos -ésimos de un objeto array, la expresión (P)-(Q) tiene el valor i − j siempre que el valor se ajusta en un objeto de tipo std::ptrdiff_t. Además, si la expresión P apunta a un elemento de un objeto de matriz o al pasado del último elemento de un objeto de matriz, y la expresión Q apunta al último elemento del mismo objeto de matriz, la expresión ((Q)+1)-(P) tiene el mismo valor que ((Q)-(P))+1 y como -((P)-((Q)+1)), y tiene el valor cero si la expresión P apunta uno más allá del último elemento del objeto de matriz, aunque la expresión (Q)+1 no apunta a un elemento del objeto de matriz. A menos que ambos punteros apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, el comportamiento no está definido.

Tenga en cuenta el número de veces undefined aparece en el párrafo. También tenga en cuenta que solo puede restar punteros si apuntan dentro del mismo objeto.

+0

+1 Le doy un puñado de segundos. Buena excavación! – templatetypedef

+0

+1 Guau, yo (obviamente) nunca supe que esta indefinición era parte del estándar. Pero, en este caso, ¿no es 'ptrdiff_t' solo un' signed_t' firmado glorificado? – Mehrdad

+2

@templatetypedef: estoy contento, por una vez, de no despertarme después de la batalla :) @Mehrdad: Yo diría que sí :) Por otro lado, nada prohíbe la implementación de la biblioteca estándar que usas para usar un 64 -bits entero incluso en una plataforma de 32 bits. La norma solo dice que no es obligatorio para ellos hacer el esfuerzo. El resto es un problema de calidad de implementación. –

8

No, porque no existe la diferencia entre "dos punteros". Solo puede restar punteros a elementos de la misma matriz (o el puntero a la ubicación justo después del final de una matriz).

+1

Pero, ¿y si * tienes * una matriz realmente grande (quizás porque * eres * el kernel)? – Mehrdad

+2

@Mehrdad: El kernel puede hacer muchas suposiciones poco importantes. Por ejemplo, el código del kernel de Linux asume que se compilará con GCC y que "largo" tiene el mismo ancho que un puntero. En los sistemas de 32 bits y de 64 bits, la diferencia entre dos direcciones cualquiera cabe en 64 bits (porque algunos de los bits altos en una dirección de 64 bits no se utilizan). –

+0

Espera, ¿qué? ¿Asumen que "largo" es el tamaño de un puntero? o__o ¿Por qué no usan simplemente 'ptrdiff_t' (que yo diría es una apuesta más segura) o crean un typedef personalizado en su lugar? – Mehrdad

1

Es totalmente aceptable que ptrdiff_t tenga el mismo tamaño que los tipos de puntero, siempre que la semántica de desbordamiento esté definida por el compilador para que toda diferencia sea representable. No hay garantía de que un ptrdiff_t negativo signifique que el segundo puntero vive en una dirección inferior en memoria que la primera, o que ptrdiff_t está firmado en absoluto.

+2

Hm ... ¿estás seguro de la parte "o esa ptrdiff_t está firmada en absoluto"? – Mehrdad

+0

No del todo, pero no tengo mi copia del estándar en mano, y no confiaría en esto para nada (es decir, usaría 'a

+0

@Simon: Ah bien, +1 de todos modos, buena respuesta. :) – Mehrdad

2

Para añadir una cotización estándar más explícito, ISO 9899:1999 §J.2/1 estados:

El comportamiento no está definido en las siguientes circunstancias:

[...]

- El resultado de restar dos punteros no es representable en un objeto del tipo ptrdiff_t (6.5.6).

-1

Sobre/underflow es matemáticamente bien definida de tamaño fijo aritmética de enteros:

(1 - 0xFFFFFFFF) % (1<<32) = 
(1 + -0xFFFFFFFF) % (1<<32) = 
1 + (-0xFFFFFFFF % (1<<32)) = 2 

Este es el resultado correcto!

Específicamente, el resultado después de over/underflow es un alias del entero correcto. De hecho, cada entero no representable tiene un alias (no distinguible) con un entero representable: cuenta hasta el infinito en enteros de tamaño fijo, y se repetirá, dando vueltas y más vueltas como un dial de un reloj analógico.

Un entero de N bits representa cualquier número entero real 2^N. En C, el módulo 2^N se escribe como% (1 < < 32).

Creo que C garantiza la corrección matemática de sobre/bajo flujo, pero solo para enteros sin signo. Se supone que el under/overflow firmado nunca ocurre (en aras de la optimización).

En la práctica, los enteros con signo complementan dos, lo que no hace ninguna diferencia en la suma o la resta, por lo que el comportamiento correcto de sub/desbordamiento está garantizado también para enteros con signo (aunque no por C).

+1

* "En la práctica, los enteros con signo son complemento a dos, lo que no hace ninguna diferencia en la suma o la resta" * ... eso no tiene sentido. Indefinido no está definido - * cualquier cosa * podría suceder. Lo que significa que no puedes confiar en que ocurra algo en particular. – Mehrdad

+1

Esta respuesta es absolutamente incorrecta. La mayor parte del tiempo El cálculo se realizará como se indicó, pero el compilador también podría hacer lo que quiera: Este es un comportamiento indefinido. [Ver esto] (http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html). – Arnaud

Cuestiones relacionadas