Suponha que eu tenha uma função que recebe alguns parâmetros de ponteiro - alguns não-const, através dos quais ela pode escrever, e alguns const através dos quais ela apenas lê. Exemplo:
void f(int * a, int const *b);
Suponha também que a função não escreva na memória (ou seja, não use variáveis globais, endereços fixos, reformule os ponteiros const como não-const e truques do tipo).
Agora, é suficiente (de acordo com o padrão da linguagem C), para atingir os benefícios de restrict
todas as leituras dentro de f()
, apenas restrict
os parâmetros de saída? Ou seja, no exemplo, restringir, a
mas não b
?
Um teste simples (GodBolt) sugere que tal restrição deve ser suficiente. Esta fonte:
int f(int * restrict a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
int all_restricted(int * restrict a, int const * restrict b) {
a[0] += b[0];
return a[0] + b[0];
}
int unrestricted(int * a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
Produz o mesmo código objeto para x86_64:
f:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
all_restricted:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
unrestricted:
mov eax, DWORD PTR [rsi]
add eax, DWORD PTR [rdi]
mov DWORD PTR [rdi], eax
add eax, DWORD PTR [rsi]
ret
mas isso não é uma garantia geral.
Não, não é suficiente.
Esta suposição é insuficiente. Também seria necessário que o compilador veja que a função não escreve de outra forma na memória para a qual o ponteiro aponta (incluindo qualquer memória que poderia ser acessada via ponteiro, como
b[18]
). Por exemplo, se o chamabar(b);
, e o compilador não pode verbar
, então ele não pode saber se a memória para a qualb
aponta é modificada durante a execução def
, mesmo que não seja.Dê esta premissa adicional, que o compilador pode ver que não há modificações em nenhuma memória apontada via
b
, então não importa para otimização seb
é declarado comconst
e/ourestrict
: O compilador sabe tudo sobre a memória, e dizer mais alguma coisa não acrescenta informação.Entretanto, muitas vezes não é o caso de o código satisfazer essa premissa. (E mesmo quando isso acontece, pode ser um incômodo para um programador ter certeza disso.) Então, vamos considerar um caso em que não temos a premissa adicional:
Quando
bar
é chamado, o compilador não sabe se*b
é modificado. Mesmo que esta função não passeb
parabar
,bar
pode acessar algum objeto externo que é*b
ou tem um ponteiro para onde*b
é, e assimbar
pode alterar o objeto que é*b
. Portanto, o compilador deve recarregar*b
da memória para o segundoprintf
.Se, em vez disso, declararmos a função
void f(int * restrict a, int const * restrict b)
, entãorestrict
afirma que, se*b
for modificado durante a execução def
(incluindo indiretamente, dentro debar
), então todo acesso a ele será viab
(diretamente, como em*b
, ou indiretamente, como através de um ponteiro visivelmente copiado ou calculado deb
). Como o compilador pode verbar
que não recebeb
, ele sabebar
que não contém nenhum acesso a*b
que seja baseado emb
, e, portanto, pode assumirbar
que não altera*b
.Portanto, adicionar
restrict
um parâmetro que é um ponteiro para umconst
tipo qualificado pode permitir algumas otimizações, mesmo que todos os outros parâmetros também sejam declaradosrestrict
.A
const
palavra-chave como mostrada causará um aviso ou erro se você tentar modificar o valor apontado pelo ponteiro, mas não necessariamente impedirá que você modifique esse valor, e certamente não impedirá que outra pessoa modifique esse valor. Assim, o compilador pode ter que assumir que tais modificações podem ocorrer.Por exemplo, se você invocar alguma função definida em um arquivo de origem diferente, o compilador não terá conhecimento do que essa função faz, então ele terá que assumir que a função pode, de alguma forma, ter acesso ao valor apontado por esse ponteiro e pode modificá-lo.
Além disso, mesmo que você modifique o valor apontado pelo ponteiro não constante, esse ponteiro não constante pode estar apontando para o mesmo valor que o ponteiro constante. (Como em,
f( &x, &x );
) Semrestrict
o ponteiro constante, o compilador tem que assumir que isso também é uma possibilidade.Portanto, até mesmo o
const
parâmetro poderia se beneficiar darestrict
palavra-chave, porque ela promete que a memória apontada por esse ponteiro não será modificada por ninguém. Essencialmente, você estaria prometendo que nunca faria algo comof( &x, &x );
.restrict
é unilateral. Ele licencia o compilador para fazer suposições sobre acessos de objetos via lvalues "com base" norestrict
ponteiro -qualificado que não depende se algum outro ponteiro também érestrict
-qualificado.Em particular, se você
restrict
parametrizara
então, independentemente de você tambémrestrict
parametrizarb
, você licencia o compilador para assumir que durante qualquer execução def()
,L
for qualquer lvalue cujo endereço é "baseado em"a
eL
é usado para acessar o objeto que ele designa (leitura ou gravação) ea
, embora não necessariamente viaL
em particular.b
mas não ema
.)Isso é explicado com mais detalhes na seção 6.7.3.1 do C17, embora esse texto seja complexo e um pouco carregado.
Assim, no caso que você descreve,
restrict
ing apenasa
autoriza o compilador a assumir que leituras via ponteiros derivados deb
nunca observarão gravações via ponteiros derivados dea
. Se ele realmente gerará código diferente é uma questão totalmente diferente, é claro.