Parola chiave "registra" in C?


273

Cosa fa la registerparola chiave in linguaggio C? Ho letto che viene utilizzato per l'ottimizzazione ma non è chiaramente definito in nessuno standard. È ancora pertinente e, in tal caso, quando lo useresti?


42
Cosa fa la parola chiave register in C? viene ignorato :)
bestsss

19
@bestsss Non completamente ignorato. Prova a ottenere un indirizzo di registervariabile.
qrdl

4
Quel codice che stai leggendo è vecchio youtube.com/watch?v=ibF36Yyeehw#t=1827
Colonnello Panic,

Risposte:


341

È un suggerimento per il compilatore che la variabile verrà utilizzata pesantemente e che, se possibile, si consiglia di conservarla in un registro del processore.

Molti compilatori moderni lo fanno automaticamente e sono più bravi a sceglierli di noi umani.


18
Bene, ho sperimentato il registro per modificare le mie osservazioni ACM, e talvolta mi ha davvero aiutato. Ma devi essere davvero attento, perché le scelte sbagliate riducono le prestazioni.
ypnos,

81
Un buon motivo per non usare 'register': non puoi prendere l'indirizzo di una variabile dichiarata 'register'
Adam Rosenfield,

23
Nota che alcuni / molti compilatori ignoreranno completamente la parola chiave register (che è perfettamente legale).
Euro Micelli,

5
ypnos: In realtà la velocità di una soluzione per i problemi ACPC ICPC dipende molto più dalla scelta dell'algoritmo che da tali micro-ottimizzazioni. Il limite di tempo di 5 secondi è in genere sufficiente per una soluzione corretta, soprattutto quando si utilizza C anziché Java.
Joey,

66
@Euro: probabilmente lo sai, ma solo per essere esplicito, il compilatore è necessario per impedire che registervenga preso l'indirizzo di una variabile; questo è l' unico effetto obbligatorio della registerparola chiave. Anche questo è sufficiente per migliorare le ottimizzazioni, perché diventa banale dire che la variabile può essere modificata solo all'interno di questa funzione.
Dale Hagglund,

69

Sono sorpreso che nessuno abbia menzionato che non puoi prendere un indirizzo per la variabile register, anche se il compilatore decide di mantenere la variabile in memoria piuttosto che nel registro.

Quindi usando registerte non vinci nulla (comunque il compilatore deciderà da solo dove posizionare la variabile) e perderà l' &operatore - nessuna ragione per usarla.


94
C'è una ragione in realtà. Il semplice fatto che non si possa prendere un indirizzo della variabile produce alcune opportunità di ottimizzazione: il compilatore può dimostrare che la variabile non sarà modificata.
Alexandre C.

8
I compilatori sono notoriamente terribili nel dimostrare che l'aliasing non si verifica in casi non banali, quindi registerè utile per questo anche se il compilatore non lo inserisce in un registro.
Miles Rout

2
@AlexandreC, Miles, compilatori sono perfettamente a posto nel verificare se e viene presa una variabile ovunque. Quindi, indipendentemente da altre difficoltà nel rilevare l'aliasing, ribadire che non ti compra nulla. Quando K + R ha creato per la prima volta C, era davvero utile sapere in anticipo che non sarebbe stato usato, dal momento che quel compilatore ha effettivamente preso la decisione di allocazione del registro nel vedere la dichiarazione, prima di guardare il seguente codice. Ecco perché esiste il divieto. La parola chiave 'register' ora è sostanzialmente obsoleta.
Greggo,

25
Con questa logica constè anche inutile poiché non vince nulla, perdi solo la possibilità di cambiare una variabile. registerpotrebbe essere usato per assicurarsi che nessuno prenda l'indirizzo di una variabile in futuro senza pensare. Non ho mai avuto motivo di usare registerperò.
Tor Klingberg,

34

Indica al compilatore di provare a utilizzare un registro CPU, anziché RAM, per memorizzare la variabile. I registri sono nella CPU e sono molto più veloci di accesso rispetto alla RAM. Ma è solo un suggerimento per il compilatore e potrebbe non essere seguito.


8
Vale la pena aggiungere per le persone che usano C ++, C ++ ti consente di prendere l'indirizzo di una variabile di registro
Will

5
@Will: ... ma il compilatore probabilmente finirà per ignorare la parola chiave come risultato. Vedi la mia risposta
bwDraco,

Sì, sembra che "register" sia un placebo in C ++, è lì solo per consentire la compilazione del codice C come C ++. E non avrebbe molto senso proibire & var mentre permetti di passarlo per riferimento, o const-reference, e senza pass-by-reference hai seriamente rotto C ++.
Greggo,

22

So che questa domanda riguarda C, ma la stessa domanda per C ++ è stata chiusa come un duplicato esatto di questa domanda. Pertanto, questa risposta potrebbe non essere valida per C.


L'ultima bozza dello standard C ++ 11, N3485 , dice questo nel 7.1.1 / 3:

Un registeridentificatore è un suggerimento per l'implementazione che la variabile così dichiarata verrà utilizzata pesantemente. [ nota: il suggerimento può essere ignorato e nella maggior parte delle implementazioni verrà ignorato se viene preso l'indirizzo della variabile. Questo uso è sconsigliato ... nota -end ]

In C ++ (ma non in C), lo standard non afferma che non è possibile prendere l'indirizzo di una variabile dichiarata register; tuttavia, poiché una variabile memorizzata in un registro della CPU per tutta la sua durata non ha una posizione di memoria associata, il tentativo di prendere il suo indirizzo non sarebbe valido e il compilatore ignorerà la registerparola chiave per consentire di prendere l'indirizzo.


17

Non è rilevante da almeno 15 anni poiché gli ottimizzatori prendono decisioni migliori al riguardo di quanto tu possa fare. Anche quando era rilevante, aveva molto più senso su un'architettura CPU con molti registri, come SPARC o M68000 rispetto a Intel con la sua scarsità di registri, molti dei quali riservati dal compilatore per i propri scopi.


13

In realtà, register dice al compilatore che la variabile non si alias di nient'altro nel programma (nemmeno di char).

Questo può essere sfruttato dai compilatori moderni in una varietà di situazioni e può aiutare un po 'il compilatore in un codice complesso - in un codice semplice i compilatori possono capirlo da soli.

Altrimenti, non serve a nulla e non viene utilizzato per l'allocazione del registro. Di solito non comporta un peggioramento delle prestazioni per specificarlo, purché il compilatore sia abbastanza moderno.


"dice al compilatore .." no, non lo fa. Tutte le variabili automatiche hanno quella proprietà, a meno che tu non prenda il suo indirizzo e lo usi in modi che superano determinati usi analizzabili. Quindi, il compilatore lo sa dal codice, indipendentemente dal fatto che si usi la parola chiave register. Accade così che la parola chiave 'register' renda illegale scrivere un tale costrutto, ma se non si usa la parola chiave e non si prende l'indirizzo in questo modo, il compilatore sa ancora che è sicuro. Tali informazioni sono cruciali per l'ottimizzazione.
Greggo,

1
@greggo: Peccato che registervieta di prendere l'indirizzo, poiché altrimenti potrebbe essere utile far conoscere ai compilatori i casi in cui un compilatore sarebbe in grado di applicare le ottimizzazioni del registro nonostante l'indirizzo di una variabile venga passato a una funzione esterna (la variabile dovrebbe essere scaricato nella memoria per quella particolare chiamata , ma una volta restituita la funzione, il compilatore potrebbe nuovamente trattarla come una variabile il cui indirizzo non era mai stato preso).
supercat,

@supercat Penso che sarebbe ancora una conversazione molto complicata con il compilatore. Se è quello che vuoi dire al compilatore, puoi farlo copiando la prima variabile in una seconda che non ha '&' su di essa, e quindi non usare mai più la prima.
Greggo,

1
@greggo: dicendo che se barè una registervariabile, un compilatore può, a sua svago sostituire foo(&bar);con int temp=bar; foo(&temp); bar=temp;, ma prendendo l'indirizzo barsarebbe vietato nella maggior parte degli altri contesti non sembrerebbe come una regola troppo complicato. Se la variabile potesse altrimenti essere tenuta in un registro, la sostituzione ridurrebbe il codice. Se la variabile dovesse essere comunque mantenuta nella RAM, la sostituzione renderebbe il codice più grande. Lasciare la questione se effettuare la sostituzione fino al compilatore porterebbe a un codice migliore in entrambi i casi.
supercat,

1
@greggo: consentire una registerqualifica per le variabili globali, indipendentemente dal fatto che il compilatore consenta o meno l'indirizzo, consentirebbe alcune ottimizzazioni nei casi in cui una funzione incorporata che utilizza una variabile globale viene chiamata ripetutamente in un ciclo. Non riesco a pensare ad altro modo per lasciare che quella variabile sia tenuta in un registro tra iterazioni cicliche - vero?
supercat,

13

Ho letto che viene utilizzato per l'ottimizzazione ma non è chiaramente definito in nessuno standard.

In effetti è chiaramente definito dallo standard C. Citando la bozza N1570 sezione 6.7.1 paragrafo 6 (altre versioni hanno la stessa formulazione):

Una dichiarazione di un identificatore per un oggetto con identificatore della classe di memoria registersuggerisce che l'accesso all'oggetto sia il più veloce possibile. La misura in cui tali suggerimenti sono efficaci è definita dall'implementazione.

L' &operatore unario non può essere applicato a un oggetto definito con registere registernon può essere utilizzato in una dichiarazione esterna.

Ci sono alcune altre regole (abbastanza oscure) che sono specifiche per registergli oggetti qualificati:

  • La definizione di un oggetto array con registerha un comportamento indefinito.
    Correzione: è legale definire un oggetto array con register, ma non è possibile fare nulla di utile con tale oggetto (l'indicizzazione in un array richiede di prendere l'indirizzo del suo elemento iniziale).
  • L' _Alignasidentificatore (nuovo in C11) non può essere applicato a tale oggetto.
  • Se il nome del parametro passato alla va_startmacro è register-qualificato, il comportamento non è definito.

Potrebbero essercene alcuni altri; scarica una bozza dello standard e cerca "registrati" se sei interessato.

Come suggerisce il nome, il significato originale di registerera richiedere la memorizzazione di un oggetto in un registro CPU. Ma con miglioramenti nell'ottimizzazione dei compilatori, questo è diventato meno utile. Le versioni moderne dello standard C non si riferiscono ai registri della CPU, perché non (più devono) presumere che esista una cosa del genere (ci sono architetture che non usano i registri). La saggezza comune è che l'applicazione registera una dichiarazione di oggetto ha maggiori probabilità di peggiorare il codice generato, poiché interferisce con l'allocazione del registro del compilatore. Potrebbero esserci ancora alcuni casi in cui è utile (ad esempio, se sai davvero con quale frequenza si accederà a una variabile e la tua conoscenza è migliore di ciò che un moderno compilatore ottimizzante può capire).

Il principale effetto tangibile di registerè che impedisce qualsiasi tentativo di prendere l'indirizzo di un oggetto. Questo non è particolarmente utile come suggerimento per l'ottimizzazione, poiché può essere applicato solo alle variabili locali e un compilatore di ottimizzazione può vedere da solo che l'indirizzo di tale oggetto non viene preso.


quindi il comportamento di questo programma non è veramente definito secondo lo standard C? È ben definito in C ++? Penso che sia ben definito in C ++.
Distruttore

@Destruttore: perché non dovrebbe essere definito? Non esiste registerun oggetto array qualificato, se è quello che stai pensando.
Keith Thompson,

Oh scusa ho dimenticato di scrivere la parola chiave register nella dichiarazione dell'array in main (). È ben definito in C ++?
Distruttore,

Ho sbagliato a definire gli registeroggetti array; vedere il primo punto elenco aggiornato nella mia risposta. È legale definire un tale oggetto, ma non puoi farci niente. Se aggiungi registeralla definizione di snel tuo esempio , il programma è illegale (una violazione di vincolo) in C. C ++ non pone le stesse restrizioni register, quindi il programma sarebbe C ++ valido (ma l'utilizzo registersarebbe inutile).
Keith Thompson,

@KeithThompson: la registerparola chiave potrebbe servire a uno scopo utile se fosse legale prendere l'indirizzo di tale variabile ma solo nei casi in cui la semantica non sarebbe influenzata copiando la variabile in un temporaneo quando viene preso il suo indirizzo e ricaricandolo dal temporaneo al punto successivo della sequenza. Ciò consentirebbe ai compilatori di supporre che la variabile possa essere conservata in modo sicuro in un registro attraverso tutti gli accessi al puntatore, a condizione che sia svuotata in qualsiasi posizione venga preso il suo indirizzo.
supercat,

9

Ora della favola!

C, come linguaggio, è un'astrazione di un computer. Ti permette di fare cose, in termini di ciò che fa un computer, cioè manipolare la memoria, fare matematica, stampare cose, ecc.

Ma C è solo un'astrazione. E alla fine, ciò che sta estraendo da te è il linguaggio Assembly. Assembly è il linguaggio che legge una CPU e, se lo usi, fai le cose in termini di CPU. Cosa fa una CPU? Fondamentalmente, legge dalla memoria, fa matematica e scrive nella memoria. La CPU non si limita a fare matematica sui numeri in memoria. Innanzitutto, devi spostare un numero dalla memoria alla memoria all'interno della CPU chiamata registro. Una volta terminato di fare tutto ciò che è necessario fare su questo numero, è possibile spostarlo nuovamente nella normale memoria di sistema. Perché usare la memoria di sistema? I registri sono in numero limitato. Si ottengono solo un centinaio di byte nei moderni processori e i vecchi processori popolari erano ancora più incredibilmente limitati (il 6502 aveva 3 registri a 8 bit per l'uso gratuito). Quindi, l'operazione matematica media è simile a:

load first number from memory
load second number from memory
add the two
store answer into memory

Molto di questo non è ... non matematica. Queste operazioni di caricamento e archiviazione possono richiedere fino alla metà del tempo di elaborazione. C, essendo un'astrazione di computer, liberò il programmatore dalla preoccupazione di usare e destreggiarsi tra i registri, e poiché il numero e il tipo variano tra i computer, C attribuisce la responsabilità dell'allocazione dei registri esclusivamente sul compilatore. Con un'eccezione.

Quando dichiari una variabile register, stai dicendo al compilatore "Yo, intendo che questa variabile venga usata molto e / o abbia vita breve. Se fossi in te, proverei a tenerlo in un registro." Quando lo standard C dice che i compilatori non devono realmente fare nulla, è perché lo standard C non sa per quale computer stai compilando, e potrebbe essere come il 6502 sopra, dove tutti e 3 i registri sono necessari solo per funzionare e non esiste un registro di riserva per conservare il numero. Tuttavia, quando dice che non puoi prendere l'indirizzo, è perché i registri non hanno indirizzi. Sono le mani del processore. Dal momento che il compilatore non deve fornirti un indirizzo e dal momento che non può mai avere un indirizzo, ora sono disponibili diverse ottimizzazioni per il compilatore. Potrebbe, diciamo, mantenere sempre il numero in un registro. Non non devo preoccuparmi di dove è archiviato nella memoria del computer (oltre alla necessità di recuperarlo di nuovo). Potrebbe anche punirlo in un'altra variabile, darlo a un altro processore, dargli una posizione che cambia, ecc.

tl; dr: variabili di breve durata che fanno molta matematica. Non dichiararne troppi contemporaneamente.


5

Stai scherzando con il sofisticato algoritmo di colorazione del grafico del compilatore. Questo è usato per l'allocazione del registro. Bene, soprattutto. Funziona come un suggerimento per il compilatore - è vero. Ma non ignorato nella sua interezza poiché non ti è permesso di prendere l'indirizzo di una variabile di registro (ricorda che il compilatore, ora in balia di te, proverà ad agire diversamente). Che in un certo senso ti sta dicendo di non usarlo.

La parola chiave è stata usata molto, molto tempo fa. Quando c'erano solo pochi registri che potevano contarli tutti usando il dito indice.

Ma, come ho detto, deprecato non significa che non puoi usarlo.


13
Alcuni dei vecchi hardware avevano più registri delle moderne macchine Intel. I conteggi dei registri non hanno nulla a che fare con l'età e tutto ciò che riguarda l'architettura della CPU.
SOLO IL MIO OPINIONE corretta,

2
@JUSTMYcorrectOPINION In effetti, X86 ne ha praticamente sei in tutto, lasciando al massimo 1 o 2 per dedicarsi al "registro". In effetti, dal momento che un sacco di codice è stato scritto o trasferito su una macchina povera di registro, sospetto che ciò abbia contribuito notevolmente alla parola chiave "register" che diventa un placebo - non ha senso suggerire i registri quando non ce ne sono. Eccoci più di 4 anni dopo e per fortuna x86_64 lo ha aumentato a 14, e anche ARM è una cosa importante adesso.
Greggo,

4

Solo una piccola demo (senza alcun scopo nel mondo reale) per il confronto: quando si rimuovono le registerparole chiave prima di ogni variabile, questo pezzo di codice impiega 3,41 secondi sul mio i7 (GCC), con register lo stesso codice completato in 0,7 secondi.

#include <stdio.h>

int main(int argc, char** argv) {

     register int numIterations = 20000;    

     register int i=0;
     unsigned long val=0;

    for (i; i<numIterations+1; i++)
    {
        register int j=0;
        for (j;j<i;j++) 
        {
            val=j+i;
        }
    }
    printf("%d", val);
    return 0;
}

2
Con gcc 4.8.4 e -O3, non ottengo alcuna differenza. Senza -O3 e 40000 iterazioni, ottengo forse 50 ms in meno su un tempo totale di 1,5 secondi, ma non l'ho eseguito abbastanza volte per sapere se era statisticamente significativo.
zstewart,

Non c'è alcuna differenza con CLANG 5.0, la piattaforma è AMD64. (Ho verificato l'uscita ASM.)
ern0

4

Ho testato la parola chiave del registro in QNX 6.5.0 utilizzando il seguente codice:

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

int main(int argc, char *argv[]) {
    uint64_t cps, cycle1, cycle2, ncycles;
    double sec;
    register int a=0, b = 1, c = 3, i;

    cycle1 = ClockCycles();

    for(i = 0; i < 100000000; i++)
        a = ((a + b + c) * c) / 2;

    cycle2 = ClockCycles();
    ncycles = cycle2 - cycle1;
    printf("%lld cycles elapsed\n", ncycles);

    cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
    printf("This system has %lld cycles per second\n", cps);
    sec = (double)ncycles/cps;
    printf("The cycles in seconds is %f\n", sec);

    return EXIT_SUCCESS;
}

Ho ottenuto i seguenti risultati:

-> 807679611 cicli trascorsi

-> Questo sistema ha 3300830000 cicli al secondo

-> I cicli in secondi sono ~ 0,244600

E ora senza registro int:

int a=0, b = 1, c = 3, i;

Ho ottenuto:

-> 1421694077 cicli scaduti

-> Questo sistema ha 3300830000 cicli al secondo

-> I cicli in secondi sono ~ 0.430700


2

Register notificherebbe al compilatore che il programmatore riteneva che questa variabile sarebbe stata scritta / letta abbastanza da giustificare la sua memorizzazione in uno dei pochi registri disponibili per l'uso variabile. La lettura / scrittura dai registri è in genere più rapida e può richiedere un set di codici operativi più piccolo.

Al giorno d'oggi, questo non è molto utile, poiché la maggior parte degli ottimizzatori dei compilatori sono migliori di te nel determinare se un registro debba essere usato per quella variabile e per quanto tempo.


2

Durante gli anni settanta, all'inizio del linguaggio C, è stata introdotta la parola chiave register per consentire al programmatore di dare suggerimenti al compilatore, dicendo che la variabile sarebbe stata usata molto spesso e che sarebbe saggio mantenere il valore in uno dei registri interni del processore.

Al giorno d'oggi, gli ottimizzatori sono molto più efficienti dei programmatori per determinare le variabili che hanno maggiori probabilità di essere tenuti nei registri e l'ottimizzatore non tiene sempre conto del suggerimento del programmatore.

Così tante persone a torto raccomandano di non usare la parola chiave register.

Vediamo perché!

La parola chiave register ha un effetto collaterale associato: non puoi fare riferimento (ottenere l'indirizzo di) una variabile del tipo di registro.

Le persone che consigliano agli altri di non usare i registri prendono erroneamente questo come ulteriore argomento.

Tuttavia, il semplice fatto di sapere che non è possibile prendere l'indirizzo di una variabile di registro, consente al compilatore (e al suo ottimizzatore) di sapere che il valore di questa variabile non può essere modificato indirettamente tramite un puntatore.

Quando a un certo punto del flusso di istruzioni, una variabile di registro ha il suo valore assegnato in un registro del processore e il registro non è stato utilizzato da quando si ottiene il valore di un'altra variabile, il compilatore sa che non è necessario ricaricare il valore della variabile in quel registro. Ciò consente di evitare costosi accessi inutili alla memoria.

Fai i tuoi test e otterrai significativi miglioramenti delle prestazioni nei tuoi loop più interni.

c_register_side_effect_performance_boost


1

Sui compilatori C supportati tenta di ottimizzare il codice in modo che il valore della variabile sia contenuto in un registro del processore effettivo.


1

Il compilatore Visual C ++ di Microsoft ignora il file register parola chiave quando è abilitata l'ottimizzazione globale dell'allocazione dei registri (il flag del compilatore / Oe).

Vedi registro Parola chiave su MSDN.


1

La parola chiave Register indica al compilatore di memorizzare la particolare variabile nei registri della CPU in modo che possa essere accessibile rapidamente. Dal punto di vista di un programmatore, la parola chiave register viene utilizzata per le variabili che sono fortemente utilizzate in un programma, in modo che il compilatore possa accelerare il codice. Anche se dipende dal compilatore se mantenere la variabile nei registri della CPU o nella memoria principale.


0

Register indica al compilatore di ottimizzare questo codice memorizzando quella particolare variabile nei registri quindi in memoria. è una richiesta al compilatore, il compilatore può o meno prendere in considerazione questa richiesta. È possibile utilizzare questa funzione nel caso in cui alcune delle variabili siano accessibili molto frequentemente. Ad esempio: un loop.

Un'altra cosa è che se si dichiara una variabile come registro, non è possibile ottenere il suo indirizzo in quanto non è memorizzato in memoria. ottiene la sua allocazione nel registro della CPU.


0

gcc 9.3 asm output, senza usare flag di ottimizzazione (tutto in questa risposta si riferisce alla compilazione standard senza flag di ottimizzazione):

#include <stdio.h>
int main(void) {
  int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 3
        add     DWORD PTR [rbp-4], 1
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave 
        ret
#include <stdio.h>
int main(void) {
  register int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 8
        mov     ebx, 3
        add     ebx, 1
        mov     esi, ebx
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

Questo costringe ebxa essere utilizzato per il calcolo, il che significa che deve essere spinto nello stack e ripristinato alla fine della funzione perché viene salvato. registerproduce più righe di codice e 1 scrittura in memoria e 1 lettura in memoria (anche se realisticamente, questo avrebbe potuto essere ottimizzato su 0 R / W se il calcolo fosse stato eseguito esi, che è ciò che accade usando i C ++ const register). Il mancato utilizzo registercausa 2 scritture e 1 lettura (anche se sulla lettura si verificherà l'inoltro da carico a archivio). Questo perché il valore deve essere presente e aggiornato direttamente nello stack in modo che il valore corretto possa essere letto dall'indirizzo (puntatore). registernon ha questo requisito e non può essere indicato. conste sovrascriverà le ottimizzazioni const a livello di file e blocco e diregister sono sostanzialmente l'opposto volatilee l'utilizzo divolatileregisterottimizzazioni a livello di blocco. const registere registerprodurrà output identici perché const non fa nulla su C in blocco-scope, quindi solo ilregister applicano ottimizzazioni.

Su clang, registerviene ignorato ma constsi verificano ancora ottimizzazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.