c++ - ¿Por qué los punteros de función pueden ser `constexpr`?
function-pointers (4)
¿Cómo sabe el compilador en qué parte de la memoria estará la raíz cuadrada antes de ejecutar el programa?
La cadena de herramientas llega a decidir dónde pone las funciones.
¿Es porque la dirección es relativa a otra dirección en la memoria?
Si el programa producido es relocatable o independiente de la posición, entonces sí, ese es el caso. Si el programa no es ninguno, entonces la dirección puede ser absoluta.
¿Por qué estarían disponibles exactamente los mismos puntos de memoria la próxima vez que se ejecute el programa?
Porque el espacio de memoria es virtual .
¿Cómo sabe el compilador en qué parte de la memoria estará la raíz cuadrada antes de ejecutar el programa? Pensé que la dirección sería diferente cada vez que se ejecuta el programa, pero esto funciona:
constexpr double(*fp)(double) = &sqrt;
cout << fp(5.0);
¿Es porque la dirección es relativa a otra dirección en la memoria? No lo creo porque el valor de fp
es grande: 0x720E1B94.
El valor de la dirección es asignado por un enlazador, por lo que el compilador no conoce el valor exacto de la dirección.
cout << fp(5.0);
Esto funciona porque se evalúa en tiempo de ejecución después de que se haya resuelto la dirección exacta.
En general, no puede utilizar el valor real (dirección) del puntero constexpr
porque no se conoce en tiempo de compilación.
El lenguaje de programación C ++ de Bjarne Stroustrup menciona la 4ª edición :
10.4.5 Expresiones de constantes de direcciones
La dirección de un objeto asignado estáticamente (§6.4.2), como una variable global, es una constante. Sin embargo, su valor es asignado por el enlazador, en lugar del compilador, por lo que el compilador no puede saber el valor de tal constante de dirección. Eso limita el rango de expresiones constantes de puntero y tipo de referencia. Por ejemplo:
constexpr const char∗ p1 = "asdf"; constexpr const char∗ p2 = p1; // OK constexpr const char∗ p2 = p1+2; // error : the compiler does not know the value of p1 constexpr char c = p1[2]; // OK, c==’d’; the compiler knows the value pointed to by p1
En el momento de la compilación, el compilador no conoce la dirección de sqrt
. Sin embargo, no puede hacer nada en tiempo de compilación con un puntero de función constexpr que le permita acceder a la dirección de ese puntero. Por lo tanto, un puntero de función en tiempo de compilación se puede tratar como un valor opaco.
Y como no puede cambiar una variable constexpr después de que se haya inicializado, cada puntero de función constexpr puede reducirse a la ubicación de una función específica.
Si hicieras algo como esto:
using fptr = float(*)(float);
constexpr fptr get_func(int x)
{
return x == 3 ? &sqrtf : &sinf;
}
constexpr fptr ptr = get_func(12);
El compilador puede detectar exactamente qué función get_func
devolverá para cualquier valor de tiempo de compilación en particular. Así que get_func(12)
reduce a &sinf
. Así que cualquier cosa que compile y get_func(12)
es exactamente lo que get_func(12)
compilaría.
Es sencillo.
Considere cómo el compilador conoce la dirección para llamar en este código:
puts("hey!");
El compilador no tiene idea de la ubicación de los puts
, y tampoco agrega una búsqueda en tiempo de ejecución (eso sería bastante malo para el rendimiento, aunque en realidad es lo que deben hacer los métodos virtuales de las clases). La posibilidad de tener una versión diferente de la biblioteca dinámica en tiempo de ejecución (sin mencionar la aleatorización del diseño del espacio de direcciones, incluso si es exactamente el mismo archivo de la biblioteca) hace que el enlazador de la cadena de herramientas de tiempo de compilación tampoco lo sepa.
Así que depende del enlazador dinámico para corregir la dirección, cuando inicia el programa binario compilado. Esto se llama reubicación .
Exactamente lo mismo sucede con su constexpr
: el compilador agrega cada lugar en el código usando esta dirección a la tabla de reubicación , y luego el enlazador dinámico hace su trabajo cada vez que se inicia el programa.