Entonces, recientemente me interesé en qué tan bien el compilador ( gcc (GCC) 4.8.3 es el que está en cuestión) está optimizando punteros y punteros.
Inicialmente, creé un entero simple y un puntero de entero y realicé operaciones en él para poder imprimirlo. Como era de esperar, todas las operaciones que estaban codificadas de forma rígida se optimizaron, a través de un puntero desreferenciado o no.
call __main leaq .LC0(%rip), %rcx movl $1, %edx call printfE incluso después de crear una función que toma un puntero int, lo desreferencia y lo cambia, aún estaba perfectamente optimizado.
call __main leaq .LC0(%rip), %rcx movl $-1, %edx call printfAhora, cuando traté mi puntero como un vacío e hice cambios convirtiéndolo en char y desreferenciandolo, en realidad todavía se optimizó perfectamente (una llamada mov 'extra' ya que inicialmente lo traté como un valor de 8 bytes, y luego como un 1 valor de byte para la desreferenciación del puntero)
call __main movl $4, 44(%rsp) movb $2, 44(%rsp) leaq .LC0(%rip), %rcx movl 44(%rsp), %eax leal 1(%rax), %edx call printfAsí que a mi (s) pregunta (s):
¿Qué tan consistente es la optimización del compilador con respecto a la desreferenciación de punteros? ¿Cuáles serían algunos casos en los que optaría por ser conservador?
Si todos mis punteros en un proyecto se declararan con la palabra clave restrict, ¿puedo confiar en que estaría tan bien optimizado como si 'no se usaran punteros en absoluto'?
(suponiendo que no haya casos volatile )
Ps¹.: Soy consciente de que el compilador generalmente hace un trabajo bastante bueno, y que un programador que se preocupa por ayudar al compilador en optimizaciones menores es, en general, improductivo (como muchos señalan en las respuestas de stackoverflow a preguntas relacionadas con la optimización). Sin embargo, todavía tengo curiosidad con respecto al asunto.
Ps².: gcc -O3 -S -c main.c fue el comando utilizado para generar el código ensamblador
Código C: (según lo solicitado)
1:
#include <stdio.h> int main (void) { int a = 4; int *ap = &a; *ap = 0; a += 1; printf("%d\n", a); return 0; }2:
#include <stdio.h> void change(int *p) { *p -= 2; } int main (void) { int a = 4; int *ap = &a; *ap = 0; change(ap); a += 1; printf("%d\n", a); return 0; }3:
#include <stdio.h> void change(void *p) { *((char*)p) += 2; } int main (void) { int a = 4; void *ap = (void*) &a; *((char*)(ap)) = 0; change(ap); a += 1; printf("%d\n", a); return 0; }LLVM y GCC emiten código de formulario de asignación única estática como parte del análisis de optimización. Una de las propiedades útiles del código SSA es que muestra con precisión el flujo de influencia para la asignación, es decir, sabe qué asignaciones llevan a otras asignaciones y, por lo tanto, puede detectar qué valores pueden influir en todos los demás.
La primera cadena de influencia se parece a
a 1 -> constante(0) -> ap -> a 2
La segunda: a 1 -> constante(0) -> ap -> p -> a 2
El tercero es bastante similar al segundo. (Lo siento, esta notación es bastante inventada, pero espero que ilustre mi punto).
Debido a que es bastante simple demostrar que la influencia de a en ap es determinista, se sentirá libre de desreferenciar 'antes' y combinar las instrucciones en una sola (aunque en los primeros dos casos esta no es la declaración más precisa ya que la constante sobrescribe la referencia original y permite que el compilador demuestre que la asignación original no fluye hasta el final del código.
Hacer que el compilador sea más conservador acerca de la desreferenciación implicaría volverse lo suficientemente complicado como para escapar de la comprensión del compilador (creo que es difícil en un programa estático) o más probablemente hacer que el compilador invoque una función phi en el proceso de SSA (en términos sencillos, para hacer que la asignación se vea influenciada por múltiples asignaciones previas) de una manera no determinista.
La palabra clave restrict tiene el propósito de insinuar al compilador que dos punteros son diferentes. Esto no restringiría el uso de la desreferencia en tiempo de ejecución si el código que produjo ese puntero todavía tenía una fuente no determinista (por ejemplo, si los datos creados en tiempo de ejecución influyeron en la elección de qué valor de puntero se desreferencia, creo que esto podría suceder si un serializado puntero fue enviado al programa desde una fuente externa?)