Domanda

Mi chiedo solo: quando aggiungo la limitazione a un puntatore, dico al compilatore che il puntatore non è un alias per un altro puntatore. Supponiamo che io abbia una funzione come:

// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
     for (size_t i = 0; i < size; ++i)
     {
         result [i] = a [0] * b [i];
     }
}

Se il compilatore deve presumere che il risultato possa sovrapporsi con a , deve recuperarlo ogni volta. Ma, poiché a è contrassegnato con const , il compilatore potrebbe anche supporre che a sia corretto, e quindi recuperarlo una volta va bene.

La domanda è, in una situazione come questa, qual è il modo consigliato di lavorare con limit? Sicuramente non voglio che il compilatore recuperi a ogni volta, ma non sono riuscito a trovare buone informazioni su come dovrebbe funzionare restring .

È stato utile?

Soluzione

Il tuo puntatore è const, dicendo a chiunque chiami la tua funzione che non toccherai i dati puntati attraverso quella variabile. Sfortunatamente, il compilatore non saprà ancora se il risultato è un alias dei puntatori const. È sempre possibile utilizzare un puntatore non const come puntatore const. Ad esempio, molte funzioni prendono un puntatore const char (cioè stringa) come parametro, ma puoi, se lo desideri, passargli un puntatore non const, la funzione ti sta semplicemente promettendo che non userà quel particolare puntatore per cambiare qualcosa.

Fondamentalmente, per avvicinarti alla tua domanda, dovresti aggiungere un limite a aeb per "promettere" al compilatore che chiunque usa questa funzione non passerà di conseguenza come alias a a o b. Supponendo, ovviamente, che tu sia in grado di fare una simile promessa.

Altri suggerimenti

Tutti qui sembrano molto confusi. Non esiste un singolo esempio di puntatore const in nessuna risposta finora.

La dichiarazione const float * a non è non un puntatore const, è un archivio const. Il puntatore è ancora modificabile. float * const a è un puntatore const a un float mutabile.

Quindi la domanda dovrebbe essere, c'è qualche punto in float * const restringe un (o const float * const limita un se preferisci).

Sì, è necessario limitare. Puntatore a const non significa che nulla può modificare i dati, solo che non è possibile modificarli tramite quel puntatore .

const è principalmente solo un meccanismo per chiedere al compilatore di aiutarti a tenere traccia di ciò che vuoi che le funzioni possano essere modificate. const non è una promessa per il compilatore che una funzione in realtà non modificherà i dati .

A differenza di restring , l'uso del puntatore a const per i dati mutabili è fondamentalmente una promessa per gli altri umani, non per il compilatore. Eliminare const dappertutto non porterà a comportamenti errati da parte dell'ottimizzatore (AFAIK), a meno che tu non provi a modificare qualcosa che il compilatore ha messo nella memoria di sola lettura (vedi sotto const statico ). Se il compilatore non riesce a vedere la definizione di una funzione durante l'ottimizzazione, deve presumere che elimini const e modifichi i dati attraverso quel puntatore (cioè che la funzione non rispetti const ness del suo puntatore args).

Il compilatore sa che static const int foo = 15; non può cambiare, tuttavia, e incorporerà il valore in modo affidabile anche se si passa il suo indirizzo a funzioni sconosciute. (Ecco perché static const int foo = 15; non è più lento di #define foo 15 per un compilatore ottimizzante. Buoni compilatori lo ottimizzeranno come un constexpr quando possibile.)


Ricorda che restringi è una promessa per il compilatore che le cose a cui accedi attraverso quel puntatore non si sovrappongono a nient'altro . Se ciò non è vero, la tua funzione non farà necessariamente quello che ti aspetti. per esempio. non chiamare foo_restrict (buf, buf, buf) per operare sul posto.

Nella mia esperienza (con gcc e clang), restring è principalmente utile sui puntatori che memorizzi. Non fa male mettere anche limitare sui puntatori sorgente, ma di solito ottieni tutto il meglio possibile inserendolo solo sui puntatori di destinazione, se tutto i negozi che la tua funzione fa tramite i puntatori limit .

Se hai delle chiamate di funzione nel tuo ciclo, restring su un puntatore sorgente permette a clang (ma non gcc) di evitare un ricaricamento. Vedi questi test- casi sull'esploratore del compilatore Godbolt , in particolare questo:

void value_only(int);  // a function the compiler can't inline

int arg_pointer_valonly(const int *__restrict__ src)
{
    // the compiler needs to load `*src` to pass it as a function arg
    value_only(*src);
    // and then needs it again here to calculate the return value
    return 5 + *src;  // clang: no reload because of __restrict__
}

gcc6.3 (destinato all'ABI SysV x86-64) decide di mantenere src (il puntatore) in un registro preservato dalla chiamata attraverso la chiamata di funzione e ricaricare * src dopo la chiamata. O gli algoritmi di gcc non hanno individuato questa possibilità di ottimizzazione, o hanno deciso che non ne valeva la pena, o gli sviluppatori di gcc di proposito non lo hanno implementato perché pensano che non sia sicuro. IDK che. Ma dal momento che Clang lo fa, immagino che sia probabilmente legale secondo lo standard C11.

clang4.0 lo ottimizza per caricare * src una sola volta e mantenere il valore in un registro protetto dalle chiamate attraverso la chiamata di funzione. Senza restring , non lo fa, perché la funzione chiamata potrebbe (come effetto collaterale) modificare * src attraverso un altro puntatore.

Il chiamante di questa funzione potrebbe aver passato l'indirizzo di una variabile globale, ad esempio . Ma qualsiasi modifica di * src diversa da tramite il puntatore src violerebbe la promessa che restring fatta al compilatore. Dal momento che non passiamo src a valonly () , il compilatore può presumere che non modifichi il valore.

Il dialetto GNU di C consente di utilizzare __attribute __ (( pure)) o __attribute __ ((const)) per dichiarare che una funzione non ha effetti collaterali , consentendo questa ottimizzazione senza limitarsi , ma non esiste un equivalente portatile in ISO C11 (AFAIK). Naturalmente, consentire la funzione inline (inserendola in un file di intestazione o usando LTO) consente anche questo tipo di ottimizzazione ed è molto meglio per le piccole funzioni, specialmente se chiamate all'interno di loop.


I compilatori sono generalmente piuttosto aggressivi nel fare ottimizzazioni che lo standard consente, anche se sono sorprendenti per alcuni programmatori e rompono alcuni codici non sicuri esistenti che funzionavano. (C è così portatile che molte cose sono un comportamento indefinito nello standard di base; le implementazioni più belle definiscono il comportamento di molte cose che lo standard lascia come UB.) C non è un linguaggio in cui è sicuro lanciare codice nel compilatore fino a quando fa quello che vuoi, senza controllare che lo stai facendo nel modo giusto (senza overflow di numeri interi con segno, ecc.)


Se guardi l'output asm x86-64 per compilare la tua funzione (dalla domanda), puoi facilmente vedere la differenza. L'ho messo su

Nella norma C-99 (ISO / IEC 9899: 1999 (E)) ci sono esempi di const * restring , ad esempio, nella sezione 7.8.2.3:

Le funzioni strtoimax e strtoumax

Sinossi

#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
                   char ** restrict endptr, int base);
--- snip ---

Pertanto, se si presume che lo standard non fornirebbe un esempio del genere se const * fosse ridondante in * restringi , in realtà non sono ridondanti.

Come indicato nella risposta precedente, devi aggiungere " restringi " ;. Volevo anche commentare il tuo scenario che il risultato "potrebbe sovrapporsi a un". Questo non è l'unico motivo per cui il compilatore rileverà che "a" potrebbe cambiare. Potrebbe anche essere cambiato da un altro thread che ha un puntatore a " a " ;. Pertanto, anche se la tua funzione non ha modificato alcun valore, il compilatore supporrà comunque che "a" potrebbe cambiare.

scroll top