2011-02-05 14 views
11

Sección §6.5.3.2 "Dirección y indirección operadores" ¶3 dice (sección relevante solamente):C estándar frente a la simplificación inconsistencia

El & operador unitario devuelve la dirección de su operando. ... Si el operando es el resultado de un operador único *, ni ese operador ni el operador & se evalúan y el resultado es como si ambos se hubieran omitido, excepto que las restricciones sobre los operadores aún se aplican y el resultado no es lvalue Del mismo modo, si el operando es el resultado de un operador de [], ni el operador & ni el unario * que está implícito en la [] se evalúa y el resultado es como si el operador & se retiraron y el operador [] se cambiaron a un operador + . ...

Esto significa que este:

#define NUM 10 
int tmp[NUM]; 
int *i = tmp; 
printf("%ti\n", (ptrdiff_t) (&*i - i)); 
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i)); 

Debe ser perfectamente legal, la impresión de 0 y el NUM (10). El estándar parece muy claro que ambos casos deben ser optimizados.

Sin embargo, no parece requerir la siguiente ser optimizado:

struct { int a; short b; } tmp, *s = tmp; 
printf("%ti\n", (ptrdiff_t) (&s->b - s)); 

Esto parece muy inconsistente. No veo ninguna razón para que el código anterior no imprima el sizeof(int) más (improbable) relleno (posiblemente 4).

Simplificar una expresión &-> va a ser el mismo conceptualmente (en mi humilde opinión) como &[], una simple dirección-más-desplazamiento. Incluso es un desplazamiento que se podrá determinar en tiempo de compilación, en lugar de potencialmente en tiempo de ejecución con el operador [].

¿Hay algo en el fundamento acerca de por qué esto es aparentemente inconsistente?

+0

He visto muchísimas preguntas sobre los estándares de C y C++ en las que no sabía cómo hacer otra cosa que votar, favoritos y esperar para leer las respuestas. Se siente raro haber escrito uno. –

+0

interesante ... ¡MSVC++ imprime 4! – Abhi

+0

@Abhi Rao - GCC (4.0) con -Wall -Wextra -Werror compila e imprime 4 sin ninguna queja. –

Respuesta

4

En su ejemplo, &i[10] no es legal: se convierte en i + 10, que se convierte en NULL + 10, y no puede realizar aritmética en un puntero nulo. (6.5.6/8 enumera las condiciones bajo las cuales se puede realizar la aritmética del puntero)

De todos modos, esta regla se agregó en C99; no estaba presente en C89. Mi entendimiento es que se añadió en gran parte a que el código como el siguiente bien definido:

int* begin, * end; 
int v[10]; 

begin = &v[0]; 
end = &v[10]; 

Esa última línea es técnicamente válida en C89 (y en C++), pero está permitido en C99 debido a esta regla. Fue un cambio relativamente menor que hizo que una construcción comúnmente utilizada estuviera bien definida.

Como no puede realizar operaciones aritméticas en un puntero nulo, su ejemplo (&s->b) no será válido de todos modos.

En cuanto a por qué hay esta "inconsistencia", solo puedo adivinar. Es probable que nadie haya pensado en hacerlo coherente o que nadie haya visto un caso de uso convincente para esto. Es posible que esto se haya considerado y finalmente se haya rechazado. No hay comentarios sobre la reducción &* en the Rationale. Es posible que pueda encontrar información definitiva en the WG14 papers, pero desafortunadamente parecen estar bastante mal organizados, por lo que pescarlos puede ser tedioso.

+0

Tomé punteros nulos de los ejemplos, ya que nunca fueron realmente lo que me preocupaba. –

+0

No veo cómo 'NULL' entra en juego en absoluto aquí. Además, para la aritmética del puntero (siempre que no se evalúe el objeto no existente) se puede usar el elemento justo después de una matriz. AFAIR, esto se menciona en varios lugares de la norma. –

+0

@Jens: los ejemplos originales en la pregunta usaron el puntero 'NULL' y no se puede realizar una aritmética bien definida en un puntero nulo. Puede obtener un puntero al "elemento" unívoco, pero no puede desreferenciarlo. Para 'int v [10];', solo en C99 es legal usar '& v [10]' o '& * (v + 10)'; en C++ y C90 dicho código produce formalmente un comportamiento indefinido. –

1

Creo que el compilador puede elegir empacar de diferentes maneras, posiblemente agregando relleno entre los miembros de una estructura para aumentar la velocidad de acceso a la memoria. Esto significa que no puede estar seguro de decir que b será siempre sea un desplazamiento de 4 de distancia. El valor único no tiene el mismo problema.

Además, es posible que el compilador no conozca el diseño de una estructura en la memoria durante la fase de optimización, evitando así cualquier tipo de optimización relacionada con los accesos a miembros de estructura y los lanzamientos de punteros posteriores.


edición:

tengo otra teoría ...

muchas veces el compilador optimizará el árbol de sintaxis abstracta justo después de análisis léxico y análisis sintáctico. Esto significa que encontrará cosas como operadores que cancelan y expresiones que evalúan a una constante y reducen esas secciones del árbol a un nodo. Esto también significa que la información sobre las estructuras no está disponible. más tarde pasa la optimización que ocurre después de que la generación de algunos códigos pueda tener esto en cuenta porque tienen información adicional, pero para cosas como recortar el AST, esa información aún no está allí.

+1

No puede estar seguro de que siempre será un desplazamiento de 4, pero para que una 'estructura 'sea útil, puede estar seguro de que será una compensación constante. Y usé un 'int' seguido de un' short', así que dudo que haya un compilador que necesite poner relleno entre ellos. –

+0

"Además, el compilador puede no conocer el diseño de una estructura en la memoria durante la fase de optimización ..." Parece una información bastante esencial para que un optimizador tenga. –

+0

, creo que también dependerá de cómo se escriba el compilador. El estándar especifica reglas sobre cómo debería funcionar normalmente, pero establecer un indicador de optimización probablemente funciona como usted dice.Mi suposición es que los escritores de la norma no querían imponer demasiada optimización. –

2

Creo que la regla no se ha agregado para fines de optimización (¿qué significa que la regla de si no?) Sino para permitir &t[sizeof(t)/sizeof(*t)] y &*(t+sizeof(t)/sizeof(*t)) que sería un comportamiento indefinido sin ella (escribiendo tales cosas directamente puede parecer tonto, pero agrega una o dos capas de macros y puede tener sentido). No veo un caso donde la carcasa especial & p-> m traería tal beneficio. Tenga en cuenta que, como señaló James, &p[10] con p un puntero nulo es aún un comportamiento indefinido; &p->m con p un puntero nulo de igual forma se han quedado inválidos (y debo admitir que no veo ningún uso cuando p es el puntero nulo).

+0

El uso obvio (en mi humilde opinión) cuando 'p = NULL' es la implementación hacky de la macro' offsetof', que se basa en '& ((struct t *) 0) -> m' en funcionamiento. Sin embargo, podría cambiarse fácilmente a '1' (o un valor de puntero válido dependiente del compilador como, por ejemplo, la pila) en lugar de' 0', y aunque probablemente no sea probable que le proporcione una buena 'struct' valores que debe darle el valor correcto de compensación. –

+0

@Chris: hace un siglo, tenía un compilador de C estándar que definía offsetof() en términos de dirección 0 y luego daba volcados centrales o errores de compilación (no recuerdo cuál, ahora) cuando se usó. Terminé pirateando el encabezado del sistema y usé 1024 como una dirección en lugar de 0; eso funcionó bien (1024) está lo suficientemente alineado como para no dar problemas, a diferencia de 1. –

+0

Excepto por las matrices de caracteres, '& t [sizeof (t)]' va mucho más allá del final del objeto asignado. –

Cuestiones relacionadas