Domanda

stavo dando un'occhiata agli po 'di documentazione e domande / risposte e ho visto che menzionato. Ho letto una breve descrizione, affermando che sarebbe fondamentalmente una promessa da parte del programmatore che il puntatore non sarà utilizzato per puntare da qualche altra parte.

Chiunque può offrire alcuni casi realistici in cui il suo valore in realtà usando questo?

È stato utile?

Soluzione

restrict dice che il puntatore è l'unica cosa che accede all'oggetto sottostante. Si elimina il potenziale di puntatore aliasing, consentendo una migliore ottimizzazione dal compilatore.

Per esempio, supponiamo di avere una macchina con le istruzioni specializzate che possono moltiplicare i vettori di numeri in memoria, e ho il seguente codice:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

Il compilatore deve gestire correttamente se dest, src1, e src2 si sovrappongono, il che significa che deve fare una moltiplicazione per volta, dall'inizio alla fine. Avendo restrict, il compilatore è libero di ottimizzare il codice utilizzando le istruzioni vettoriali.

Wikipedia ha una voce su restrict, con un altro esempio, qui .

Altri suggerimenti

Il Wikipedia esempio è molto illuminante.

Si mostra chiaramente come permette di salvare uno istruzioni di montaggio .

Senza limitare:

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

assemblaggio Pseudo:

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 a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

Con limitare:

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

assemblaggio Pseudo:

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
load R2 ← *b
add R2 += R1
set R2 → *b

Does GCC davvero farlo?

GCC 4.8 Linux x86-64:

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

Con -O0, sono la stessa cosa.

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 chi non lo sapesse, il convenzione di chiamata è:

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

uscita GCC è stato ancora più chiaro che l'articolo wiki: 4 istruzioni vs 3 Istruzioni

.

Array

Finora abbiamo singole risparmio di istruzione, ma se il puntatore rappresentare array ad essere passato sopra, un caso d'uso comune, allora un gruppo di istruzioni potuto essere salvati, come detto da supercat .

Si consideri ad esempio:

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

A causa di restrict, un compilatore intelligente (o umano), potrebbe ottimizzare quello a:

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

che è potenzialmente molto più efficiente in quanto può essere ottimizzato montaggio su un'implementazione libc discreto (come glibc): e 'meglio usare std :: memcpy () o std :: copy () in termini di prestazioni?

Does GCC davvero farlo?

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 gli stessi.

Con -O3:

  • con limitare:

    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 chiamate memset come previsto.

  • senza limitare: nessuna chiamata stdlib, a soli 16 iterazione vasta anello svolgimento che non intendo riprodurre qui: -)

Non ho avuto la pazienza di punto di riferimento, ma credo che la versione limitare sarà più veloce.

C99

Vediamo lo standard per completezza.

restrict dice che due puntatori non possono puntare a sovrapposizione delle regioni di memoria. L'utilizzo più comune è per gli argomenti della funzione.

Questo restringe come la funzione può essere chiamata, ma consente una più ottimizzazioni in fase di compilazione.

Se il chiamante non segue il contratto restrict, un comportamento indefinito.

Il C99 N1256 progetto 6.7. 3/7 "Tipo di qualificazione", dice:

  

La destinazione d'uso del qualificatore limitare (come la classe di archiviazione registro) è quello di promuovere l'ottimizzazione e l'eliminazione di tutte le istanze del qualificatore da tutte le unità di traduzione di pre-elaborazione che compongono un programma conforme non cambia il suo significato (cioè, comportamento osservabile).

e 6.7.3.1 "Definizione formale di limitare" dà i dettagli scabrosi.

regola aliasing Strict

La parola chiave restrict riguarda solo i puntatori di tipi compatibili (ad esempio due int*) perché le rigide regole aliasing dice che aliasing tipi incompatibili è un comportamento indefinito di default, e così compilatori può assumere che non accada e ottimizzare via.

Vedi: Qual è la regola rigorosa aliasing

Vedi anche

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top