Array de Ponteiro em C

Galera, estou com um pequeno problema aqui, é o seguinte:
Primeiro, quero saber se a sentença abaixo é verídica quanto a ponteiros:

int **a é a mesma coisa que int *a[]?

Se eu passar um **a como argumento de uma função, como eu descubro o tamanho deste array?
O problema se encontra neste código, onde eu quero saber o tamanho do array a passado como argumento.
Código: https://paste.ofcode.org/zzucqP39qWFJwiqUtEKZf4

Sim, é a mesma coisa. Arrays em C são apenas um “syntax sugar” para facilitar e ocultar o uso de ponteiros inicialmente. Além disso, arrays são alocados na stack e portanto não precisam ser liberados manualmente.

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

int main() {
    int tamanho = 5;
    int array[] = {1, 2, 3, 4, 5};
    int *ponteiro = array;
    for (int i = 0; i < tamanho; i++) {
        printf("Tanto faz %d ou %d\n", ponteiro[i], *(ponteiro + i));
    }
    return 0;
}

Não tem, a principio, como você descobrir o tamanho de um array em C, se apenas o array é te dado. Contudo, como é você que instancia os arrays, você pode manter por perto as variáveis que guardam o tamanho daquele array.

No caso de arrays instanciados por outras funções da biblioteca, você sempre vai ter como passar um “argumento de saída”, onde a função te diz qual o tamanho do array alocado.

2 curtidas

Então, como usar argumentos com ponteiro de ponteiro: int **a.

Eu vou tentar fazer algo extremamente arriscado, que é explicar de forma breve como funcionam ponteiros e alocação de memória. Se por um acaso ficar confuso, faz mais perguntas que eu ou outras pessoas te respondemos.

Quando você aloca memória para um ponteiro, o esquema funciona mais ou menos da seguinte maneira:

int *v = malloc(5 * sizeof(int));

O malloc vai tentar reservar uma sequência de memória de tamanho 5 * tamanho de um int. Se o int tem 4 bytes, essa chamada em particular vai tentar reservar 5 * 4 bytes, que no final são 20 bytes. O retorno dessa chamada é o primeiro endereço dessa lista de bytes alocados.

Imagine que isso aqui é a memória, e cada posição representa 1 byte e tem um endereço:

{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

Cada inteiro precisa de 4 posições dessas para ser representado. Portanto, o primeiro número é representado com os 4 primeiros endereços. O segundo com os 4 seguintes, e assim por diante.

Quando você faz isso:

int primeiro = v[0];

Você está dizendo: eu quero que você leia os 4 primeiros bytes a partir desse endereço v, interprete como int e atribua na variável primeiro.

Em seguida, você pode fazer:

int segundo = v[1];

Que quer dizer: eu quero que você leia os 4 primeiros bytes a partir do endereço v + (1 * tamanho de int), interprete com int e atribua na variável segundo.

Esse número que vai dentro do operador [] significa quantas vezes o tamanho de um int (no caso de um ponteiro para inteiros) deve ser pulado a partir do primeiro endereço da sequência de memória. Pesquisa sobre aritmética de ponteiros se quiser saber mais sobre isso.

A aritmética de endereços e feita exatamente da mesma forma para todos os tipos de dados. Acontece que ponteiros também são um tipo de dado.

Por isso, quando você declara e inicializa uma variável como:

int **ponteiroParaPonteiros = malloc(5 * sizeof(int*));

Você está alocando 5 * o tamanho de um ponteiro para int. Cada posição desse array de bytes alocado pode ser interpretado como um ponteiro para inteiro. Por isso você pode fazer assim:

int *primeiroPonteiro = ponteiroParaPonteiros[0];

Esse negócio é extremamente cabeludo a primeira vista, mas uma hora tudo vai clicar e você vai compreender. Uma forma que me ajuda a compreender o tipo de dado quando tem um monte de símbolo de ponteiros, é ler a definição da direita para a esquerda.

Por exemplo:

int * a;

Lendo da direita para a esquerda: a é um ponteiro para inteiros

int ** a;

a é um ponteiro para ponteiros para inteiros.

E assim sucessivamente.

1 curtida

Uma outra forma que pode te ajudar a compreender o tipo int **a é o seguinte. Imagine o tipo int* como sendo um tipo normal (e é, na verdade), como um float ou char. A sintaxe para declaração de um ponteiro é:

tipo *nome;

Então você pode substituir tipo por int*, e ter no final:

int* *nome;

Só que os espaços não fazem a menor diferença. Todas essas declarações significam exatamente a mesma coisa:

int **nome;
int** nome;
int * * nome;

Perfeito!! Eu já havia estudado ponteiro há uns 3 anos atrás, mas agora deu uma clareada melhor! Obrigado mesmo!