Usos para vários níveis de dereferências de ponteiro?

Quando o uso de pointers em qualquer idioma requer que alguém use mais de um, digamos um ponteiro triplo. Quando faz sentido usar um ponteiro triplo em vez de usar apenas um ponteiro normal?

Por exemplo:

char * * *ptr; 

ao invés de

 char *ptr; 

cada estrela deve ser lida como “apontada por um ponteiro”

 char *foo; 

é “char que apontado por um ponteiro foo”. Contudo

 char *** foo; 

é “char que apontado por um ponteiro que é apontado para um ponteiro que é apontado para um ponteiro foo”. Assim foo é um ponteiro. Nesse endereço é um segundo ponteiro. No endereço apontado por esse é um terceiro ponteiro. A desreferência do terceiro ponteiro resulta em um caractere. Se isso é tudo, é difícil fazer muito disso.

Ainda é possível obter algum trabalho útil, no entanto. Imagine que estamos escrevendo um substituto para o bash, ou algum outro programa de controle de processo. Queremos gerenciar as invocações de nossos processos de maneira orientada a objects …

 struct invocation { char* command; // command to invoke the subprocess char* path; // path to executable char** env; // environment variables passed to the subprocess ... } 

Mas nós queremos fazer algo chique. Queremos ter uma maneira de procurar todos os diferentes conjuntos de variables ​​de ambiente, conforme visto por cada subprocess. para fazer isso, reunimos cada conjunto de membros env das instâncias de invocação em uma matriz env_list e passamos para a function que lida com isso:

 void browse_env(size_t envc, char*** env_list); 

Se você trabalha com “objects” em C, você provavelmente tem isto:

 struct customer { char *name; char *address; int id; } typedef Customer; 

Se você quiser criar um object, você faria algo assim:

 Customer *customer = malloc(sizeof Customer); // Initialise state. 

Estamos usando um ponteiro para uma struct aqui porque os argumentos do struct são passados ​​por valor e precisamos trabalhar com um object. (Também: Objective-C, uma linguagem wrapper orientada a objects para C, usa internamente, mas visivelmente, pointers para struct s.)

Se eu precisar armazenar vários objects, eu uso uma matriz:

 Customer **customers = malloc(sizeof(Customer *) * 10); int customerCount = 0; 

Como uma variável de matriz em C aponta para o primeiro item, eu uso um ponteiro… de novo. Agora eu tenho pointers duplos.

Mas agora imagine que tenho uma function que filtra a matriz e retorna uma nova. Mas imagine que não é possível por meio do mecanismo de retorno porque ele deve retornar um código de erro – minha function acessa um database. Eu preciso fazer isso através de um argumento por referência. Esta é a assinatura da minha function:

 int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount); 

A function recebe uma série de clientes e retorna uma referência a uma matriz de clientes (que são pointers para uma struct ). Também leva o número de clientes e retorna o número de clientes filtrados (novamente, por argumento de referência).

Eu posso chamar assim:

 Customer **result, int n = 0; int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n); 

Eu poderia continuar imaginando mais situações … Esta é sem o typedef :

 int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns); 

Obviamente, eu seria um desenvolvedor horrível e / ou sádico para deixar assim. Então, usando:

 typedef Customer *CustomerArray; typedef CustomerArray *CustomerMatrix; 

Eu só posso fazer isso:

 int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns); 

Se seu aplicativo for usado em um hotel em que você usa uma matriz por nível, provavelmente precisará de uma matriz para uma matriz:

 int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels); 

Ou apenas isto:

 typedef CustomerMatrix *Hotel; int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels); 

Não me faça nem começar em uma série de hotéis:

 int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels); 

… Organizados em uma matriz (algum tipo de grande empresa hoteleira?):

 int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns); 

O que estou tentando dizer é que você pode imaginar aplicativos malucos para múltiplas indirecções. Apenas certifique-se de usar typedef se multi-pointers forem uma boa idéia e você decidir usá-los.

(Este post conta como uma aplicação para um SevenStarDeveloper ?)

Um ponteiro é simplesmente uma variável que contém um endereço de memory.

Portanto, você usa um ponteiro para um ponteiro quando deseja manter o endereço de uma variável de ponteiro.

Se você quiser retornar um ponteiro e já estiver usando a variável de retorno para algo, você passará o endereço de um ponteiro. A function então desreferencia esse ponteiro para que ele possa definir o valor do ponteiro. Ou seja, o parâmetro dessa function seria um ponteiro para um ponteiro.

Vários níveis de indireção também são usados ​​para matrizes multidimensionais. Se você quiser retornar um array bidimensional, você usaria um ponteiro triplo. Ao usá-los para matrizes multidimensionais, tenha cuidado para projetar corretamente ao passar por cada nível de indireção.

Aqui está um exemplo de retornar um valor de ponteiro por meio de um parâmetro:

 //Not a very useful example, but shows what I mean... bool getOffsetBy3Pointer(const char *pInput, char **pOutput) { *pOutput = pInput + 3; return true; } 

E você chama essa function assim:

 const char *p = "hi you"; char *pYou; bool bSuccess = getOffsetBy3Pointer(p, &pYou); assert(!stricmp(pYou, "you")); 

A varinha do ImageMagicks tem uma function que é declarada como

 WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *) 

Eu não estou inventando isso .

Arrays dinamicamente alocados N-dimensionais, onde N> 3, requerem três ou mais níveis de indireção em C.

Um uso padrão de pointers duplos, por exemplo: myStruct ** ptrptr, é como um ponteiro para um ponteiro. Por exemplo, como um parâmetro de function, isso permite que você altere a estrutura real para a qual o chamador está apontando, em vez de apenas poder alterar os valores dentro dessa estrutura.

Char *** foo pode ser interpretado como um ponteiro para um array bidimensional de strings.

Você usa um nível extra de indireção – ou apontando – quando necessário, não porque seria divertido. Você raramente vê pointers triplos; Eu não acho que eu já vi um ponteiro quádruplo (e minha mente iria se aborrecer se eu fizesse).

As tabelas de estado podem ser representadas por um array 2D de um tipo de dados apropriado (pointers para uma estrutura, por exemplo). Quando escrevi um código quase genérico para fazer tabelas de estado, lembro-me de ter uma function que recebia um ponteiro triplo – que representava uma matriz 2D de pointers para estruturas. Ai!

  int main( int argc, char** argv ); 

Funções que encapsulam a criação de resources geralmente usam pointers duplos. Ou seja, você passa o endereço de um ponteiro para um recurso. A function pode, então, criar o recurso em questão e definir o ponteiro para apontar para ele. Isso só é possível se tiver o endereço do ponteiro em questão, portanto, deve ser um ponteiro duplo.

Se você tiver que modificar um ponteiro dentro de uma function, você deve passar uma referência para ela.

Faz sentido usar um ponteiro para um ponteiro sempre que o ponteiro apontar para um ponteiro (essa cadeia é ilimitada, portanto, “pointers triplos”, etc.) são possíveis.

O motivo para criar esse código é porque você quer que o compilador / interpretador seja capaz de verificar corretamente os tipos que você está usando (evite bugs misteriosos).

Você não precisa usar esses tipos – você sempre pode simplesmente usar um simples “void *” e typecast sempre que precisar realmente referenciar o ponteiro e acessar os dados que o ponteiro está direcionando. Mas isso geralmente é uma prática ruim e propensa a erros – certamente há casos em que o uso de void * é realmente bom e torna o código muito mais elegante. Pense nisso como seu último recurso.

=> É principalmente para ajudar o compilador para se certificar de que as coisas são usadas da maneira que devem ser usadas.

Para ser honesto, raramente vi um ponteiro triplo.

Eu olhei na pesquisa de código do google, e há alguns exemplos , mas não muito esclarecedores. (veja os links no final – SO não gosta deles)

Como outros já mencionaram, os pointers duplos que você verá de tempos em tempos. Ponteiros simples simples são úteis porque apontam para algum recurso alocado. Os pointers duplos são úteis porque você pode passá-los para uma function e fazer com que a function preencha o ponteiro “simples” para você.

Parece que você precisa de alguma explicação sobre quais são os pointers e como eles funcionam? Você precisa entender isso primeiro, se ainda não o fez.

Mas essa é uma questão separada (:

http://www.google.com/codesearch/p?hl=pt-BR#e_ObwTAVPyo/security/nss/lib/ckfw/capi/ckcapi.h&q=***%20lang:c&l=301

http://www.google.com/codesearch/p?hl=pt-BR#eVvq2YWVpsY/openssl-0.9.8e/crypto/ec/ec_mult.c&q=***%20lang:c&l=344

Ponteiros para pointers raramente são usados ​​em C ++. Eles basicamente têm dois usos.

O primeiro uso é passar um array. char** , por exemplo, é um ponteiro para ponteiro para char, que geralmente é usado para transmitir uma matriz de strings. Ponteiros para arrays não funcionam por bons motivos, mas esse é um tópico diferente (veja o FAQ comp.lang.c se você quiser saber mais). Em alguns casos raros, você pode ver um terceiro * usado para uma matriz de matrizes, mas é comum armazenar mais tudo em um array contíguo e indexá-lo manualmente (por exemplo, array[x+y*width] vez de array[x][y] ). No C ++, no entanto, isso é muito menos comum devido a classs de contêiner.

O segundo uso é passar por referência. Um parâmetro int* permite que a function modifique o número inteiro apontado pela function de chamada e é comumente usado para fornecer vários valores de retorno. Esse padrão de passagem de parâmetros por referência para permitir retornos múltiplos ainda está presente em C ++, mas, como outros usos de passagem por referência, é geralmente substituído pela introdução de referências reais. A outra razão para a passagem por referência – evitando a cópia de construções complexas – também é possível com a referência C ++.

C ++ tem um terceiro fator que reduz o uso de múltiplos pointers: tem string . Uma referência a string pode pegar o tipo char** em C, para que a function possa alterar o endereço da variável de string que é passada, mas em C ++, normalmente vemos string& instead.

Quando você usa estruturas de dados aninhadas dinamicamente alocadas (ou ligadas por pointers). Essas coisas estão todas ligadas por pointers.

Particularmente em dialetos de encadeamento único de C que não usam agressivamente a análise de aliasing baseada em tipo, às vezes pode ser útil escrever gerenciadores de memory que possam acomodar objects relocáveis. Em vez de fornecer aos aplicativos pointers diretos para blocos de memory, o aplicativo recebe pointers em uma tabela de descritores de identificadores, cada um contendo um ponteiro para um bloco real de memory junto com uma palavra indicando seu tamanho. Se alguém precisa alocar espaço para uma struct woozle , pode-se dizer:

 struct woozle **my_woozle = newHandle(sizeof struct woozle); 

e depois acessar (um pouco desajeitadamente na syntax C – a syntax é mais limpa em Pascal): (* my_woozle) -> someField = 23; É importante que os aplicativos não mantenham pointers diretos para o destino de qualquer identificador nas chamadas a funções que alocam memory, mas se existir um único ponteiro para cada bloco identificado por um identificador, o gerenciador de memory poderá mover as coisas caso a fragmentação se torne um problema.

A abordagem não funciona tão bem em dialetos de C que perseguem agressivamente o aliasing baseado em tipos, já que o ponteiro retornado por NewHandle não identifica um ponteiro do tipo struct woozle* mas identifica um ponteiro do tipo void* , e mesmo em plataformas onde esses tipos de ponteiro teriam a mesma representação, o Padrão não exige que as implementações interpretem um ponteiro convertido como uma indicação de que ele deve esperar que o aliasing possa ocorrer.

A dupla indireta simplifica muitos algoritmos de balanceamento de trees, nos quais normalmente se deseja ser capaz de “desvincular” com eficiência uma subtree de seu pai. Por exemplo, uma implementação de tree do AVL pode usar:

 void rotateLeft(struct tree **tree) { struct tree *t = *tree, *r = t->right, *rl = r->left; *tree = r; r->left = t; t->right = rl; } 

Sem o “ponteiro duplo”, teríamos que fazer algo mais complicado, como explicitamente manter o controle do pai de um nó e se é um ramo esquerdo ou direito.