Che cosa significa la parola chiave restringente in C ++?


182

Non ero sempre sicuro, cosa significa la parola chiave restringente in C ++?

Significa che i due o più puntatori dati alla funzione non si sovrappongono? Che altro significa?


23
restrictè una parola chiave c99. Sì, Rpbert S. Barnes, so che la maggior parte dei compilatori supporta __restrict__. Noterai che qualsiasi cosa con doppio trattino basso è, per definizione, specifica dell'implementazione e quindi NON C ++ , ma una versione specifica del compilatore.
KitsuneYMG

5
Che cosa? Solo perché è specifico dell'implementazione non lo rende C ++; il C ++ consente esplicitamente cose specifiche per l'implementazione e non le proibisce né le rende C ++.
Alice,

4
@Alice KitsuneYMG significa che non fa parte di ISO C ++ ed è invece considerata un'estensione C ++. I creatori di compilatori sono autorizzati a creare e distribuire le proprie estensioni, che coesistono con ISO C ++ e agiscono come parte di un'aggiunta non ufficiale di solito meno o non portatile a C ++. Esempi potrebbero essere il vecchio C ++ gestito di MS e il loro più recente C ++ / CLI. Altri esempi potrebbero essere le direttive e le macro del preprocessore fornite da alcuni compilatori, come la #warningdirettiva comune o le macro della firma della funzione ( __PRETTY_FUNCTION__su GCC, __FUNCSIG__su MSVC, ecc.).
Justin Time - Ripristina Monica il

4
@Alice Per quanto ne so, C ++ 11 non richiede il pieno supporto per tutto il C99, né C ++ 14 o ciò che so di C ++ 17. restrictnon è considerata una parola chiave C ++ (vedi en.cppreference.com/w/cpp/keyword ) e, di fatto, l'unica menzione restrictnello standard C ++ 11 (vedi open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , una copia del FDIS con lievi modifiche editoriali, §17.2 [library.c], pagina PDF 413) afferma che:
Justin Time - Ripristina Monica

4
@Alice In che modo? Ho dichiarato la parte che dice che restrictdeve essere omesso (escluso da, lasciato fuori) dalle firme e dalla semantica della funzione di libreria standard C quando tali funzioni sono incluse nella libreria standard C ++. O in altre parole, ho affermato il fatto che se la firma di una funzione di libreria standard C contiene restrictin C, la restrictparola chiave deve essere rimossa dalla firma dell'equivalente C ++.
Justin Time - Ripristina Monica il

Risposte:


143

Nel suo articolo, Memory Optimization , Christer Ericson afferma che sebbene restrictnon faccia ancora parte dello standard C ++, che è supportato da molti compilatori e ne raccomanda l'utilizzo quando disponibile:

limitare la parola chiave

! Nuovo allo standard ANSI / ISO C del 1999

! Non ancora in standard C ++, ma supportato da molti compilatori C ++

! Solo un suggerimento, quindi potrebbe non fare nulla ed essere ancora conforme

Un puntatore (o riferimento) qualificato con restrizioni ...

! ... è fondamentalmente una promessa per il compilatore che per l'ambito del puntatore, la destinazione del puntatore sarà accessibile solo attraverso quel puntatore (e puntatori copiati da esso).

Nei compilatori C ++ che lo supportano, probabilmente dovrebbe comportarsi come in C.

Vedi questo messaggio SO per i dettagli: utilizzo realistico della parola chiave "limit" di C99?

Prenditi mezz'ora per sfogliare il documento di Ericson, è interessante e vale la pena.

modificare

Ho anche scoperto che il compilatore AIX C / C ++ di__restrict__ IBM supporta la parola chiave .

g ++ sembra anche supportare questo dato che il seguente programma viene compilato in modo pulito su g ++:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

Ho anche trovato un bell'articolo sull'uso di restrict:

Demistificare la parola chiave limitata

Edit2

Mi sono imbattuto in un articolo che discute specificamente dell'uso di restringimento nei programmi C ++:

Load-hit-store e la parola chiave __restrict

Inoltre, Microsoft Visual C ++ supporta anche la __restrictparola chiave .


2
Il link al documento Ottimizzazione della memoria è morto, ecco un link all'audio della sua presentazione GDC. gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael: Ovviamente se lo utilizzerai in un progetto C ++ portatile, dovresti #ifndef __GNUC__ #define __restrict__ /* no-op */o simili. E definirlo __restrictse _MSC_VERè definito.
Peter Cordes,

96

Come altri hanno detto, se non significa nulla a partire da C ++ 14 , quindi consideriamo l' __restrict__estensione GCC che fa lo stesso del C99 restrict.

C99

restrictafferma che due puntatori non possono puntare a aree di memoria sovrapposte. L'uso più comune è per gli argomenti delle funzioni.

Ciò limita il modo in cui è possibile chiamare la funzione, ma consente ulteriori ottimizzazioni di compilazione.

Se il chiamante non segue il restrictcontratto, comportamento indefinito.

Il progetto C99 N1256 6.7.3 / 7 "Qualificatori di tipo" dice:

L'uso previsto del qualificatore di limitazione (come la classe di archiviazione dei registri) è quello di promuovere l'ottimizzazione e l'eliminazione di tutte le istanze del qualificatore da tutte le unità di traduzione di preelaborazione che compongono un programma conforme non ne modifica il significato (vale a dire comportamento osservabile).

e 6.7.3.1 "Definizione formale di restringimento" fornisce i dettagli cruenti.

Una possibile ottimizzazione

L' esempio di Wikipedia è molto illuminante.

Mostra chiaramente come consente di salvare un'istruzione di assemblaggio .

Senza restrizioni:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Pseudo assemblaggio:

load R1  *x    ; Load the value of x pointer
load R2  *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2  *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

Con limitazione:

void fr(int *restrict a, int *restrict b, int *restrict x);

Pseudo assemblaggio:

load R1  *x
load R2  *a
add R2 += R1
set R2  *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2  *b
add R2 += R1
set R2  *b

GCC lo fa davvero?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

Con -O0, sono gli stessi.

Con -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

Per i non iniziati, la convenzione di chiamata è:

  • rdi = primo parametro
  • rsi = secondo parametro
  • rdx = terzo parametro

L'output di GCC era persino più chiaro dell'articolo wiki: 4 istruzioni contro 3 istruzioni.

Array

Finora abbiamo risparmi con una singola istruzione, ma se il puntatore rappresenta array da sottoporre a loop, un caso d'uso comune, allora un gruppo di istruzioni potrebbe essere salvato, come menzionato da supercat e michael .

Considera ad esempio:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

A causa di restrict, un compilatore intelligente (o umano), potrebbe ottimizzarlo per:

memset(p1, 4, size);
memset(p2, 9, size);

Che è potenzialmente molto più efficiente in quanto può essere ottimizzato per l'assemblaggio su un'implementazione decente di libc (come glibc) È meglio usare std :: memcpy () o std :: copy () in termini di prestazioni? , possibilmente con le istruzioni SIMD .

Senza, limitare, questa ottimizzazione non potrebbe essere eseguita, ad esempio considerare:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Quindi la forversione rende:

p1 == {4, 4, 4, 9}

mentre la memsetversione rende:

p1 == {4, 9, 9, 9}

GCC lo fa davvero?

GCC 5.2.1. Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

Con -O0, entrambi sono uguali.

Con -O3:

  • con limitazione:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    Due memsetchiamate come previsto.

  • senza restrizioni: nessuna chiamata stdlib, solo un srotolamento a ciclo continuo di 16 iterazioni che non intendo riprodurre qui :-)

Non ho avuto la pazienza di confrontarli, ma credo che la versione con restrizioni sarà più veloce.

Regola aliasing rigorosa

La restrictparola chiave influenza solo i puntatori di tipi compatibili (ad es. Due int*) perché le regole di aliasing rigoroso affermano che l'alias di tipi incompatibili è un comportamento indefinito per impostazione predefinita, e quindi i compilatori possono presumere che non accada e ottimizzare via.

Vedi: Qual è la regola di aliasing rigorosa?

Funziona per i riferimenti?

Secondo i documenti GCC: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html con sintassi:

int &__restrict__ rref

Esiste anche una versione per le thisfunzioni membro:

void T::fn () __restrict__

bella risposta. Cosa succede se l'aliasing rigoroso è disabilitato -fno-strict-aliasing, quindi non restrictdovrebbe fare alcuna differenza tra puntatori dello stesso tipo o tipi diversi, no? (Mi riferisco a "La parola chiave restrittiva riguarda solo i puntatori di tipi compatibili")
idclev 463035818

@ tobi303 Non lo so! Fammi sapere se lo scopri per certo ;-)
Ciro Santilli 9 冠状 病 六四 事件 法轮功

@jww sì, è un modo migliore per formularlo. Aggiornato.
Ciro Santilli 25 冠状 病 六四 事件 法轮功

restrictsignifica qualcosa in C ++. Se chiamate una funzione di libreria C con restrictparametri da un programma C ++, dovete obbedire alle implicazioni di ciò. Fondamentalmente, se restrictutilizzato in un'API della libreria C, significa qualcosa per chiunque lo chiami da qualsiasi lingua, incluso FFI dinamico di Lisp.
Kaz,

22

Niente. È stato aggiunto allo standard C99.


8
Questo non è del tutto vero. Apparentemente è supportato da alcuni compilatori C ++ e alcune persone consigliano vivamente il suo utilizzo quando è disponibile, vedere la mia risposta di seguito.
Robert S. Barnes,

18
@Robert S Barnes: lo standard C ++ non riconosce restrictcome parola chiave. Quindi la mia risposta è corretta. Ciò che descrivi è un comportamento specifico dell'implementazione e qualcosa su cui non dovresti davvero fare affidamento.
data

27
@dirkgently: Con tutto il rispetto, perché no? Molti progetti sono legati a specifiche estensioni di linguaggio non standard supportate solo da compilatori specifici o molto pochi. Mi vengono in mente il kernel Linux e gcc. Non è raro attenersi a un compilatore specifico o persino a una revisione specifica di un compilatore specifico per l'intera vita utile di un progetto. Non tutti i programmi devono essere rigorosamente conformi.
Robert S. Barnes,

7
@Rpbert S. Barnes: non posso assolutamente sottolineare perché non dovresti dipendere da un comportamento specifico dell'implementazione. Per quanto riguarda Linux e gcc, pensa e vedrai perché non sono un buon esempio a tua difesa. Devo ancora vedere anche un software di moderatamente successo eseguito su un singolo versione del compilatore per tutta la sua vita.
diretto il

16
@Rbert S. Barnes: la domanda diceva c ++. Non MSVC, non gcc, non AIX. Se acidzombie24 voleva estensioni specifiche del compilatore, avrebbe dovuto dirlo / taggarlo.
KitsuneYMG

12

Questa è la proposta originale per aggiungere questa parola chiave. Come ha sottolineato dirgentemente, questa è una funzionalità C99 ; non ha nulla a che fare con il C ++.


5
Molti compilatori C ++ supportano la __restrict__parola chiave che è identica per quanto posso dire.
Robert S. Barnes,

Ha tutto a che fare con C ++, perché i programmi C ++ chiamano librerie C e le librerie C usano restrict. Il comportamento del programma C ++ diventa indefinito se viola le restrizioni implicite restrict.
Kaz,

@kaz Totalmente sbagliato. Non ha nulla a che fare con C ++ perché non è una parola chiave o una funzionalità di C ++ e se si utilizzano i file di intestazione C in C ++ è necessario rimuovere la restrictparola chiave. Ovviamente se passi puntatori con alias a una funzione C che li dichiara limitati (cosa che puoi fare da C ++ o C), allora non è definito, ma questo dipende da te.
Jim Balter,

@JimBalter Capisco, quindi quello che stai dicendo è che i programmi C ++ chiamano le librerie C e le librerie C usano restrict. Il comportamento del programma C ++ diventa indefinito se viola le restrizioni implicite dalla limitazione. Ma questo in realtà non ha nulla a che fare con il C ++, perché è "su di te".
Kaz,

5

Non esiste una parola chiave simile in C ++. L'elenco delle parole chiave C ++ è disponibile nella sezione 2.11 / 1 dello standard del linguaggio C ++. restrictè una parola chiave nella versione C99 del linguaggio C e non in C ++.


5
Molti compilatori C ++ supportano la __restrict__parola chiave che è identica per quanto posso dire.
Robert S. Barnes,

18
@Robert: Ma non esiste una parola chiave simile in C ++ . Quello che fanno i singoli compilatori sono affari loro, ma non fa parte del linguaggio C ++.
jalf

4

Poiché i file di intestazione di alcune librerie C usano la parola chiave, il linguaggio C ++ dovrà fare qualcosa al riguardo .. come minimo, ignorando la parola chiave, quindi non dobbiamo #definire la parola chiave in una macro vuota per sopprimere la parola chiave .


3
Immagino che sia gestito utilizzando una extern Cdichiarazione o che venga eliminato in silenzio, come nel caso del compilatore AIX C / C ++, che invece gestisce la __rerstrict__parola chiave. Quella parola chiave è supportata anche in gcc in modo che il codice compili lo stesso in g ++.
Robert S. Barnes,
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.