O que a palavra-chave restringir significa em C ++?

182

Sempre tive dúvidas, o que a palavra-chave restringir significa em C ++?

Isso significa que o ponteiro de dois ou mais dados para a função não se sobrepõe? O que mais isso significa?

Esteira
fonte
23
restricté uma palavra-chave c99. Sim, Rpbert S. Barnes, eu sei que a maioria dos compiladores suporta __restrict__. Você notará que qualquer coisa com sublinhado duplo é, por definição, específica da implementação e, portanto, NÃO C ++ , mas uma versão específica do compilador.
usar o seguinte código
5
O que? Só porque é específico da implementação, não o torna C ++; o C ++ permite explicitamente coisas específicas da implementação e não o desaprova ou o torna não C ++.
Alice
4
O @Alice KitsuneYMG significa que não faz parte do ISO C ++ e, em vez disso, é considerado uma extensão do C ++. Os criadores de compiladores têm permissão para criar e distribuir suas próprias extensões, que coexistem com o ISO C ++ e atuam como parte de uma adição não-oficial normalmente menos ou não-portátil ao C ++. Os exemplos seriam o C ++ gerenciado antigo da MS e o C ++ / CLI mais recente. Outros exemplos seriam diretivas e macros de pré-processador fornecidas por alguns compiladores, como a #warningdiretiva comum ou as macros de assinatura de função ( __PRETTY_FUNCTION__no GCC, __FUNCSIG__no MSVC, etc.).
Justin Time - Restabelece Monica
4
@ Alice Pelo que sei, o C ++ 11 não exige suporte total para todo o C99, nem o C ++ 14 ou o que eu sei do C ++ 17. restrictnão é considerada uma palavra-chave C ++ (consulte en.cppreference.com/w/cpp/keyword ) e, de fato, a única menção restrictno padrão C ++ 11 (consulte open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , uma cópia do FDIS com pequenas alterações editoriais, §17.2 [library.c], página em PDF 413) afirma que:
Justin Time - Reinstate Monica
4
@ Alice Como assim? Eu declarei a parte que diz que restrictdeve ser omitida (excluída, deixada de fora) assinaturas e semânticas de funções da biblioteca padrão C quando essas funções estão incluídas na biblioteca padrão C ++. Ou, em outras palavras, afirmei o fato de que, se a assinatura de uma função de biblioteca padrão C contiver restrictem C, a restrictpalavra - chave deverá ser removida da assinatura do equivalente em C ++.
Justin Time - Restabelece Monica

Respostas:

143

Em seu artigo, Memory Optimization , Christer Ericson diz que, embora restrictainda não faça parte do padrão C ++, ele é suportado por muitos compiladores e recomenda seu uso quando disponível:

restringir palavra-chave

! Novo no padrão ANSI / ISO C de 1999

! Ainda não está no padrão C ++, mas é suportado por muitos compiladores C ++

! Apenas uma dica, portanto, pode não fazer nada e ainda estar em conformidade

Um ponteiro qualificado por restrição (ou referência) ...

! ... é basicamente uma promessa ao compilador de que, para o escopo do ponteiro, o alvo do ponteiro será acessado apenas através desse ponteiro (e ponteiros copiados dele).

Nos compiladores C ++ que o suportam, provavelmente deve se comportar da mesma forma que em C.

Veja esta publicação do SO para obter detalhes: Uso realista da palavra-chave 'restringir' C99?

Demore meia hora para folhear o artigo de Ericson, é interessante e vale a pena.

Editar

Também descobri que o compilador AIX C / C ++__restrict__ da IBM suporta a palavra - chave .

O g ++ também parece suportar isso, pois o seguinte programa é compilado corretamente no 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;
}

Eu também encontrei um bom artigo sobre o uso de restrict:

Desmistificando a palavra-chave Restringir

Edit2

Encontrei um artigo que discute especificamente o uso de restringir em programas C ++:

Load-hit-stores e a palavra-chave __restrict

Além disso, o Microsoft Visual C ++ também suporta a __restrictpalavra - chave .

Robert S. Barnes
fonte
2
O link do papel de Otimização de memória está esgotado. Aqui está o link para o áudio de sua apresentação no GDC. gdcvault.com/play/1022689/Memory
Grimeh
1
@EnnMichael: Obviamente, se você for usá-lo em um projeto portátil em C ++, deveria #ifndef __GNUC__ #define __restrict__ /* no-op */ou algo parecido. E defina-o como __restrictse _MSC_VERestiver definido.
27617 Peter Cordes
96

Como outros disseram, se não significa nada como no C ++ 14 , vamos considerar a __restrict__extensão GCC, que faz o mesmo que o C99 restrict.

C99

restrictdiz que dois ponteiros não podem apontar para regiões de memória sobrepostas. O uso mais comum é para argumentos de função.

Isso restringe como a função pode ser chamada, mas permite mais otimizações de compilação.

Se o chamador não seguir o restrictcontrato, comportamento indefinido.

O projeto C99 N1256 6.7.3 / 7 "Qualificadores de tipo" diz:

O uso pretendido do qualificador restrito (como a classe de armazenamento de registro) é promover a otimização e excluir todas as instâncias do qualificador de todas as unidades de conversão de pré-processamento que compõem um programa em conformidade não altera seu significado (ou seja, comportamento observável).

e 6.7.3.1 "Definição formal de restrição" fornece detalhes sangrentos.

Uma possível otimização

O exemplo da Wikipedia é muito esclarecedor.

Mostra claramente como ele permite salvar uma instrução de montagem .

Sem restrição:

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

Pseudo montagem:

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

Com restringir:

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

Pseudo montagem:

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

O GCC realmente faz isso?

g++ 4.8 Linux x86-64:

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

Com -O0, eles são os mesmos.

Com -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) 

Para os não iniciados, a convenção de chamada é:

  • rdi = primeiro parâmetro
  • rsi = segundo parâmetro
  • rdx = terceiro parâmetro

A saída do GCC foi ainda mais clara que o artigo da wiki: 4 instruções vs 3 instruções.

Matrizes

Até o momento, temos economia de instruções únicas, mas se o ponteiro representar matrizes a serem repetidas, um caso de uso comum, várias instruções podem ser salvas, como mencionado por supercat e michael .

Considere, por exemplo:

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;
     }
 }

Por causa disso restrict, um compilador inteligente (ou humano) pode otimizar isso para:

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

O que é potencialmente muito mais eficiente, pois pode ser otimizado para montagem em uma implementação decente da libc (como glibc). É melhor usar std :: memcpy () ou std :: copy () em termos de desempenho? , possivelmente com instruções SIMD .

Sem, restringir, essa otimização não pode ser feita, por exemplo, considere:

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

Então a forversão faz:

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

enquanto a memsetversão faz:

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

O GCC realmente faz isso?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Com -O0, ambos são iguais.

Com -O3:

  • com restringir:

    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

    Duas memsetchamadas conforme o esperado.

  • sem restrição: sem chamadas stdlib, apenas um loop de 16 iterações que eu não pretendo reproduzir aqui :-)

Não tive paciência para compará-los, mas acredito que a versão restrita será mais rápida.

Regra estrita de alias

A restrictpalavra-chave afeta apenas ponteiros de tipos compatíveis (por exemplo, dois int*) porque as regras estritas de aliasing dizem que o aliasing de tipos incompatíveis é um comportamento indefinido por padrão, e assim os compiladores podem assumir que isso não acontece e otimizar.

Veja: Qual é a regra estrita de alias?

Isso funciona para referências?

De acordo com os documentos do GCC, ele faz: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html com sintaxe:

int &__restrict__ rref

Existe até uma versão para thisfunções-membro:

void T::fn () __restrict__
Ciro Santilli adicionou uma nova foto
fonte
nice asnwer. E se o aliasing estrito estiver desativado -fno-strict-aliasing, restrictnão deve fazer diferença entre ponteiros do mesmo tipo ou tipos diferentes, não? (Estou referindo-se a "Restringir palavra-chave afeta somente ponteiros de tipos compatíveis")
idclev 463035818
@ tobi303 Não sei! Deixe-me saber se você descobrir com certeza ;-)
Ciro Santilli郝海东冠状病六四事件法轮功
@ jww sim, essa é a melhor maneira de expressar isso. Atualizada.
Ciro Santilli escreveu:
restrictsignifica algo em C ++. Se você chamar uma função de biblioteca C com restrictparâmetros de um programa C ++, terá que obedecer às implicações disso. Basicamente, se restrictfor usado em uma API da biblioteca C, significa algo para qualquer pessoa que a chamar de qualquer idioma, incluindo o FFI dinâmico do Lisp.
Kaz
22

Nada. Foi adicionado ao padrão C99.

dirkgently
fonte
8
Isso não é completamente verdade. Aparentemente, é suportado por alguns compiladores C ++ e algumas pessoas recomendam fortemente seu uso quando disponível, veja minha resposta abaixo.
31510 Robert S. Barnes
18
@ Robert S Barnes: O padrão C ++ não reconhece restrictcomo uma palavra-chave. Portanto, minha resposta está correta. O que você descreve é ​​um comportamento específico da implementação e algo em que você realmente não deve confiar.
dirkgently
27
@ dirkgently: Com todo o respeito, por que não? Muitos projetos estão vinculados a extensões de idioma não padrão específicas, suportadas apenas por compiladores específicos ou muito poucos. O Kernel do Linux e o gcc vêm à mente. Não é incomum ficar com um compilador específico, ou mesmo uma revisão específica de um compilador específico durante toda a vida útil de um projeto. Nem todo programa precisa estar em conformidade estrita.
Robert S. Barnes
7
@Rpbert S. Barnes: Não posso enfatizar mais por que você não deve depender do comportamento específico da implementação. Quanto ao Linux e ao gcc - pense e você verá por que eles não são um bom exemplo em sua defesa. Ainda estou para ver até mesmo um software de sucesso moderado sendo executado em uma única versão do compilador por toda a vida.
dirkgently
16
@Rpbert S. Barnes: A pergunta dizia c ++. Não MSVC, não gcc, não AIX. Se acidzombie24 quisesse extensões específicas do compilador, ele deveria ter dito / marcado isso.
usar o seguinte código
12

Esta é a proposta original para adicionar esta palavra-chave. Como dirkgently apontou, porém, este é um recurso C99 ; não tem nada a ver com C ++.

descontrair
fonte
5
Muitos compiladores C ++ suportam a __restrict__palavra - chave que é idêntica até onde eu sei.
31510 Robert S. Barnes
Tem tudo a ver com C ++, porque os programas C ++ chamam bibliotecas C e as bibliotecas C usam restrict. O comportamento do programa C ++ fica indefinido se violar as restrições implícitas por restrict.
Kaz
@kaz Totalmente errado. Não tem nada a ver com C ++ porque não é uma palavra-chave ou recurso do C ++, e se você usar arquivos de cabeçalho C em C ++, deverá remover a restrictpalavra - chave. Obviamente, se você passar ponteiros com alias para uma função C que os declara restritos (o que você pode fazer com C ++ ou C), isso é indefinido, mas isso é com você.
Jim Balter 18/06
@ JimBalter eu vejo, então o que você está dizendo é que os programas C ++ chamam bibliotecas C, e as bibliotecas C usam restrict. O comportamento do programa C ++ fica indefinido se violar as restrições implícitas pelo restringir. Mas isso realmente não tem nada a ver com C ++, porque está "em você".
Kaz
5

Não existe tal palavra-chave em C ++. A lista de palavras-chave C ++ pode ser encontrada na seção 2.11 / 1 do padrão da linguagem C ++. restricté uma palavra-chave na versão C99 da linguagem C e não em C ++.

Formiga
fonte
5
Muitos compiladores C ++ suportam a __restrict__palavra - chave que é idêntica até onde eu sei.
31510 Robert S. Barnes
18
@ Robert: Mas não existe tal palavra-chave em C ++ . O que os compiladores individuais fazem é seu próprio negócio, mas não faz parte da linguagem C ++.
jalf 27/12/2009
4

Como os arquivos de cabeçalho de algumas bibliotecas C usam a palavra-chave, a linguagem C ++ precisará fazer algo a respeito. No mínimo, ignorando a palavra-chave, para que não tenhamos que # definir a palavra-chave em uma macro em branco para suprimi-la. .

Johan Boulé
fonte
3
Eu acho que isso é tratado usando uma extern Cdeclaração ou sendo descartada silenciosamente, como é o caso do compilador AIX C / C ++, que manipula a __rerstrict__palavra - chave. Essa palavra-chave também é suportada no gcc, para que o código compile o mesmo no g ++.
31510 Robert S. Barnes