Ponteiro C para desambiguação de array / array de ponteiros


463

Qual é a diferença entre as seguintes declarações:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Qual é a regra geral para entender declarações mais complexas?


54
Aqui está um ótimo artigo sobre a leitura de declarações complexas em C: unixwiz.net/techtips/reading-cdecl.html
Jesper

@jesper Infelizmente, os qualificadores conste volatile, que são importantes e complicados, estão faltando nesse artigo.
Não-usuário

@ não usuário não são relevantes para esta questão. O seu comentário não é relevante. Por favor, abster-se.
User64742 18/01

Respostas:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

O terceiro é o mesmo que o primeiro.

A regra geral é a precedência do operador . Pode ficar ainda mais complexo à medida que ponteiros de função entram em cena.


4
Portanto, para sistemas de 32 bits: int * arr [8]; / * Bytes 8x4 alocados, para cada ponteiro / int (* arr) [8]; / 4 bytes alocados, apenas um ponteiro * /
George

10
Não. int * arr [8]: total de 8x4 bytes alocados , 4 bytes para cada ponteiro. int (* arr) [8] está certo, 4 bytes.
Mehrdad Afshari

2
Eu deveria ter relido o que escrevi. Eu quis dizer 4 para cada ponteiro. Obrigado pela ajuda!
1313 George

4
O motivo pelo qual o primeiro é o mesmo que o último é que sempre é permitido colocar parênteses em torno dos declaradores. P [N] é um declarador de matriz. P (....) é um declarador de função e * P é um declarador de ponteiro. Portanto, tudo a seguir é o mesmo que sem parênteses (exceto para uma das funções '"()": int (((* p))); void ((g (void))); int * (a [1]); void (* (p ())).
Johannes Schaub - litb 13/05/09

2
Muito bem na sua explicação. Para uma referência detalhada sobre precedência e associatividade dos operadores, consulte a página 53 da Linguagem de programação C (ANSI C segunda edição) de Brian Kernighan e Dennis Ritchie. Os operadores ( ) [ ] associam da esquerda para a direita e tem precedência maior do que *assim que ler int* arr[8]como uma matriz de tamanho 8 onde cada elemento aponta para um int e int (*arr)[8]como um ponteiro para uma matriz de tamanho 8 que contém números inteiros
Mushy

267

Use o programa cdecl , conforme sugerido pela K&R.

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Isso funciona do outro jeito também.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii A maioria das distribuições Linux deve ter um pacote. Você também pode criar seu próprio binário.
Sigloice # 15/19

ah desculpe por não mencionar, o macOS aqui. vai ver se disponível, caso contrário, o site também está bem. ^^ obrigado por me informar sobre isso. Sinta-se à vontade para sinalizar o NLN.
Ankii 15/10/19

2
@ankii Você pode instalar a partir do Homebrew (e talvez do MacPorts?). Se você não gosta do seu gosto, é trivial criar o seu próprio no link do Github, no canto superior direito do cdecl.org (eu o criei no macOS Mojave). Em seguida, basta copiar o binário cdecl para o seu PATH. Eu recomendo $ PATH / bin, porque não há necessidade de envolver raiz em algo tão simples como isso.
sigjuice

Oh não tinha lido o pequeno parágrafo sobre instalação em leia-me. apenas alguns comandos e sinalizadores para lidar com dependências. Instalado usando o brew. :)
ankii 15/10/19

1
Quando li isso pela primeira vez, pensei: "Nunca chegarei a esse nível". No dia seguinte, eu baixei.
Ciro Santilli escreveu:

126

Não sei se ele tem um nome oficial, mas o chamo de Thingy Direita Esquerda (TM).

Comece na variável, depois vá para a direita, esquerda e direita ... e assim por diante.

int* arr1[8];

arr1 é uma matriz de 8 ponteiros para números inteiros.

int (*arr2)[8];

arr2 é um ponteiro (o parêntese bloqueia a direita esquerda) para uma matriz de 8 números inteiros.

int *(arr3[8]);

arr3 é uma matriz de 8 ponteiros para números inteiros.

Isso deve ajudá-lo com declarações complexas.


19
Eu ouvi isso mencionado pelo nome de "A Regra Espiral", que pode ser encontrada aqui .
fouric

6
@ InkBlend: A regra de espiral é diferente da regra da direita e esquerda . O primeiro falha em casos como int *a[][10]enquanto o último consegue.
Legenda2k

1
@dogeen Pensei que prazo tinha algo a ver com Bjarne Stroustrup :)
Anirudh Ramanathan

1
Como o InkBlend e o legends2k disseram, esta é a Regra Espiral, que é mais complexa e não funciona em todos os casos, portanto, não há razão para usá-la.
kotlomoy

Não se esqueça do lef a associatividade direita ( ) [ ]e da direita para esquerda* &
Mushy

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

O terceiro não deveria ser: a é uma matriz de ponteiros para uma matriz inteira de tamanho 8? Quero dizer que cada uma das matrizes inteiras terá tamanho 8, certo?
Rushil Paul 2/12/12

2
@Rushil: não, o último subscrito ( [5]) representa a dimensão interna. Isso significa que (*a[8])é a primeira dimensão e, portanto, é a representação externa da matriz. O que cada elemento dentro a aponta para um array de inteiros diferente de tamanho 5.
zeboidlund

Obrigado pelo terceiro. Eu estou procurando como escrever matriz de ponteiros para matriz.
Deqing

15

A resposta para os dois últimos também pode ser deduzida da regra de ouro em C:

A declaração segue o uso.

int (*arr2)[8];

O que acontece se você desreferenciar arr2? Você obtém uma matriz de 8 números inteiros.

int *(arr3[8]);

O que acontece se você retirar um elemento arr3? Você recebe um ponteiro para um número inteiro.

Isso também ajuda ao lidar com ponteiros para funções. Para pegar o exemplo de sigjuice:

float *(*x)(void )

O que acontece quando você desreferencia x? Você obtém uma função que você pode chamar sem argumentos. O que acontece quando você chama? Ele retornará um ponteiro para a float.

A precedência do operador é sempre complicada, no entanto. No entanto, o uso de parênteses também pode ser confuso, pois a declaração segue o uso. Pelo menos, para mim, intuitivamente arr2parece uma matriz de 8 ponteiros para ints, mas na verdade é o contrário. Apenas leva algum tempo para se acostumar. Motivo suficiente para sempre adicionar um comentário a essas declarações, se você me perguntar :)

edit: exemplo

A propósito, acabei de me deparar com a seguinte situação: uma função que tem uma matriz estática e usa aritmética do ponteiro para verificar se o ponteiro da linha está fora dos limites. Exemplo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Resultado:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Observe que o valor da borda nunca muda, então o compilador pode otimizar isso. Isso é diferente do que você pode querer inicialmente usar:: const int (*border)[3]que declara a borda como um ponteiro para uma matriz de 3 números inteiros que não alterará o valor enquanto a variável existir. No entanto, esse ponteiro pode ser apontado para qualquer outra matriz a qualquer momento. Em vez disso, queremos esse tipo de comportamento (porque essa função não altera nenhum desses números inteiros). A declaração segue o uso.

(ps: fique à vontade para melhorar esta amostra!)



3

Como regra geral, os operadores unários direito (como [], (), etc) têm preferência sobre os de esquerda. Portanto, int *(*ptr)()[];seria um ponteiro que aponta para uma função que retorna uma matriz de ponteiros para int (obtenha os operadores corretos o mais rápido possível, assim que sair do parêntese)


Isso é verdade, mas também é ilegal. Você não pode ter uma função que retorne uma matriz. Eu tentei e consegui isso: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];sob o GCC 8 com$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito

1
O motivo do compilador para fornecer esse erro é que ele está interpretando a função como retornando uma matriz, conforme a interpretação correta da regra de precedência. É ilegal como declaração, mas a declaração legal int *(*ptr)();permite que uma expressão como p()[3](ou (*p)()[3]) seja usada posteriormente.
Luis Colorado

Ok, se eu entendi, você está falando sobre a criação de uma função que retorna um ponteiro para o primeiro elemento de uma matriz (não uma matriz em si) e, posteriormente, usa essa função como se estivesse retornando uma matriz? Idéia interessante. Eu vou tentar. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }e chame assim: o foo(arr)[4];que deve conter arr[2][4], certo?
Cacahuete Frito

certo ... mas você estava certo também, e a declaração era ilegal. :)
Luis Colorado

2

Eu acho que podemos usar a regra simples ..

example int * (*ptr)()[];
start from ptr 

" ptré um ponteiro para" ir para a direita .. seu ") agora vá para a esquerda é um" ("saia para a direita" () "so" para uma função que não aceita argumentos "vá para a esquerda" e retorna um ponteiro "go direita "para uma matriz" vá para a esquerda "de números inteiros"


Eu melhoraria um pouco: "ptr é um nome que se refere a" vá para a direita ... é ), agora vá para a esquerda ... é *"um ponteiro para" vá para a direita ... é ), agora vá para a esquerda ... é a (sair, vá ()para a direita "para uma função que não aceita argumentos" vá para a direita ... []"e retorne uma matriz de" vá para a direita ;, então vá para a esquerda ... *"ponteiros para" vá para a esquerda ... int"números inteiros"
Cacahuete Frito


2

Aqui está como eu o interpreto:

int *something[n];

Nota sobre precedência: o operador de subscrito da matriz ( []) tem prioridade mais alta que o operador de desreferência ( *).

Então, aqui aplicaremos o []antes *, tornando a declaração equivalente a:

int *(something[i]);

Observe como uma declaração faz sentido: int numsignifica numé umint , int *ptrou int (*ptr)significa, (valor em ptr) é um int, que faz ptrum ponteiro para int.

Isso pode ser lido como, (o valor de (valor no i-ésimo índice de alguma coisa)) é um número inteiro. Portanto, (o valor no i-ésimo índice de alguma coisa) é um (ponteiro inteiro), o que torna o algo uma matriz de ponteiros inteiros.

No segundo,

int (*something)[n];

Para entender essa afirmação, você deve estar familiarizado com este fato:

Nota sobre a representação do ponteiro da matriz: somethingElse[i]é equivalente a*(somethingElse + i)

Então, substituindo somethingElsepor (*something), obtemos *(*something + i), que é um número inteiro conforme a declaração. Então, (*something)nos deu uma matriz, que faz algo equivalente a (ponteiro para uma matriz) .


0

Eu acho que a segunda declaração é confusa para muitos. Aqui está uma maneira fácil de entender isso.

Vamos ter uma matriz de números inteiros, ou seja int B[8].

Vamos também ter uma variável A que aponta para B. Agora, o valor em A é B, ie (*A) == B. Portanto, A aponta para uma matriz de números inteiros. Na sua pergunta, arr é semelhante a A.

Da mesma forma, em int* (*C) [8], C é um ponteiro para uma matriz de ponteiros para um número inteiro.


0
int *arr1[5]

Nesta declaração, arr1há uma matriz de 5 ponteiros para números inteiros. Motivo: colchetes têm maior precedência sobre * (operador de desreferenciamento). E nesse tipo, o número de linhas é fixo (5 aqui), mas o número de colunas é variável.

int (*arr2)[5]

Nesta declaração, arr2é um ponteiro para uma matriz inteira de 5 elementos. Razão: Aqui, colchetes () têm maior precedência que []. E nesse tipo, o número de linhas é variável, mas o número de colunas é fixo (5 aqui).


-7

No ponteiro para um número inteiro, se o ponteiro for incrementado, ele será o próximo número inteiro.

na matriz do ponteiro se o ponteiro for incrementado, ele pula para a próxima matriz


" na matriz do ponteiro, se o ponteiro for incrementado, ele pula para a próxima matriz ", isso está errado.
alk
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.