Crie um ponteiro para uma matriz bidimensional

119

Preciso de um ponteiro para uma matriz bidimensional estática. Como isso é feito?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Recebo todos os tipos de erros como:

  • aviso: atribuição de tipo de ponteiro incompatível
  • o valor subscrito não é array nem ponteiro
  • erro: uso inválido de membro de matriz flexível
aneto
fonte
Leia stackoverflow.com/questions/423823/… pode ajudá-lo
Johannes Schaub - litb
1
@ JohannesSchaub-litb Isso não existe mais. (Como faço para vê-lo novamente ...? Sei que membros com baixa repetição podem vê-lo, mas esqueci como ...)
Mateen Ulhaq
@muntoo: Aqui está uma cópia dele: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Respostas:

139

Aqui você quer fazer um ponteiro para o primeiro elemento da matriz

uint8_t (*matrix_ptr)[20] = l_matrix;

Com typedef, isso parece mais limpo

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Então você pode aproveitar a vida novamente :)

matrix_ptr[0][1] = ...;

Cuidado com o mundo de ponteiro / array em C, pois há muita confusão em torno disso.


Editar

Revendo algumas das outras respostas aqui, porque os campos de comentário são muito curtos para fazer lá. Múltiplas alternativas foram propostas, mas não foi demonstrado como elas se comportam. É assim que eles fazem

uint8_t (*matrix_ptr)[][20] = l_matrix;

Se você corrigir o erro e adicionar o operador de endereço &como no seguinte trecho

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Então aquele cria um ponteiro para um tipo de array incompleto de elementos do tipo array de 20 uint8_t. Como o ponteiro é para uma matriz de matrizes, você deve acessá-lo com

(*matrix_ptr)[0][1] = ...;

E porque é um ponteiro para uma matriz incompleta, você não pode fazer como um atalho

matrix_ptr[0][0][1] = ...;

Porque a indexação requer que o tamanho do tipo de elemento seja conhecido (indexar implica na adição de um inteiro ao ponteiro, então não funcionará com tipos incompletos). Observe que isso só funciona em C, porque T[]e T[N]são tipos compatíveis. C ++ não tem um conceito de tipos compatíveis e, portanto, rejeitará esse código, pois T[]e T[10]são tipos diferentes.


A alternativa a seguir não funciona, porque o tipo de elemento da matriz, quando você a visualiza como uma matriz unidimensional, não é uint8_t, masuint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

O seguinte é uma boa alternativa

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Você acessa com

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Tem a vantagem de preservar o tamanho da dimensão externa. Então você pode aplicar sizeof nele

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Há uma outra resposta que faz uso do fato de que os itens em uma matriz são armazenados de forma contígua

uint8_t *matrix_ptr = l_matrix[0];

Agora, isso formalmente só permite acessar os elementos do primeiro elemento do array bidimensional. Ou seja, a seguinte condição é mantida

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Você notará que provavelmente funciona até 10*20-1, mas se você lançar na análise de alias e outras otimizações agressivas, algum compilador pode fazer uma suposição que pode quebrar esse código. Dito isso, eu nunca encontrei um compilador que falhe nele (mas, novamente, eu não usei essa técnica em código real), e até mesmo o C FAQ contém essa técnica (com um aviso sobre seu UB'ness ), e se você não pode alterar o tipo de array, esta é a última opção para salvá-lo :)

Johannes Schaub - litb
fonte
+1 - boas informações sobre a divisão int (*) [] [20] - não posso fazer isso em C ++
Faisal Vali
@litb, sinto muito, mas isso está errado, pois sua solução não fornece nenhuma alocação de armazenamento para o array.
Rob Wells
2
@Rob, não te entendo muito bem. o armazenamento em todos esses casos é fornecido pelo próprio array l_matix. Os ponteiros para eles obtêm armazenamento de onde quer que sejam declarados e como (pilha, segmento de dados estáticos, ...).
Johannes Schaub - litb
Apenas curioso por que precisamos do endereço "&" de l_matrix?
electro
1
@Sohaib - não, isso cria apenas um ponteiro. Você pode ter confundido com uint8_t *d[20], que cria um array de 3 ponteiros para uint8_t, mas isso não funcionaria neste caso.
Palo
27

Para entender isso totalmente , você deve compreender os seguintes conceitos:

Arrays não são ponteiros!

Em primeiro lugar (e tem sido pregado o suficiente), matrizes não são ponteiros . Em vez disso, na maioria dos usos, eles 'decaem' para o endereço de seu primeiro elemento, que pode ser atribuído a um ponteiro:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

Presumo que funcione dessa forma para que o conteúdo do array possa ser acessado sem copiar todos eles. Isso é apenas um comportamento de tipos de array e não significa que eles sejam a mesma coisa.



Matrizes multidimensionais

Arrays multidimensionais são apenas uma forma de 'particionar' a memória de uma forma que o compilador / máquina possa entender e operar.

Por exemplo, int a[4][3][5]= uma matriz contendo 4 * 3 * 5 (60) 'pedaços' de memória de tamanho inteiro.

A vantagem sobre o uso do int a[4][3][5]vs plain int b[60]é que agora eles estão 'particionados' (mais fácil de trabalhar com seus 'pedaços', se necessário), e o programa agora pode realizar a verificação vinculada.

Na verdade, int a[4][3][5]é armazenado exatamente como int b[60]na memória - A única diferença é que o programa agora o gerencia como se fossem entidades separadas de certos tamanhos (especificamente, quatro grupos de três grupos de cinco).

Lembre-se: int a[4][3][5]e int b[60]são iguais na memória, e a única diferença é como são tratados pelo aplicativo / compilador

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

A partir disso, você pode ver claramente que cada "partição" é apenas um array que o programa controla.



Sintaxe

Agora, os arrays são sintaticamente diferentes dos ponteiros . Especificamente, isso significa que o compilador / máquina os tratará de maneira diferente. Isso pode parecer óbvio, mas dê uma olhada nisto:

int a[3][3];

printf("%p %p", a, a[0]);

O exemplo acima imprime o mesmo endereço de memória duas vezes, assim:

0x7eb5a3b4 0x7eb5a3b4

No entanto, apenas um pode ser atribuído a um ponteiro de forma tão direta :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Por que não pode a ser atribuído a um ponteiro, mas a[0] pode?

Isso, simplesmente, é uma consequência de matrizes multidimensionais, e vou explicar por quê:

No nível de ' a', ainda vemos que temos outra 'dimensão' pela qual esperar. No nível de ' a[0]', entretanto, já estamos na dimensão superior, portanto, no que diz respeito ao programa, estamos apenas olhando para um array normal.

Você pode estar perguntando:

Por que é importante se o array é multidimensional em relação a fazer um ponteiro para ele?

É melhor pensar assim:

Um 'decaimento' de um array multidimensional não é apenas um endereço, mas um endereço com dados de partição (também conhecido como ele ainda entende que seus dados subjacentes são feitos de outros arrays), que consiste em limites definidos pelo array além da primeira dimensão.

Esta lógica de 'partição' não pode existir dentro de um ponteiro, a menos que o especifiquemos:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Caso contrário, o significado das propriedades de classificação da matriz será perdido.

Observe também o uso de parênteses em torno de *p: int (*p)[5][95][8]- Isso é para especificar que estamos fazendo um ponteiro com esses limites, não uma matriz de ponteiros com esses limites:int *p[5][95][8]



Conclusão

Vamos revisar:

  • Matrizes decaem para endereços se não tiverem outra finalidade no contexto usado
  • Matrizes multidimensionais são apenas matrizes de matrizes - Portanto, o endereço 'decadente' carregará o fardo de "Eu tenho subdimensões"
  • Os dados de dimensão não podem existir em um ponteiro, a menos que você os forneça .

Em resumo: arrays multidimensionais decaem para endereços que carregam a capacidade de entender seus conteúdos.

Super Gato
fonte
1
A primeira parte da resposta é ótima, mas a segunda não. Incorreto. int *p1 = &(a[0]); // RIGHT !Na verdade, é idêntico aint *p1 = a;
2501
@ 2501 Obrigado por detectar esse erro, eu o corrigi. Não posso dizer com certeza por que o exemplo que define essa "regra" também a desafiou. Vale a pena reafirmar é que só porque duas entidades podem ser interpretadas como ponteiros e produzir o mesmo valor, não significa que tenham o mesmo significado.
Super Cat
7

No

int *ptr= l_matrix[0];

você pode acessar como

*p
*(p+1)
*(p+2)

afinal, as matrizes bidimensionais também são armazenadas como 1-d.

Sagar
fonte
5

Bom dia,

A declaração

static uint8_t l_matrix[10][20];

reservou armazenamento para 10 linhas de 20 locais unit8_t, ou seja, 200 locais de tamanho uint8_t, com cada elemento sendo encontrado calculando 20 x linha + coluna.

Então não

uint8_t (*matrix_ptr)[20] = l_matrix;

dar o que você precisa e apontar para o elemento zero da coluna da primeira linha da matriz?

Edit: pensando um pouco mais sobre isso, não é um nome de array, por definição, um ponteiro? Ou seja, o nome de uma matriz é um sinônimo para a localização do primeiro elemento, ou seja, l_matrix [0] [0]?

Edit2: Como mencionado por outros, o espaço de comentários é um pouco pequeno para uma discussão mais aprofundada. De qualquer forma:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

não fornece nenhuma alocação de armazenamento para o array em questão.

Conforme mencionado acima, e conforme definido pela norma, a declaração:

static uint8_t l_matrix[10][20];

reservou 200 locais sequenciais do tipo uint8_t.

Referindo-se a l_matrix usando instruções da forma:

(*l_matrix + (20 * rowno) + colno)

fornecerá o conteúdo do elemento colno'th encontrado na linha rowno.

Todas as manipulações do ponteiro levam em consideração automaticamente o tamanho do objeto apontado. - K&R Seção 5.4, p.103

Este também é o caso se qualquer deslocamento de preenchimento ou alinhamento de byte estiver envolvido no armazenamento do objeto em questão. O compilador se ajustará automaticamente para eles. Por definição do padrão C ANSI.

HTH

Felicidades,

Rob Wells
fonte
1
uint8_t (* matrix_ptr) [] [20] << os primeiros colchetes devem ser deixados de fora, correto é uint8_t (* matrix_ptr) [20]
Aconcagua
5

Em C99 (compatível com clang e gcc), há uma sintaxe obscura para a passagem de matrizes multidimensionais para funções por referência:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Ao contrário de um ponteiro simples, isso sugere o tamanho do array, teoricamente permitindo que o compilador avise sobre a passagem de um array muito pequeno e identifique o acesso fora dos limites.

Infelizmente, ele não corrige sizeof()e os compiladores parecem não usar essa informação ainda, então continua sendo uma curiosidade.

Kornel
fonte
1
Essa resposta é enganosa: Isso não torna o argumento uma matriz de tamanho fixo, ainda é um ponteiro. static 10é algum tipo de garantia de que pelo menos 10 elementos estão presentes, o que novamente significa que o tamanho não é fixo.
bluss
1
@bluss a questão era sobre um ponteiro, então não vejo como responder com um ponteiro (anotando por referência ) pode ser enganoso. A matriz tem tamanho fixo da perspectiva da função, porque o acesso a elementos além desses limites é indefinido.
Kornel
Não acho que o acesso além de 10 seja indefinido, não consigo ver nada que indique isso.
bluss
Essa resposta parece sugerir que, sem a palavra-chave static, o array não seria passado por referência, o que não é verdade. Os arrays são passados ​​por referência de qualquer maneira. A pergunta original era sobre um caso de uso diferente - acessando elementos de array 2D usando um ponteiro suplementar dentro da mesma função / namespace.
Palo
4

Você sempre pode evitar mexer no compilador declarando o array como linear e fazendo o (linha, col) para o cálculo do índice do array você mesmo.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

isso é o que o compilador teria feito de qualquer maneira.

gnose
fonte
1
Isso é o que o compilador C faz de qualquer maneira. C realmente não tem nenhuma noção real de uma "matriz" - a notação [] é apenas um açúcar sintático para aritmética de ponteiro
Ken Keenan
7
Essa solução tem a desvantagem de nunca descobrir a maneira certa de fazer isso.
Craig McQueen
2

A sintaxe básica do ponteiro de inicialização que aponta para a matriz multidimensional é

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

A sintaxe básica para chamá-lo é

(*pointer_name)[1st index][2nd index][...]

Aqui está um exemplo:

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

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

O resultado é:

Subham
Messi

Funcionou...

Subham Debnath
fonte
1

Você pode fazer assim:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Nick Dandoulakis
fonte
1
isso não ocupa 10 * 20 bytes de RAM? (estou em um microcontrolador)
Dill
Ele ocupará 4 bytes ou qualquer tamanho que um ponteiro seja grande em sua caixa. Mas lembre-se se você tiver este, você deve indexar com matrix_ptr [0] [x] [y] ou (* matrix_ptr) [x] [y]. É a interpretação direta e palavra por palavra de "ponteiro para matriz bidimensional": p
Johannes Schaub - litb
Obrigado litb, esqueci de mencionar como acessá-lo. Não adianta editar minha resposta, já que você fez um ótimo trabalho com sua resposta :)
Nick Dandoulakis
Então, isso ocupa 10 * 20 bytes de RAM ou não?
Danijel
@Danijel, já que é um ponteiro para um array bidimensional, ele ocupará apenas 4 bytes ou qualquer tamanho de um ponteiro em sua caixa, ou seja, 16 bits, 32 bits, 64 bits, etc.
Nick Dandoulakis
1

Você quer um ponteiro para o primeiro elemento, então;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}
Ken Keenan
fonte
0

Você também pode adicionar um deslocamento se quiser usar índices negativos:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Se o seu compilador fornecer um erro ou aviso, você pode usar:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;
mathengineer
fonte
Olá. Esta pergunta está marcada com c, portanto, a resposta deve estar no mesmo idioma. Observe as tags.
2501