Por que usar dupla indireção? ou Por que usar ponteiros para ponteiros?

272

Quando um duplo indireção deve ser usado em C? Alguém pode explicar com um exemplo?

O que eu sei é que uma dupla indireção é um ponteiro para um ponteiro. Por que eu precisaria de um ponteiro para um ponteiro?

manju
fonte
49
Seja cuidadoso; a frase "ponteiro duplo" também se refere ao tipo double*.
21416 Keith Thompson
1
Tome nota: a resposta a esta pergunta é diferente para C e C ++ - não adicione a tag c + a essa pergunta muito antiga.
BЈовић

Respostas:

479

Se você deseja ter uma lista de caracteres (uma palavra), pode usar char *word

Se você quiser uma lista de palavras (uma frase), poderá usar char **sentence

Se você quiser uma lista de frases (um monólogo), poderá usar char ***monologue

Se você deseja uma lista de monólogos (uma biografia), pode usar char ****biography

Se você quiser uma lista de biografias (uma bio-biblioteca), poderá usar char *****biolibrary

Se você quiser uma lista de bibliotecas biológicas (a ?? lol), poderá usar char ******lol

... ...

sim, eu sei que essas podem não ser as melhores estruturas de dados


Exemplo de uso com um muito, muito, muito chato lol

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

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

Resultado:

total de palavras na minha lol: 243
pmg
fonte
7
Só queria salientar que a arr[a][b][c]não é a ***arr. Ponteiro de ponteiros usa referências de referências, enquanto arr[a][b][c]é armazenado como uma matriz usual na ordem principal das linhas.
MCCCS
170

Uma razão é que você deseja alterar o valor do ponteiro passado para uma função como argumento da função; para fazer isso, é necessário um ponteiro para um ponteiro.

Em palavras simples, use **quando quiser preservar (OU reter alterações) a alocação ou atribuição de memória mesmo fora de uma chamada de função. (Portanto, passe essa função com o ponteiro duplo arg.)

Este pode não ser um exemplo muito bom, mas mostra o uso básico:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
Asha
fonte
14
o que seria diferente se alocar void allocate(int *p)e você o chamou como allocate(p)?
アレックス
@AlexanderSupertramp Sim. O código irá falhar. Por favor, veja a resposta de Silviu.
Abhishek 30/09
@Asha, qual é a diferença entre alocar (p) e alocar (& p)?
user2979872
1
@Asha - Não podemos simplesmente devolver o ponteiro? Se precisarmos mantê-lo nulo, o que é um caso de uso prático desse cenário?
Shabirmean
91
  • Digamos que você tenha um ponteiro. Seu valor é um endereço.
  • mas agora você deseja alterar esse endereço.
  • você poderia. ao fazer pointer1 = pointer2, você fornece ao ponteiro1 o endereço do ponteiro2.
  • mas! se você fizer isso dentro de uma função e desejar que o resultado persista após a conclusão da função, precisará fazer um trabalho extra. você precisa de um novo ponteiro3 apenas para apontar para o ponteiro1. passe o ponteiro3 para a função

  • aqui está um exemplo. observe primeiro a saída abaixo, para entender.

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

Aqui está a saída: ( leia isto primeiro )

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 
Brian Joseph Spinos
fonte
4
Essa é uma ótima resposta e realmente me ajudou a visualizar o objetivo e a utilidade de um ponteiro duplo.
11117 Justin
1
@ Justin, você checou minha resposta acima desta? seu limpador :)
Brian Joseph Spinos
10
Ótima resposta, só falta explicar que <code> void cant_change (int * x, int * z) </code> falha porque seus 'parâmetros são apenas novos ponteiros no escopo local que são inicializados da mesma forma a e f (por isso não são o mesmo que aef).
Pedro Reis
1
Simples? Realmente? ;)
alk
1
Esta resposta realmente explica um dos usos mais comuns de ponteiro para ponteiro, obrigado!
tonyjosi 27/01
49

Adicionando à resposta de Asha , se você usar um ponteiro único para o exemplo abaixo (por exemplo, assign1 ()), perderá a referência à memória alocada dentro da função.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

A razão pela qual isso ocorre é que no alloc1ponteiro é passado por valor. Portanto, quando é reatribuído ao resultado da mallocchamada dentro de alloc1, a alteração não se refere ao código em um escopo diferente.

Silviu
fonte
1
O que acontece se p for um ponteiro inteiro estático? Obtendo falha de segmentação.
precisa saber é o seguinte
free(p)não é suficiente, você precisa if(p) free(*p)também
Shijing Lv
@ShijingLv: No. *pavalia como intmantendo um valor de 10, passar intpara free () `é uma má ideia.
alq
A alocação feita em alloc1()introduz um vazamento de memória. O valor do ponteiro a ser liberado é perdido, retornando da função.
alq
Não (!) É necessário converter o resultado de malloc em C.
alk
23

Eu vi um exemplo muito bom hoje, desta postagem do blog , como resumir abaixo.

Imagine que você tenha uma estrutura para nós em uma lista vinculada, o que provavelmente é

typedef struct node
{
    struct node * next;
    ....
} node;

Agora você deseja implementar uma remove_iffunção, que aceita um critério de remoção rmcomo um dos argumentos e percorre a lista vinculada: se uma entrada satisfizer o critério (algo como rm(entry)==true), seu nó será removido da lista. No final, remove_ifretorna a cabeça (que pode ser diferente da cabeça original) da lista vinculada.

Você pode escrever

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

como seu forloop. A mensagem é que, sem ponteiros duplos, é necessário manter uma prevvariável para reorganizar os ponteiros e lidar com os dois casos diferentes.

Mas com ponteiros duplos, você pode escrever

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

Você não precisa de um prevagora porque pode modificar diretamente o que prev->nextapontou .

Para tornar as coisas mais claras, vamos seguir um pouco o código. Durante a remoção:

  1. if entry == *head: será *head (==*curr) = *head->next- headagora aponta para o ponteiro do novo nó de cabeçalho. Você faz isso alterando diretamente heado conteúdo de um novo ponteiro.
  2. if entry != *head: da mesma forma, *curré o que prev->nextapontou e agora aponta para entry->next.

Não importa em que caso, você pode reorganizar os ponteiros de maneira unificada com ponteiros duplos.

ziyuang
fonte
22

1. Conceito Básico -

Quando você declara o seguinte: -

1. char * ch - (chamado ponteiro de caractere)
- ch contém o endereço de um único caractere.
- (* ch) desreferirá o valor do caractere.

2. char ** ch -
'ch' contém o endereço de uma matriz de ponteiros de caracteres. (como em 1)
'* ch' contém o endereço de um único caractere. (Observe que é diferente de 1, devido à diferença na declaração).
(** ch) desreferirá o valor exato do personagem.

A adição de mais ponteiros expande a dimensão de um tipo de dados, do caractere para a seqüência de caracteres, para a matriz de seqüências de caracteres, etc.

Portanto, o uso do ponteiro depende de como você o declara.

Aqui está um código simples ..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Outra aplicação de ponteiros duplos -
(isso também cobriria passagem por referência)

Suponha que você queira atualizar um caractere de uma função. Se você tentar o seguinte: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

A saída será AA. Isso não funciona, pois você "Passou por valor" para a função.

A maneira correta de fazer isso seria -

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Agora estenda esse requisito para atualizar uma sequência em vez de caractere.
Para isso, você precisa receber o parâmetro na função como um ponteiro duplo.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

Neste exemplo, o método espera um ponteiro duplo como parâmetro para atualizar o valor de uma sequência.

Bhavuk Mathur
fonte
#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } Mas você pode fazer isso sem usar o ponteiro duplo também.
kumar
" char ** ch - 'ch' contém o endereço de uma matriz de ponteiros de caracteres. " Não, contém o endereço do primeiro elemento de uma matriz de charponteiros. Um ponteiro para uma matriz de char*seria digitado, por exemplo, como este: char(*(*p)[42])define pcomo ponteiro para uma matriz de 42 ponteiros para char.
alq
O último trecho está completamente quebrado. Para iniciantes: aqui *str = ... stré desreferenciado, não inicializado, invocando um comportamento indefinido.
alq
Esta malloc(sizeof(char) * 10);não aloca espaço para 10 ponteiro para charmas para 10 charúnica ..
alk
Esse loop for(i=0;i<10;i++) { str = ... falha ao usar o índice i.
alq
17

Ponteiros para ponteiros também são úteis como "alças" para a memória, onde você deseja passar um "identificador" entre as funções para recolocar a memória. Isso basicamente significa que a função pode alterar a memória apontada pelo ponteiro dentro da variável do identificador, e cada função ou objeto que estiver usando o identificador apontará corretamente para a memória recém-realocada (ou alocada). Bibliotecas gostam de fazer isso com tipos de dados "opacos", ou seja, quando você não precisa se preocupar com o que eles estão fazendo com a memória apontada, basta passar o "identificador" entre os funções da biblioteca para executar algumas operações nessa memória ...

Por exemplo:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

Espero que isto ajude,

Jason

Jason
fonte
Qual é o motivo do tipo de identificador ser char não assinado **? Void ** funcionaria tão bem?
Connor Clark
5
unsigned charé usado especificamente porque estamos armazenando um ponteiro para dados binários que serão representados como bytes brutos. O uso voidexigirá uma conversão em algum momento e geralmente não é tão legível quanto a intenção do que está sendo feito.
Jason
7

Exemplo simples que você provavelmente já viu muitas vezes antes

int main(int argc, char **argv)

No segundo parâmetro, você tem: ponteiro para ponteiro para char.

Observe que a notação do ponteiro ( char* c) e a notação da matriz ( char c[]) são intercambiáveis ​​nos argumentos da função. Então você também pode escrever char *argv[]. Em outras palavras char *argv[]e char **argvsão intercambiáveis.

O que o descrito acima é de fato uma matriz de seqüências de caracteres (os argumentos da linha de comando que são fornecidos a um programa na inicialização).

Consulte também esta resposta para obter mais detalhes sobre a assinatura da função acima.

plats1
fonte
2
"notação de ponteiro ( char* c) e notação de matriz ( char c[]) são intercambiáveis" (e têm o mesmo significado exato) nos argumentos da função . Eles são diferentes, porém, fora dos argumentos da função.
Pmg
6

Strings são um ótimo exemplo de uso de ponteiros duplos. A string em si é um ponteiro; portanto, a qualquer momento que você precisar apontar para uma string, precisará de um ponteiro duplo.

Drysdam
fonte
5

Por exemplo, convém garantir que, ao liberar a memória de algo, defina o ponteiro como nulo posteriormente.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Quando você chama essa função, você o chama com o endereço de um ponteiro

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Agora myMemoryestá definido como NULL e qualquer tentativa de reutilização estará obviamente muito errada.

Jeff Foster
fonte
1
ela deve ser if(*memory)efree(*memory);
Asha
1
Bom ponto, perda de sinal entre o cérebro e o teclado. Eu editei para fazer um pouco mais de sentido.
Jeff Foster
Por que não podemos fazer o seguinte ... void safeFree (void * memória) {if (memory) {free (memory); memória = NULL; }}
Peter_pk 27/04
@Peter_pk Atribuir memória a nulo não ajudaria porque você passou um ponteiro por valor, não por referência (daí o exemplo de um ponteiro para um ponteiro).
Jeff Foster
2

Por exemplo, se você deseja acesso aleatório a dados não contíguos.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- em C

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Você armazena um ponteiro pque aponta para uma matriz de ponteiros. Cada ponteiro aponta para um dado.

Se sizeof(T)for grande, pode não ser possível alocar um bloco contíguo (ou seja, usando malloc) de sizeof(T) * nbytes.

log0
fonte
1
Não (!) É necessário converter o resultado de malloc em C.
alk
2

Uma coisa que eu uso constantemente é quando tenho uma matriz de objetos e preciso realizar pesquisas (pesquisa binária) neles por campos diferentes.
Eu mantenho a matriz original ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Em seguida, faça uma matriz de ponteiros classificados para os objetos.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Você pode criar quantas matrizes de ponteiro classificadas precisar e, em seguida, usar uma pesquisa binária na matriz de ponteiros classificados para acessar o objeto necessário pelos dados que você possui. A matriz original de objetos pode permanecer sem classificação, mas cada matriz de ponteiro será classificada por seu campo especificado.

DavidMFrey
fonte
2

Por que ponteiros duplos?

O objetivo é alterar o que o aluno A aponta, usando uma função.

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */
Brian Joseph Spinos
fonte
1
Não (!) É necessário converter o resultado de malloc em C.
alk
2

A seguir, é apresentado um exemplo C ++ muito simples que mostra que, se você deseja usar uma função para definir um ponteiro para apontar para um objeto, precisa de um ponteiro para um ponteiro . Caso contrário, o ponteiro continuará revertendo para nulo .

(Uma resposta C ++, mas acredito que seja a mesma em C.)

(Além disso, para referência: Google ("passar por valor c ++") = "Por padrão, os argumentos em C ++ são passados ​​por valor. Quando um argumento é passado por valor, o valor do argumento é copiado no parâmetro da função.")

Então, queremos definir o ponteiro bigual à string a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

O que acontece na fila Function_1(&a, b);?

  • O "valor" &main::a(um endereço) é copiado para o parâmetro std::string* Function_1::a. Portanto, Function_1::aé um ponteiro para (ou seja, o endereço de memória) da string main::a.

  • O "valor" main::b(um endereço na memória) é copiado para o parâmetro std::string* Function_1::b. Portanto, agora existem 2 desses endereços na memória, ambos ponteiros nulos. Na linha b = a;, a variável local Function_1::bé alterada para igual Function_1::a(= &main::a), mas a variável main::bnão é alterada. Após a chamada para Function_1, main::bainda é um ponteiro nulo.

O que acontece na fila Function_2(&a, &b);?

  • O tratamento da avariável é o mesmo: dentro da função, Function_2::aé o endereço da string main::a.

  • Mas a variável bagora está sendo passada como um ponteiro para um ponteiro. O "valor" de &main::b(o endereço do ponteiro main::b ) é copiado std::string** Function_2::b. Portanto, em Function_2, desreferenciando isso como *Function_2::birá acessar e modificar main::b. Portanto, a linha *b = a;está realmente configurando main::b(um endereço) igual a Function_2::a(= endereço de main::a) que é o que queremos.

Se você deseja usar uma função para modificar uma coisa, seja um objeto ou um endereço (ponteiro), é necessário passar um ponteiro para essa coisa. O que você realmente passa não pode ser modificado (no escopo da chamada) porque é feita uma cópia local.

(Uma exceção é se o parâmetro for uma referência, como std::string& a. Mas geralmente são const. Geralmente, se você chamar f(x), se xfor um objeto, poderá assumir que f não será modificado x. Mas se xfor um ponteiro, deverá suponha que f possa modificar o objeto apontado por x.)

jt117
fonte
O código C ++ para responder a uma pergunta em C não é a melhor ideia.
alq
1

Um pouco atrasado para a festa, mas espero que isso ajude alguém.

Nas matrizes C, sempre alocam memória na pilha, portanto, uma função não pode retornar uma matriz (não estática) devido ao fato de que a memória alocada na pilha é liberada automaticamente quando a execução chega ao final do bloco atual. Isso é realmente irritante quando você deseja lidar com matrizes bidimensionais (ou seja, matrizes) e implementar algumas funções que podem alterar e retornar matrizes. Para conseguir isso, você pode usar um ponteiro para ponteiro para implementar uma matriz com memória alocada dinamicamente:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Aqui está uma ilustração:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

O ponteiro duplo para o ponteiro duplo A aponta para o primeiro elemento A [0] de um bloco de memória cujos elementos são os próprios ponteiros duplos. Você pode imaginar esses ponteiros duplos como as linhas da matriz. Essa é a razão pela qual cada ponteiro duplo aloca memória para elementos num_cols do tipo double. Além disso, A [i] aponta para a i-ésima linha, ou seja, A [i] aponta para A [i] [0] e esse é apenas o primeiro elemento duplo do bloco de memória para a i-ésima linha. Finalmente, você pode acessar o elemento na i-ésima linha e j-ésima coluna facilmente com A [i] [j].

Aqui está um exemplo completo que demonstra o uso:

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

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}
joni
fonte
0

Hoje, usei ponteiros duplos enquanto programava algo para o trabalho, para poder responder por que tivemos que usá-los (é a primeira vez que tive que usar ponteiros duplos). Tivemos que lidar com a codificação em tempo real de quadros contidos em buffers que são membros de algumas estruturas. No codificador, tivemos que usar um ponteiro para uma dessas estruturas. O problema era que nosso ponteiro estava sendo alterado para apontar para outras estruturas de outro thread. Para usar a estrutura atual no codificador, tive que usar um ponteiro duplo, a fim de apontar para o ponteiro que estava sendo modificado em outro thread. Não era óbvio a princípio, pelo menos para nós, que tínhamos que adotar essa abordagem. Muitos endereços foram impressos no processo :)).

Você DEVE usar ponteiros duplos quando trabalha em ponteiros que são alterados em outros locais da sua aplicação. Você também pode achar ponteiros duplos obrigatórios quando lida com o hardware que retorna e endereça a você.

Axenie Ionut
fonte
0

Compare o valor de modificação da variável com o valor de modificação do ponteiro :

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

Isso me ajudou a evitar o retorno do valor do ponteiro quando o ponteiro foi modificado pela função chamada (usada na lista vinculada individual).

VELHO (ruim):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NOVO (melhor):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Sany
fonte