O que significa "desreferenciar" um ponteiro?

541

Por favor, inclua um exemplo com a explicação.

asir
fonte
isso pode ajudá-lo: stackoverflow.com/questions/2795575/…
Harry Joy
24
int *p;definiria um ponteiro para um número inteiro e *pdesdiferenciaria esse ponteiro, o que significa que ele realmente recuperaria os dados para os quais p aponta.
Peyman
4
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) é um ótimo vídeo sobre ponteiros que podem esclarecer as coisas. @ Erik- Você gosta de colocar o link da Stanford CS Library. Há tantas guloseimas por lá ... #
templatetypedef
6
A resposta de Harry é o oposto de útil aqui.
Jim Balter

Respostas:

731

Revendo a terminologia básica

É geralmente bom o suficiente - a menos que você está programando montagem - prever um ponteiro contém um endereço de memória numérica, com 1 referente ao segundo byte na memória do processo, 2 o terceiro, 3 a quarta e assim por diante ....

  • O que aconteceu com 0 e o primeiro byte? Bem, vamos falar disso mais tarde - veja os ponteiros nulos abaixo.
  • Para uma definição mais precisa do que os ponteiros armazenam e como a memória e os endereços se relacionam, consulte "Mais sobre endereços de memória e por que você provavelmente não precisa saber" no final desta resposta.

Quando você deseja acessar os dados / valor na memória para a qual o ponteiro aponta - o conteúdo do endereço com esse índice numérico -, desrefere o ponteiro.

Linguagens de computador diferentes têm notações diferentes para dizer ao compilador ou intérprete que você agora está interessado no valor (atual) do objeto apontado - eu me concentro abaixo em C e C ++.

Um cenário de ponteiro

Considere em C, dado um ponteiro como pabaixo ...

const char* p = "abc";

... quatro bytes com os valores numéricos usados ​​para codificar as letras 'a', 'b', 'c' e um byte 0 para indicar o final dos dados textuais, são armazenados em algum lugar na memória e o endereço numérico desse os dados são armazenados em p. Dessa forma, C codifica texto na memória é conhecido como ASCIIZ .

Por exemplo, se a literal da string estivesse no endereço 0x1000 e pum ponteiro de 32 bits em 0x2000, o conteúdo da memória seria:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Note-se que não há uma variável nome / identificador para o endereço 0x1000, mas podemos indiretamente consulte a string literal usando um ponteiro armazenar o endereço: p.

Desreferenciando o ponteiro

Para nos referir aos caracteres paos quais apontamos, fazemos a desreferência pusando uma dessas notações (novamente, para C):

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

Você também pode mover os ponteiros pelos dados apontados, desmarcando-os à medida que avança:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Se você tiver alguns dados que podem ser gravados, poderá fazer coisas como estas:

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

Acima, você deve saber em tempo de compilação que precisaria de uma variável chamada x, e o código solicita ao compilador que organize onde deve ser armazenado, garantindo que o endereço esteja disponível via &x.

Desreferenciando e Acessando um Membro de Dados da Estrutura

Em C, se você tiver uma variável que é um ponteiro para uma estrutura com membros de dados, poderá acessar esses membros usando o ->operador de desreferenciação:

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

Tipos de dados de vários bytes

Para usar um ponteiro, um programa de computador também precisa de algumas dicas sobre o tipo de dados que está sendo apontado - se esse tipo de dado precisar de mais de um byte para representar, o ponteiro normalmente apontará para o byte de número mais baixo dos dados.

Então, analisando um exemplo um pouco mais complexo:

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

Ponteiros para memória alocada dinamicamente

Às vezes, você não sabe quanta memória precisará até que seu programa esteja em execução e veja quais dados são lançados nele ... então você pode alocar dinamicamente a memória usando malloc. É prática comum armazenar o endereço em um ponteiro ...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

Em C ++, a alocação de memória é normalmente feita com o newoperador e a desalocação com delete:

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

Veja também ponteiros inteligentes em C ++ abaixo.

Perder e vazar endereços

Freqüentemente, um ponteiro pode ser a única indicação de onde existem alguns dados ou buffer na memória. Se for necessário o uso contínuo desses dados / buffer, ou a capacidade de chamar free()ou deleteevitar vazamento de memória, o programador deverá operar em uma cópia do ponteiro ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... ou orquestrar cuidadosamente a reversão de quaisquer alterações ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

Ponteiros inteligentes em C ++

No C ++, é uma prática recomendada usar objetos de ponteiro inteligente para armazenar e gerenciar os ponteiros, desalocando-os automaticamente quando os destruidores dos ponteiros inteligentes são executados. Desde o C ++ 11, a Biblioteca Padrão fornece dois, unique_ptrpara quando houver um único proprietário para um objeto alocado ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... e shared_ptrpela propriedade das ações (usando a contagem de referência ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

Ponteiros nulos

Em C NULLe 0- e adicionalmente em C ++ nullptr- pode ser usado para indicar que um ponteiro não contém atualmente o endereço de memória de uma variável e não deve ser desreferenciado ou usado na aritmética de ponteiros. Por exemplo:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

Em C e C ++, assim como os tipos numéricos incorporados não necessariamente são padronizados para 0, nem boolspara false, nem sempre os ponteiros são definidos como NULL. Tudo isso é definido como 0 / false / NULL quando são staticvariáveis ​​ou (apenas C ++) variáveis ​​de membro diretas ou indiretas de objetos estáticos ou suas bases, ou sofrem inicialização zero (por exemplo, new T();e new T(x, y, z);executam inicialização zero nos membros de T, incluindo ponteiros, enquanto new T;não).

Além disso, quando você atribuir 0, NULLe nullptrpara um ponteiro os bits no ponteiro não são necessariamente todos de reset: o ponteiro não pode conter "0" ao nível do hardware, ou consulte o endereço 0 no seu espaço de endereço virtual. O compilador é permitido para armazenar outra coisa lá se tiver motivos para, mas o que ele faz - se você vir e comparar o ponteiro para 0, NULL, nullptrou outro ponteiro que foi atribuído qualquer um desses, o trabalho de comparação obrigação como esperado. Portanto, abaixo do código-fonte no nível do compilador, "NULL" é potencialmente um pouco "mágico" nas linguagens C e C ++ ...

Mais sobre endereços de memória e por que você provavelmente não precisa saber

Mais estritamente, os ponteiros inicializados armazenam um padrão de bits que identifica um NULLou um endereço de memória (geralmente virtual ).

O caso simples é onde esse é um deslocamento numérico em todo o espaço de endereço virtual do processo; em casos mais complexos, o ponteiro pode ser relativo a alguma área específica da memória, que a CPU pode selecionar com base nos registros de "segmento" da CPU ou em algum tipo de ID de segmento codificado no padrão de bits e / ou procurando em lugares diferentes, dependendo do instruções de código de máquina usando o endereço.

Por exemplo, uma int*inicialização adequada para apontar para uma intvariável pode - após converter para float*- a memória de acesso na memória "GPU" bastante distinta da memória em que a intvariável está; depois, uma vez convertida e usada como ponteiro de função, ela pode apontar para opcodes de máquina de retenção de memória distintos para o programa (com o valor numérico do int*efetivamente um ponteiro aleatório e inválido nessas outras regiões de memória).

Linguagens de programação 3GL como C e C ++ tendem a esconder essa complexidade, de modo que:

  • Se o compilador fornecer um ponteiro para uma variável ou função, você pode desdiferenciá-lo livremente (desde que a variável não seja destruída / desalocada) e é problema do compilador se, por exemplo, um determinado registro de segmento de CPU precisa ser restaurado antes, ou um instrução de código de máquina distinta usada

  • Se você obtiver um ponteiro para um elemento em uma matriz, poderá usar a aritmética do ponteiro para mover-se para qualquer outro lugar na matriz, ou mesmo para formar um endereço que seja legal para comparar com outros ponteiros para elementos na matriz (ou que foram movidos de maneira semelhante pela aritmética do ponteiro para o mesmo valor de um passado-o-final); novamente em C e C ++, cabe ao compilador garantir que isso "funcione"

  • Funções específicas do SO, por exemplo, mapeamento de memória compartilhada, podem fornecer ponteiros e eles "simplesmente funcionam" dentro do intervalo de endereços que faz sentido para eles

  • As tentativas de mover ponteiros legais além desses limites, ou converter números arbitrários em ponteiros ou usar ponteiros convertidos para tipos não relacionados, geralmente têm um comportamento indefinido , portanto devem ser evitados em bibliotecas e aplicativos de nível superior, mas codificam para sistemas operacionais, drivers de dispositivo, etc. talvez precise confiar em comportamentos deixados indefinidos pelo padrão C ou C ++, que, no entanto, são bem definidos por sua implementação ou hardware específico.

Tony Delroy
fonte
é p[1] e *(p + 1) idêntico ? Ou seja, gera p[1] e *(p + 1)gera as mesmas instruções?
Pacerier 22/09
2
@Pacerier: de 6.5.2.1/2 no rascunho da norma N1570 C (primeira vez que encontrei on-line) "A definição do operador subscrito [] é que E1 [E2] é idêntico a (* ((E1) + (E2)) ). " - Não consigo imaginar nenhuma razão para que um compilador não os converta imediatamente em representações idênticas em um estágio inicial de compilação, aplicando as mesmas otimizações depois disso, mas não vejo como alguém possa provar definitivamente que o código seria idêntico sem examinar todos os compiladores já escritos.
Tony Delroy
3
@Mel: o valor 1000 hexadecimal é muito grande para codificar em um único byte (8 bits) de memória: você só pode armazenar números não assinados de 0 a 255 em um byte. Portanto, você não pode armazenar 1000 hexadecimais no "apenas" endereço 2000. Em vez disso, um sistema de 32 bits usaria 32 bits - que são quatro bytes - com endereços de 2000 a 2003. Um sistema de 64 bits usaria 64 bits - 8 bytes - de 2000 a 2007. De qualquer maneira, o endereço base de pé apenas 2000: se você tivesse outro ponteiro p, teria que armazenar 2000 em seus quatro ou oito bytes. Espero que ajude! Felicidades.
Tony Delroy
1
@TonyDelroy: Se uma união ucontiver uma matriz arr, o gcc e o clang reconhecerão que o lvalue u.arr[i]pode acessar o mesmo armazenamento que outros membros da união, mas não reconhecerão que o lvalue *(u.arr+i)pode fazê-lo. Não tenho certeza se os autores desses compiladores pensam que o último chama UB, ou que o primeiro chama UB, mas eles devem processá-lo de qualquer maneira, mas eles claramente veem as duas expressões como diferentes.
precisa
3
Eu raramente vi ponteiros e seu uso no C / C ++ de forma concisa e simples.
precisa saber é o seguinte
102

Desreferenciar um ponteiro significa obter o valor armazenado no local da memória apontado pelo ponteiro. O operador * é usado para fazer isso e é chamado de operador de desreferenciação.

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.
Mahesh
fonte
15
Um ponteiro não aponta para um valor , aponta para um objeto .
Keith Thompson
52
@KeithThompson Um ponteiro não aponta para um objeto, ele aponta para um endereço de memória, onde um objeto (talvez um primitivo) está localizado.
mg30rg
4
@ mg30rg: Não tenho certeza de que distinção você está fazendo. Um valor de ponteiro é um endereço. Um objeto, por definição, é uma "região de armazenamento de dados no ambiente de execução, cujo conteúdo pode representar valores". E o que você quer dizer com "primitivo"? O padrão C não usa esse termo.
Keith Thompson
6
@KeithThompson Eu mal estava apontando, que você realmente não agregou valor à resposta, você estava apenas focando na terminologia (e fez isso errado também). O valor do ponteiro certamente é um endereço, é assim que "aponta" para um endereço de memória. A palavra "objeto" em nosso mundo orientado para OOP pode ser enganosa, porque pode ser interpretada como "instância de classe" (sim, eu não sabia que a pergunta estava rotulada como [C] e não [C ++]) e usei a palavra "primitivo" como no oposto de "copmlex" (estrutura de dados como uma struct ou classe).
mg30rg
3
Deixe-me acrescentar a esta resposta que o operador de subscrito da matriz []também desreferencia um ponteiro ( a[b]está definido para significar *(a + b)).
cmaster - restabelecer monica
20

Um ponteiro é uma "referência" a um valor .. bem como um número de chamada de biblioteca é uma referência a um livro. "Desreferenciando" o número de chamada está passando e recuperando fisicamente esse livro.

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

Se o livro não estiver lá, o bibliotecário começa a gritar, fecha a biblioteca e duas pessoas estão prontas para investigar a causa de uma pessoa encontrar um livro que não está lá.

bobobobo
fonte
17

Em palavras simples, desreferenciar significa acessar o valor de um determinado local de memória contra o qual esse ponteiro está apontando.

Fahad Naeem
fonte
7

Código e explicação do básico do ponteiro :

A operação de desreferência começa no ponteiro e segue a seta para acessar o ponteiro. O objetivo pode ser olhar para o estado de ponta ou alterar o estado de ponta. A operação de desreferência em um ponteiro funciona apenas se o ponteiro tiver um pontapé - o ponteiro deve ser alocado e o ponteiro deve estar definido para apontar para ele. O erro mais comum no código do ponteiro é esquecer de configurar o ponta. O travamento de tempo de execução mais comum devido a esse erro no código é uma falha na operação de desreferência. Em Java, a desreferência incorreta será sinalizada educadamente pelo sistema de tempo de execução. Em linguagens compiladas como C, C ++ e Pascal, a desreferência incorreta às vezes falha e outras vezes corrompem a memória de alguma maneira sutil e aleatória.

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}
atp
fonte
Você realmente precisa alocar memória para onde x deve apontar. Seu exemplo tem um comportamento indefinido.
Peyman
3

Acho que todas as respostas anteriores estão erradas, pois afirmam que a desreferenciação significa acessar o valor real. A Wikipedia fornece a definição correta: https://en.wikipedia.org/wiki/Dereference_operator

Ele opera em uma variável de ponteiro e retorna um valor l equivalente ao valor no endereço do ponteiro. Isso é chamado de "desreferenciando" o ponteiro.

Dito isso, podemos desreferenciar o ponteiro sem acessar o valor que ele aponta. Por exemplo:

char *p = NULL;
*p;

Desreferenciamos o ponteiro NULL sem acessar seu valor. Ou poderíamos fazer:

p1 = &(*p);
sz = sizeof(*p);

Novamente, desreferenciando, mas nunca acessando o valor. Esse código NÃO trava: a trava acontece quando você realmente acessa os dados por um ponteiro inválido. No entanto, infelizmente, de acordo com o padrão, desreferenciar um ponteiro inválido é um comportamento indefinido (com algumas exceções), mesmo que você não tente tocar nos dados reais.

Então, resumindo: desreferenciar o ponteiro significa aplicar o operador de desreferência. Esse operador apenas retorna um valor l para seu uso futuro.

stsp
fonte
bem, você desferenciou um ponteiro NULL, o que levaria a uma falha de segmentação.
arjun gaur
além disso, você procurou por 'operador de desreferenciamento' e não 'desreferenciando um ponteiro', o que realmente significa obter o valor / acessar um valor em um local de memória apontado por um ponteiro.
Arjun gaur # /
Você tentou? Eu fiz. O seguinte não falha: `#include <stdlib.h> int main () {char * p = NULL; * p; retornar 0; } `
stsp 3/09/16
1
@stsp Faz porque o código não falha agora, não significa que não irá no futuro ou em algum outro sistema.
1
*p;causa comportamento indefinido. Embora você está certo de que dereferencing não acessa o valor per se , o código *p; faz o acesso ao valor.
MM