Quais são a semântica de C99 de “restringir” com relação aos ponteiros para ponteiros?
-
11-09-2019 - |
Pergunta
Estou fazendo muita aritmética matriz e gostaria de aproveitar qualificador ponteiro restrict
de C99.
Eu gostaria de configurar meus matrizes como ponteiros para ponteiros para permitir a fácil subscripting, assim:
int **A = malloc (ncols * sizeof(int *));
A[0] = malloc (nrows * ncols * sizof(int));
for (int i=1; i < ncols; i++) {
A[i] = A[0] + i*nrows;
}
Agora, para uma função multiplicação de matrizes
void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);
I deve qualificar ambos os ponteiros dos argumentos tão restrito? É sintaxe válida, mas eu estou tendo um momento difícil determinar se comporta int *restrict *restrict
de forma diferente do int **restrict
.
Em seguida, com os ponteiros corretamente restrito, está acessando elementos através A[0][col*nrows + row]
indefinido? (Ou seja, vai o compilador assumir que eu única acesso a matriz através A[col][row]
para valores de row
tal que row < nrow
)? Ou devo simplesmente manter a coerência?
Solução
Para a primeira pergunta, "sim", isso significará algo diferente se você usar os dois qualificadores restrict
, especificamente, que os ponteiros também não vai ser alias. Quanto a saber se ele faz alguma diferença: teoricamente sim, na prática, isso depende do otimizador
Para a segunda pergunta, "sim", ele assumirá que nada acessado através de um ponteiro de linha só é acessado através o ponteiro de linha.
Você poderia jogar const
lá também.
Finalmente, se este é gcc em -O2, O3, ou -Os, o compilador já está fazendo uma análise apelido baseado em tipos. Eu tenho certeza que outros compiladores fazer isso também. Isto significa que restringir os ponteiros vs os ints já é compreendido, deixando apenas as matrizes que poderia armazenar um ao outro.
Em suma, o otimizador irá assumir que os ponteiros não estão sendo armazenados em como ints, e ele sabe que não está fazendo nenhum escreve ponteiro durante o loop.
Então, você provavelmente terá o mesmo código com apenas o restringir.
Outras dicas
O exterior (segundo) restringir informa o compilador que nenhuma das matrizes de ponteiros (A, B, e para fora) alias. O interior (primeiro) restringir informa o compilador que nenhum dos conjuntos de ints (apontada por elementos das matrizes de ponteiros) alias.
Se você acessar ambos A [0] [col * nrows + linha] e A [col] [linha], então você está violando restringir o interior, então as coisas podem quebrar.
int **restrict
única afirma que a memória endereçada por Fora, A e B não se sobrepõem (exceto que A e B podem sobrepor-se, assumindo a sua função não modifica nenhum dos dois). Isso significa que as matrizes de ponteiros. Ele não afirma nada sobre o conteúdo da memória apontada por Fora, A e B. Nota de rodapé 117 em n1124 diz:
Se identificador p tem o tipo (int ** restringir), então o ponteiro expressões p e p + 1 baseiam-se na objeto ponteiro restrito designado por p, mas as expressões ponteiro * P e p [1] não são.
Por analogia com const
, eu suspeito que a qualificação com restrict
duas vezes fará valer o que você quer, o que é que nenhum dos valores nos pontos de matriz de sobreposição de memória. Mas lendo o padrão, eu não posso provar a mim mesmo que ele realmente faz. Calculo que "Seja D uma declaração de um identificador comum que proporciona um meio de designando um objecto P como um ponteiro restringir-qualificado para tipo T", de facto, significa que para int *restrict *restrict A
, então A [0] e [1] são objetos designado como um ponteiro restringir-qualificado para int. Mas é juridiquês muito pesado.
Eu não tenho idéia se o seu compilador irá realmente fazer alguma coisa com esse conhecimento, você mente. Claramente o que podia, é uma questão de saber se ele é implementado.
Então, eu realmente não sei o que você ganhou ao longo de um convencional C matriz de 2-D, onde você só alocar rows * cols * sizeof(int)
e índice com A[cols*row + col]
. Então você claramente só precisa de um uso de restringir, e qualquer compilador que faz qualquer coisa com restrict
será capaz de re-encomenda lê A e B através de gravações para fora. Sem restrict
, é claro, não pode, por isso, fazer o que você está fazendo, você está jogando-se sobre a misericórdia de seu compilador. Se ele não consegue lidar com duplo restringir, apenas o único restringir caso, então o seu duplo engano custou-lhe a otimização.
Na primeira suposição, a multiplicação é provável que seja mais rápido do que um engano ponteiro adicional de qualquer maneira. Você, obviamente, se preocupam com o desempenho ou você não estaria usando restringir em tudo, então eu testar o desempenho bastante cuidado (em todos os compiladores você se preocupa) antes de fazer esta mudança para o bem de um pouco mais agradável sintaxe e não ter que lembrar de quantas colunas existem em sua matriz cada vez que você acessá-lo.
está acessando elementos através de A [0] [col * nrows + row] indefinido?
Sim, se o elemento é modificado por um dos acessos, porque isso faz com que A [0] um alias para memória também acessado através de um [col]. Isso seria bom se apenas A e B foram ponteiros restringir-qualificados, mas não se A [0] e A [col] são.
Eu suponho que você não modifique A nesta função, por isso, na verdade, que alias é bem. Se você fez a mesma coisa com o Fora, porém, o comportamento seria indefinida.