Por que preciso usar o tipo ** para apontar para o tipo *?

Eu tenho lido Learn C The Hard Way por alguns dias, mas aqui está algo que eu realmente quero entender. Zed, o autor, escreveu que char ** é para um “ponteiro para (um ponteiro para char)”, e dizendo que isso é necessário porque estou tentando apontar para algo bidimensional.

Aqui está o que exatamente está escrito na página da web

Um char * já é um “pointer to char”, então é apenas uma string. No entanto, você precisa de 2 níveis, pois os nomes são bidimensionais, o que significa que você precisa de char ** para um tipo de “ponteiro para (um ponteiro para char)”.

Isso significa que eu tenho que usar uma variável que pode apontar para algo bidimensional, e é por isso que eu preciso de dois ** ?

Apenas um pouco de acompanhamento, isso também se aplica a uma dimensão n?

Aqui está o código relevante

 char *names[] = { "Alan", "Frank", "Mary", "John", "Lisa" }; char **cur_name = names; 

Não, esse tutorial é de qualidade questionável. Eu não recomendaria continuar lendo.

Um char** é um ponteiro para ponteiro. Não é um array 2D. Não é um ponteiro para um array. Não é um ponteiro para um array 2D.

O autor do tutorial provavelmente está confuso porque existe uma prática errada e incorreta dizendo que você deve alocar arrays 2D dynamics como este:

 // BAD! Do not do like this! int** heap_fiasco; heap_fiasco = malloc(X * sizeof(int*)); for(int x=0; x 

No entanto, isso não é um array 2D, é uma tabela de pesquisa lenta e fragmentada alocada em todo o heap. A syntax de acessar um item na tabela de consulta, heap_fiasco[x][y] , é semelhante à syntax de indexação de matriz, portanto, muitas pessoas acreditam que é assim que você aloca arrays 2D.

A maneira correta de alocar um array 2D dinamicamente é:

 // correct int (*array2d)[Y] = malloc(sizeof(int[X][Y])); 

Você pode dizer que o primeiro não é um array porque se você fizer memcpy(heap_fiasco, heap_fiasco2, sizeof(int[X][Y])) o código irá travar e queimar. Os itens não são alocados na memory adjacente.

Da mesma forma, o memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco)) também trava e grava, mas por outras razões: você obtém o tamanho de um ponteiro e não uma matriz.

Enquanto o memcpy(array2d, array2d_2, sizeof(*array2d)) funcionará, porque é um array 2D.

Os pointers demoraram um pouco para entender. Eu recomendo fortemente desenhar diagramas.

Por favor, leia e entenda esta parte do tutorial do C ++ (pelo menos no que diz respeito aos pointers que os diagramas realmente me ajudaram).

Dizer-lhe que você precisa de um ponteiro para um ponteiro para criar uma matriz bidimensional é uma mentira. Você não precisa, mas é uma maneira de fazer isso.

A memory é seqüencial. Se você quiser colocar 5 caracteres (letras) em uma linha como na palavra olá, você pode definir 5 variables ​​e sempre lembrar em que ordem usá-las, mas o que acontece quando você quer salvar uma palavra com 6 letras? Você define mais variables? Não seria mais fácil se você apenas os armazenasse na memory em sequência?

Então, o que você faz é perguntar ao sistema operacional por 5 caracteres (e cada caractere é um byte) e o sistema retorna para você um endereço de memory onde sua sequência de 5 caracteres começa. Você pega esse endereço e o armazena em uma variável que chamamos de ponteiro, porque aponta para sua memory.

O problema com pointers é que eles são apenas endereços. Como você sabe o que é armazenado nesse endereço? São 5 chars ou é um grande número binário de 8 bytes? Ou é uma parte de um arquivo que você carregou? Como você sabe?

É aqui que a linguagem de programação, como C, tenta ajudar, dando tipos. Um tipo informa o que a variável está armazenando e os pointers também possuem tipos, mas seus tipos informam para o que o ponteiro está apontando. Portanto, char * é um ponteiro para um local de memory que contém um único char ou uma sequência de chars . Infelizmente, a parte sobre quantos char estão lá você precisará lembrar de si mesmo. Geralmente você armazena essa informação em uma variável que você guarda para lembrar quantos caracteres estão lá.

Então, quando você quer ter uma estrutura de dados bidimensional, como você representa isso?

Isso é melhor explicado com um exemplo. Vamos fazer uma matriz:

 1 2 3 4 5 6 7 8 9 10 11 12 

Tem 4 colunas e 3 linhas. Como podemos armazenar isso?

Bem, podemos fazer 3 sequências de 4 números cada. A primeira seqüência é 1 2 3 4 , a segunda é 5 6 7 8 e a terceira e última seqüência é 9 10 11 12 . Então, se quisermos armazenar 4 números, pediremos ao sistema que reserve 4 números para nós e nos dê um ponteiro para eles. Estes serão pointers para números . No entanto, uma vez que precisamos de 3 deles, vamos pedir ao sistema para nos dar 3 pointers para números de pointers .

E é assim que você acaba com a solução proposta …

A outra maneira de fazer isso seria perceber que você precisa de 4 vezes 3 números e apenas pedir ao sistema que 12 números sejam armazenados em uma sequência. Mas então, como você acessa o número na linha 2 e na coluna 3? É aqui que a matemática entra, mas vamos tentar no nosso exemplo:

 1 2 3 4 5 6 7 8 9 10 11 12 

Se os armazenarmos um ao lado do outro, ficariam assim:

 offset from start: 0 1 2 3 4 5 6 7 8 9 10 11 numbers in memory: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] 

Então, nosso mapeamento é assim:

 row | column | offset | value 1 | 1 | 0 | 1 1 | 2 | 1 | 2 1 | 3 | 2 | 3 1 | 4 | 3 | 4 2 | 1 | 4 | 5 2 | 2 | 5 | 6 2 | 3 | 6 | 7 2 | 4 | 7 | 8 3 | 1 | 8 | 9 3 | 2 | 9 | 10 3 | 3 | 10 | 11 3 | 4 | 11 | 12 

E agora precisamos elaborar uma fórmula agradável e fácil para converter uma linha e coluna em um deslocamento … Eu voltarei quando tiver mais tempo … Agora preciso ir para casa (desculpe). ..

Edit: Estou um pouco atrasado, mas deixe-me continuar. Para encontrar o deslocamento de cada um dos números de uma linha e coluna, você pode usar a seguinte fórmula:

 offset = (row - 1) * 4 + (column - 1) 

Se você notar os dois -1 aqui e pensar sobre isso, você vai entender que é porque as numerações de linhas e colunas começam com 1 que temos que fazer isso e é por isso que os cientistas da computação preferem compensações baseadas em zero (por causa de esta fórmula). No entanto, com pointers em C, a própria linguagem aplica essa fórmula quando você usa um array multidimensional. E, portanto, esta é a outra maneira de fazer isso.

De sua pergunta o que eu entendo é que você está perguntando por que você precisa char ** para a variável que é declarada como * nomes []. Então a resposta é quando você simplesmente escreve nomes [], que é a syntax de array e array é basicamente um ponteiro.

Então, quando você escreve * nomes [], isso significa que você está apontando para uma matriz. E como matriz é basicamente um ponteiro, o que significa que você tem um ponteiro para um ponteiro e é por isso que o compilador não vai reclamar se você escrever

char ** cur_name = nomes;

Na linha acima, você está declarando um ponteiro para um ponteiro de caractere e, em seguida, inicializando-o com o ponteiro para um array (lembre-se array também é ponteiro).