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.