"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > A obscura palavra-chave “restringir” em C

A obscura palavra-chave “restringir” em C

Publicado em 2024-11-04
Navegar:406

The Obscure “restrict” Keyword in C

Introdução

Entre outras coisas, C99 adicionou a palavra-chavestrict como uma forma de um programador especificar que um ponteiro é o único ponteiro para um determinado objeto em um escopo e, conseqüentemente, dar ao compilador uma “dica ”que pode realizar otimizações adicionais ao acessar o objeto por meio desse ponteiro.

O problema

Para ilustrar o problema que a restrição deveria resolver, considere uma função como:

void update_ptrs( int *p, int *q, int const *v ) {
  *p  = *v;
  *q  = *v;
}

para o qual o compilador irá gerar código x86-64 como:

mov eax, [rdx]  ; tmp = *v   // 1
add [rdi], eax  ; *p  = tmp
mov eax, [rdx]  ; tmp = *v   // 3
add [rsi], eax  ; *q  = tmp

Você pode estar se perguntando por que ele gera a linha 3, já que parece redundante com a linha 1. O problema é que o compilador não pode saber que você não fez algo assim:

int x = 1, v = 2;
update_ptrs( &v, &x, &v );   // x = 5, v = 4

Em update_ptrs(), p e v seriam alias o mesmo int, então o compilador tem que jogar pelo seguro e assumir que o valor de *v pode mudar entre leituras, daí a instrução mov adicional.

Em geral, ponteiros em C confundem a otimização, pois o compilador não consegue saber se dois ponteiros se alias. Em código de desempenho crítico, eliminar leituras de memória poderia ser uma grande vitória se o compilador pudesse fazer isso com segurança.

A solução

Para resolver o problema mencionado acima, restrito foi adicionado a C para permitir que você especifique que um determinado ponteiro é o único ponteiro para um objeto no escopo do ponteiro, ou seja, nenhum outro ponteiro no mesmo aliases de escopo isto.

Para usar restringir, você insere-o entre o * e o nome do ponteiro em uma declaração. Um update_ptrs() reescrito para usar restrição seria:

void update_ptrs_v2( int *restrict p, int *restrict q,
                     int const *restrict v ) {
  *p  = *v;
  *q  = *v;
}

(Leia da direita para a esquerda, por exemplo, v é um ponteiro restrito para uma constante int; ou use cdecl.)

Ao adicionar restrição, o compilador agora pode gerar código como:

mov eax, [rdx]  ; tmp = *v
add [rdi], eax  ; *p  = tmp
add [rsi], eax  ; *q  = tmp

Agora, o compilador foi capaz de elidir a linha 3 anterior da instrução mov adicional.

Talvez o exemplo mais conhecido onde a restrição é usada seja a função de biblioteca padrão memcpy(). É a maneira mais rápida de copiar um pedaço de memória se os endereços de origem e destino não se sobrepõem. A função memmove() um pouco mais lenta existe para uso quando os endereços do se sobrepõem.

Armadilhas

O uso indevido de restrição resulta em comportamento indefinido, por exemplo, passando ponteiros que fazem alias entre si para update_ptrs_v2() ou memcpy(). Em alguns casos, o compilador pode avisá-lo, mas não em todos os casos, portanto, não confie no compilador para detectar usos indevidos.

Observe que a restrição é para um determinado escopo. Atribuir um ponteiro restrito a outro no mesmo escopo resulta em comportamento indefinido:

void f( int *restrict d, int *restrict s ) {
  int *restrict p = s;    // undefined behavior

No entanto, você pode atribuir um ponteiro restrito a um ponteiro irrestrito:

void f( int *restrict d, int *restrict s ) {
  int *p = s;             // OK

Mesmo que p seja irrestrito, o compilador ainda pode realizar as mesmas otimizações.

Também não há problema em atribuir um ponteiro restrito em um escopo interno a outro em um escopo externo (mas não o contrário):

void f( int *restrict d, int *restrict s ) {
  {                       // inner scope
    int *restrict p = s;  // OK
    // ...
    s = p;                // undefined behavior
  }
}

Quando (e quando não) usar restrição

Primeiro, você definitivamente deve criar um perfil de seu código (e talvez até mesmo olhar para o código assembly gerado) para ver se o uso de restrição realmente traz uma melhoria de desempenho significativa para justificar o risco de possíveis armadilhas. Diagnosticar bugs causados ​​pelo uso indevido de restrição é muito difícil de fazer.

Segundo, se o uso de restrição estiver confinado à implementação de uma função onde a memória acessada por meio de ponteiros restritos foi alocada por você, então é mais seguro. Por exemplo, dado:

void safer( unsigned n ) {
  n  = n % 2 != 0;  // make even by rounding up
  int *const array = malloc( n * sizeof(unsigned) );
  unsigned *restrict half_1st = array;
  unsigned *restrict half_2nd = array   n/2;
  // ...
  free( array );
}

o código pode operar na primeira e segunda metades do array com segurança porque elas não se sobrepõem (assumindo que você nunca acesse half_1st[n/2] ou além).

Terceiro, se restringir for usado nos parâmetros de uma função, então é potencialmente menos seguro. Por exemplo, compare safer() com update_ptrs_v2() onde o caller controla os ponteiros. Pelo que você sabe, o chamador entendeu errado e passou ponteiros para esse alias.

Variado

Somente ponteiros para objetos (ou void) podem ser qualificados com restrição:

restrict int x;       // error: can't restrict object
int restrict *p;      // error: pointer to restrict object
int (*restrict f)();  // error: pointer-to-function

Você pode usar restrição para membros de struct, por exemplo:

struct node {
   void *restrict data;
   struct node *restrict left;
   struct node *restrict right;
};

diz que os dados serão o único ponteiro para esses dados e que esquerda e direita nunca apontarão para o mesmo nó. No entanto, o uso de restrição para membros struct é muito incomum.

Por último, C não tem restrição. Por que não? A resposta é longa, mas a versão TL;DR é esta:

  • Pode ser uma fonte de bugs difíceis de encontrar que o comitê C não queria importar de C.
  • O aumento do uso de ponteiros por C, por exemplo, isso torna o uso restrito com segurança ainda mais difícil.

No entanto, muitos compiladores têm __restrict__ como extensão.

Conclusão

Em casos limitados, o uso de restrição pode levar a melhorias de desempenho, mas existem várias armadilhas significativas. Se você está pensando em usar restrição, crie primeiro o perfil do seu código.

Use com sabedoria.

Declaração de lançamento Este artigo está reproduzido em: https://dev.to/pauljlucas/the-obscure-restrict-keyword-in-c-2541 Se houver alguma infração, entre em contato com [email protected] para excluí-la
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3