Esecuzione di funzioni in fase di compilazione in D Esecuzione di funzioni in fase di compilazione in D

Esecuzione di funzioni in fase di compilazione in D

Questo post descriverà le basi di una funzionalità molto potente del linguaggio di programmazione D: la Compile-time Function Execution

La Compile-time Function Execution (CTFE) consente di valutare completamente funzioni complesse in fase di compilazione, indipendentemente dai livelli di ottimizzazione.

Esecuzione di funzioni in fase di compilazione in D
La Compile-time Function Execution (CTFE) – bajo.it

Ottimizzazioni C
Se sei un programmatore C medio, sai che il codice semplice può essere considerato affidabile per essere valutato in fase di compilazione grazie agli ottimizzatori. Ad esempio, se scrivi qualcosa del tipo:

void square(int x) { return x*x; }

void foo(void)
{
int k = square(32);
/* … */
}

ti fidi del fatto che il tuo compilatore valuti il ​​quadrato in fase di compilazione, quando le ottimizzazioni sono attive. Quando le cose si fanno più complicate:

int factorial(int x)
{
int result = x;
while (–x)
result *= x;
return result;
}

void foo(void)
{
int k = factorial(8);
/* … */
}

I programmatori C diventano immediatamente meno sicuri di ciò che accadrà in fase di esecuzione. Ad esempio, diresti che il tuo compilatore è in grado di espandere il codice sopra in fase di compilazione o no? In realtà la risposta è “sì” in questo caso particolare (a meno che non si utilizzi un compilatore molto vecchio), ma il punto è comunque valido: questo non è codice C che si scriverebbe se si vuole essere sicuri che l’intero calcolo sia corretto. piegato in fase di compilazione.

C’è anche un altro problema: poiché il linguaggio non impone che il valore venga piegato (e in effetti, non viene piegato quando le ottimizzazioni sono disabilitate), non è possibile crearne una costante, ad esempio assegnandola a una variabile const .

Quando le cose si fanno complicate

Ora proviamo con una soluzione (molto ingenua e semplice) del problema n. 1 del Progetto Euler:

#include <stdio.h>

int euler1(int max)
{
int i,res=0;
for (i=1; i<max; i++)
{
if ((i % 3) == 0 || (i % 5) == 0)
res += i;
}
return res;
}

int main()
{
int r10 = euler1(10);
int r1000 = euler1(1000);
printf(“%d %d\n”, r10, r1000);
return 0;
}

Questo programma calcola semplicemente la somma di tutti i divisori di 3 o 5 inferiori a 1000. Ma se guardi il codice generato con GCC sotto -O3, vedrai che i risultati effettivi non vengono calcolati in fase di compilazione, ma piuttosto calcolati in fase di esecuzione . Credo che qualsiasi programmatore C medio sarebbe d’accordo sul fatto che non dovremmo aspettarci che questo codice venga piegato in fase di compilazione.

Ora, incontra il codice D equivalente:

int euler1(int max)
{
int i,res=0;
for (i=1; i<max; i++)
{
if ((i % 3) == 0 || (i % 5) == 0)
res += i;
}
return res;
}

int main()
{
int r10 = euler1(10);
int r1000 = euler1(1000);
printf(“%d %d\n”, r10, r1000);
return 0;
}

Già visto? Sì, è esattamente lo stesso, a parte l’istruzione include iniziale che non è richiesta (in realtà, non esiste un preprocessore in D e i moduli si riferiscono tra loro con l’istruzione import, ma printf è integrato). Naturalmente, l’esempio sopra è stato realizzato manualmente per renderlo valido sia in codice C che D, ma essendo D un’evoluzione del C, la sintassi di base è la stessa.