O que exatamente é um ponteiro C, se não um endereço de memória?


206

Em uma fonte respeitável sobre C, as seguintes informações são fornecidas após a discussão do &operador:

... É um pouco lamentável que a terminologia [endereço de] permaneça, porque confunde quem não sabe do que se trata o endereço e engana quem o faz: pensar nos ponteiros como se fossem endereços geralmente leva à tristeza. .

Outros materiais que eu li (de fontes igualmente respeitáveis, eu diria) sempre se referiram descaradamente aos ponteiros e ao &operador como endereços de memória. Eu adoraria continuar procurando a atualidade do assunto, mas é meio difícil quando fontes respeitáveis ​​TIPO DE discordam.

Agora estou um pouco confuso - o que exatamente é um ponteiro, se não um endereço de memória?

PS

O autor diz mais tarde: ... Continuarei a usar o termo 'endereço de', porque inventar outro [termo] seria ainda pior.


118
Um ponteiro é uma variável que contém um endereço. Ele também tem seu próprio endereço. Essa é a diferença fundamental entre um ponteiro e uma matriz. Uma matriz é efetivamente um endereço (e por implicação, seu próprio endereço ).
WhozCraig

7
Qual é a sua "fonte respeitável" para a cotação?
Cornstalks

22
A melhor fonte respeitável é o padrão de linguagem, e não os livros semi-derivados e semi-puxados da bunda do autor. Aprendi da maneira mais difícil, cometendo quase todos os erros que pude e lentamente construindo um modelo mental de C um pouco mais próximo do descrito pela norma e, finalmente, substituindo o modelo pelo modelo da norma.
Alexey Frunze #

9
@thang As pessoas pensam em ponteiro = número inteiro porque geralmente é assim (o Linux e Windows x86 "nos ensinam" isso), porque as pessoas adoram generalizar, porque as pessoas não conhecem bem o padrão da linguagem e porque tiveram pouca experiência com mudanças radicalmente diferentes. plataformas. É provável que essas mesmas pessoas suponham que um ponteiro para dados e um ponteiro para uma função possam ser convertidos um para o outro e os dados possam ser executados como código e o código ser acessado como dados. Embora isso possa ser verdade nas arquiteturas von Neuman (com 1 espaço de endereço), mas não necessariamente nas arquiteturas de Harvard (com espaços de código e dados).
Alexey Frunze #

6
Os padrões do @exebook não são para iniciantes (especialmente os completos). Eles não devem fornecer apresentações gentis e multidões de exemplos. Eles definem formalmente algo, para que ele possa ser implementado corretamente pelos profissionais.
Alexey Frunze

Respostas:


148

O padrão C não define o que é um ponteiro internamente e como ele funciona internamente. Isso é intencional para não limitar o número de plataformas, onde C pode ser implementado como uma linguagem compilada ou interpretada.

Um valor de ponteiro pode ser algum tipo de ID ou identificador ou uma combinação de vários IDs (diga Olá para segmentos e compensações x86) e não necessariamente um endereço de memória real. Esse ID pode ser qualquer coisa, mesmo uma sequência de texto de tamanho fixo. Representações sem endereço podem ser especialmente úteis para um intérprete C.


34
Não há muito o que explicar. Toda variável tem seu endereço na memória. Mas você não precisa armazenar seus endereços em ponteiros para eles. Em vez disso, você pode numerar suas variáveis ​​de 1 a qualquer que seja e armazenar esse número no ponteiro. Isso é perfeitamente legal para o padrão de linguagem, desde que a implementação saiba como transformar esses números em endereços e como fazer aritmética de ponteiro com esses números e todas as outras coisas exigidas pelo padrão.
Alexey Frunze

4
Eu gostaria de acrescentar que no x86, um endereço de memória consiste em um seletor de segmentos e um deslocamento, representando assim um ponteiro como segmento: o deslocamento ainda está usando o endereço de memória.
thang

6
@Lundin Não tenho problemas para ignorar a natureza genérica do padrão e o inaplicável quando conheço minha plataforma e meu compilador. A pergunta original é genérica, no entanto, portanto, você não pode ignorar o padrão ao respondê-lo.
Alexey Frunze #

8
@Lundin Você não precisa ser revolucionário ou cientista. Suponha que você queira emular uma máquina de 32 bits em uma máquina física de 16 bits e estenda seus 64 KB de RAM para até 4 GB usando armazenamento em disco e implemente ponteiros de 32 bits como compensações em um arquivo enorme. Esses ponteiros não são endereços de memória reais.
Alexey Frunze #

6
O melhor exemplo que eu já vi disso foi a implementação C para Symbolics Lisp Machines (por volta de 1990). Cada objeto C foi implementado como uma matriz Lisp e os ponteiros foram implementados como um par de uma matriz e um índice. Devido à verificação de limites da matriz do Lisp, você nunca pode estourar de um objeto para outro.
Barmar

62

Não tenho certeza da sua fonte, mas o tipo de linguagem que você está descrevendo é do padrão C:

6.5.3.2 Operadores de endereço e indireto
[...]
3. O operador unário e fornece o endereço de seu operando. [...]

Então ... sim, ponteiros apontam para endereços de memória. Pelo menos é assim que o padrão C sugere que isso significa.

Para dizer um pouco mais claramente, um ponteiro é uma variável que contém o valor de algum endereço . O endereço de um objeto (que pode ser armazenado em um ponteiro) é retornado com o &operador unário .

Posso armazenar o endereço "42 Wallaby Way, Sydney" em uma variável (e essa variável seria uma espécie de "ponteiro", mas como esse não é um endereço de memória, não é algo que chamaríamos de "ponteiro"). Seu computador possui endereços para seus baldes de memória. Os ponteiros armazenam o valor de um endereço (ou seja, um ponteiro armazena o valor "42 Wallaby Way, Sydney", que é um endereço).

Edit: Eu quero expandir sobre o comentário de Alexey Frunze.

O que exatamente é um ponteiro? Vamos dar uma olhada no padrão C:

6.2.5 Tipos
[...]
20. [...]
Um tipo de ponteiro pode ser derivado de um tipo de função ou tipo de objeto, chamado tipo referenciado . Um tipo de ponteiro descreve um objeto cujo valor fornece uma referência a uma entidade do tipo referenciado. Um tipo de ponteiro derivado do tipo T referenciado às vezes é chamado de '' ponteiro para T ''. A construção de um tipo de ponteiro a partir de um tipo referenciado é chamada '' derivação de tipo de ponteiro ''. Um tipo de ponteiro é um tipo de objeto completo.

Essencialmente, os ponteiros armazenam um valor que fornece uma referência a algum objeto ou função. Mais ou menos. Os ponteiros destinam-se a armazenar um valor que forneça uma referência a algum objeto ou função, mas esse nem sempre é o caso:

6.3.2.3 Ponteiros
[...]
5. Um número inteiro pode ser convertido para qualquer tipo de ponteiro. Exceto conforme especificado anteriormente, o resultado é definido pela implementação, pode não estar alinhado corretamente, pode não apontar para uma entidade do tipo referenciado e pode ser uma representação de interceptação.

A citação acima diz que podemos transformar um número inteiro em um ponteiro. Se fizermos isso (ou seja, se inserirmos um valor inteiro em um ponteiro em vez de uma referência específica a um objeto ou função), o ponteiro "pode ​​não apontar para uma entidade do tipo de referência" (ou seja, pode não fornecer um referência a um objeto ou função). Isso pode nos fornecer outra coisa. E este é um lugar onde você pode colocar algum tipo de identificador ou ID em um ponteiro (ou seja, o ponteiro não está apontando para um objeto; está armazenando um valor que representa algo, mas esse valor pode não ser um endereço).

Então, sim, como Alexey Frunze diz, é possível que um ponteiro não esteja armazenando um endereço para um objeto ou função. É possível que um ponteiro esteja armazenando algum tipo de "identificador" ou ID, e você pode fazer isso atribuindo algum valor inteiro arbitrário a um ponteiro. O que esse identificador ou ID representa depende do sistema / ambiente / contexto. Desde que seu sistema / implementação possa entender o valor, você estará em boa forma (mas isso depende do valor específico e do sistema / implementação específico).

Normalmente , um ponteiro armazena um endereço em um objeto ou função. Se não estiver armazenando um endereço real (para um objeto ou função), o resultado será uma implementação definida (o que significa que exatamente o que acontece e o que o ponteiro representa agora depende do seu sistema e implementação, portanto, pode ser um identificador ou ID em um sistema específico, mas usar o mesmo código / valor em outro sistema pode travar seu programa).

Isso acabou sendo mais do que eu pensei que seria ...


3
Em um intérprete C, um ponteiro pode conter um ID / identificador / endereço não-endereço / etc.
Alexey Frunze #

4
@exebook O padrão não é de qualquer modo limitado a compilado C.
Alexey Frunze

7
@Lundin Bravo! Vamos ignorar mais o padrão! Como se ainda não o tivéssemos ignorado o suficiente e não tivéssemos produzido software de buggy e mal portátil por causa disso. Além disso, não se esqueça que a pergunta original é genérica e, como tal, precisa de uma resposta genérica.
Alexey Frunze #

3
Quando outras pessoas estão dizendo que um ponteiro pode ser um identificador ou outra coisa que não seja um endereço, elas não significam apenas que você pode coagir dados em um ponteiro lançando um número inteiro em um ponteiro. Eles significam que o compilador pode estar usando algo diferente de endereços de memória para implementar ponteiros. No processador Alpha com ABI do DEC, um ponteiro de função não era o endereço da função, mas o endereço de um descritor de uma função, e o descritor continha o endereço da função e alguns dados sobre os parâmetros da função. O ponto é que o padrão C é muito flexível.
Eric Postpischil 01/03

5
@Lundin: A afirmação de que os ponteiros são implementados como endereços inteiros em 100% dos sistemas de computadores existentes no mundo real é falsa. Existem computadores com endereçamento de palavras e endereçamento de deslocamento de segmento. Os compiladores ainda existem com suporte para ponteiros próximos e distantes. Existem computadores PDP-11, com o RSX-11 e o Task Builder e suas sobreposições, nos quais um ponteiro deve identificar as informações necessárias para carregar uma função do disco. Um ponteiro não pode ter o endereço de memória de um objeto se o objeto não estiver na memória!
Eric Postpischil 01/03

39

Pointer vs Variable

Nesta foto,

pointer_p é um ponteiro localizado em 0x12345 e está apontando para uma variável variable_v em 0x34567.


16
Isso não apenas não aborda a noção de endereço em vez de ponteiro, mas também perde integralmente o ponto de que um endereço não é apenas um número inteiro.
Gilles 'SO- stop be evil' em

19
-1, isso apenas explica o que é um ponteiro. Essa não era a questão - e você está deixando de lado todas as complexidades sobre as quais se trata.
31413 Alexis

34

Pensar em um ponteiro como um endereço é uma aproximação . Como todas as aproximações, é bom o suficiente para ser útil às vezes, mas também não é exato, o que significa que confiar nela causa problemas.

Um ponteiro é como um endereço, pois indica onde encontrar um objeto. Uma limitação imediata dessa analogia é que nem todos os ponteiros realmente contêm um endereço. NULLé um ponteiro que não é um endereço. O conteúdo de uma variável de ponteiro pode de fato ser de um dos três tipos:

  • o endereço de um objeto, que pode ser desreferenciado (se pcontiver o endereço de x, a expressão terá *po mesmo valor que x);
  • um ponteiro nulo , do qual NULLé um exemplo;
  • conteúdo inválido , que não aponta para um objeto (se pnão tiver um valor válido, *ppoderá fazer qualquer coisa (“comportamento indefinido”), com o travamento do programa é uma possibilidade bastante comum).

Além disso, seria mais preciso dizer que um ponteiro (se válido e não nulo) contém um endereço: um ponteiro indica onde encontrar um objeto, mas há mais informações ligadas a ele.

Em particular, um ponteiro tem um tipo. Na maioria das plataformas, o tipo do ponteiro não tem influência no tempo de execução, mas tem uma influência que vai além do tipo no tempo de compilação. Se pé um ponteiro para int( int *p;), p + 1aponta para um número inteiro que é sizeof(int)bytes depois p(supondo que p + 1ainda seja um ponteiro válido). Se qé um ponteiro para charesse ponto para o mesmo endereço que p( char *q = p;), então q + 1não é o mesmo endereço que p + 1. Se você pensa em ponteiro como endereços, não é muito intuitivo que o “próximo endereço” seja diferente para ponteiros diferentes no mesmo local.

É possível em alguns ambientes ter vários valores de ponteiro com diferentes representações (diferentes padrões de bits na memória) que apontam para o mesmo local na memória. Você pode pensar neles como ponteiros diferentes com o mesmo endereço ou como endereços diferentes para o mesmo local - a metáfora não é clara nesse caso. O ==operador sempre informa se os dois operandos estão apontando para o mesmo local; portanto, nesses ambientes, você pode ter um padrão de bits diferente e p == qainda assim .pq

Existem até ambientes em que os ponteiros carregam outras informações além do endereço, como informações de tipo ou permissão. Você pode facilmente passar por sua vida como programador sem encontrá-los.

Existem ambientes em que diferentes tipos de ponteiros têm representações diferentes. Você pode pensar nisso como diferentes tipos de endereços com diferentes representações. Por exemplo, algumas arquiteturas possuem ponteiros de bytes e ponteiros de palavras ou ponteiros de objeto e ponteiros de função.

Em suma, pensar em ponteiros como endereços não é tão ruim, desde que você tenha em mente que

  • são apenas ponteiros válidos e não nulos que são endereços;
  • você pode ter vários endereços no mesmo local;
  • você não pode fazer aritmética em endereços e não há ordem neles;
  • o ponteiro também carrega informações de tipo.

Indo ao contrário é muito mais problemático. Nem tudo o que parece um endereço pode ser um ponteiro . Em algum lugar no fundo, qualquer ponteiro é representado como um padrão de bits que pode ser lido como um número inteiro, e você pode dizer que esse número inteiro é um endereço. Mas, indo na outra direção, nem todo número inteiro é um ponteiro.

Existem primeiro algumas limitações bem conhecidas; por exemplo, um número inteiro que designa um local fora do espaço de endereço do seu programa não pode ser um ponteiro válido. Um endereço desalinhado não faz um ponteiro válido para um tipo de dados que requer alinhamento; por exemplo, em uma plataforma em que intrequer alinhamento de 4 bytes, 0x7654321 não pode ser um int*valor válido .

No entanto, isso vai muito além disso, porque quando você transforma um ponteiro em um número inteiro, está em um mundo de problemas. Uma grande parte desse problema é que a otimização dos compiladores é muito melhor em microoptimização do que a maioria dos programadores espera, de modo que seu modelo mental de como um programa funciona está profundamente errado. Só porque você tem ponteiros com o mesmo endereço, não significa que eles sejam equivalentes. Por exemplo, considere o seguinte snippet:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Você pode esperar que em uma máquina comum onde sizeof(int)==4e sizeof(short)==2, isso imprima 1 = 1?(little endian) ou 65536 = 1?(big endian). Mas no meu PC Linux de 64 bits com GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

O GCC tem a gentileza de nos alertar sobre o que está acontecendo de errado neste exemplo simples - em exemplos mais complexos, o compilador pode não perceber. Como ppossui um tipo diferente de &x, alterar para quais ppontos não pode afetar para que &xpontos (fora de algumas exceções bem definidas). Portanto, o compilador tem a liberdade de manter o valor de xem um registro e não atualizar esse registro como *palterações. O programa desreferencia dois ponteiros para o mesmo endereço e obtém dois valores diferentes!

A moral deste exemplo é que pensar em um ponteiro (válido não nulo) como um endereço é bom, desde que você permaneça dentro das regras precisas da linguagem C. O outro lado da moeda é que as regras da linguagem C são complexas e difíceis de obter uma sensação intuitiva, a menos que você saiba o que acontece sob o capô. E o que acontece sob o capô é que a ligação entre ponteiros e endereços é um tanto frouxa, tanto para suportar arquiteturas de processador “exóticas” quanto para otimizar compiladores.

Portanto, pense nos ponteiros como endereços como um primeiro passo para o seu entendimento, mas não siga essa intuição muito longe.


5
+1. Outras respostas parecem não entender que um ponteiro vem com informações de tipo. Isso é muito mais importante do que o endereço / ID / qualquer discussão.
undur_gongor

+1 Excelentes pontos sobre informações de tipo. Não tenho certeza se os exemplos do compilador estão corretos ... Parece muito improvável, por exemplo, que *p = 3seja garantido um êxito quando p não tiver sido inicializado.
Larsh

@LarsH Você está certo, obrigado, como eu escrevi isso? Substituí-o por um exemplo que até demonstra o comportamento surpreendente no meu PC.
Gilles 'SO- stop be evil' em

1
hum, NULL é ((nulo *) 0) ..?
Aniket Inge

1
@ gnasher729 O ponteiro nulo é um ponteiro. NULLnão é, mas para o nível de detalhe necessário aqui, essa é uma distração irrelevante. Mesmo para a programação do dia-a-dia, o fato de que NULLpode ser implementado como algo que não diz "ponteiro" não aparece com frequência (passando principalmente NULLpara uma função variada - mas mesmo lá, se você não a estiver transmitindo) , você já está assumindo que todos os tipos de ponteiros têm a mesma representação).
Gilles 'SO- stop being evil'

19

Um ponteiro é uma variável que HOLDS endereço de memória, não o endereço em si. No entanto, você pode desreferenciar um ponteiro - e obter acesso ao local da memória.

Por exemplo:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

É isso aí. É simples assim.

insira a descrição da imagem aqui

Um programa para demonstrar o que estou dizendo e sua saída está aqui:

http://ideone.com/rcSUsb

O programa:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Pode confundir ainda mais. Alice, você pode ver um gato? Não, eu posso ver apenas o sorriso de um gato. Então, dizer que ponteiro é um endereço ou ponteiro é uma variável que contém um endereço ou dizer que ponteiro é o nome de um conceito que se refere à idéia de um endereço, até que ponto os escritores de livros podem chegar a pontos de vista confusos?
exebook 01/03

@ livro para aqueles experientes em ponteiros, é bastante simples. Talvez uma foto ajude?
Aniket Inge

5
Um ponteiro não necessariamente contém um endereço. Em um intérprete C, poderia ser outra coisa, algum tipo de ID / identificador.
Alexey Frunze #

O "rótulo" ou nome da variável é um compilador / montador e não existe no nível da máquina, portanto, acho que não deve aparecer na memória.
Ben

1
@Aniket Uma variável de ponteiro pode conter um valor de ponteiro. Você só precisa armazenar o resultado fopenem uma variável se precisar usá-lo mais de uma vez (o que, por fopenenquanto, é praticamente o tempo todo).
Gilles 'SO- stop be evil' 'em

16

É difícil dizer exatamente o que os autores desses livros significam exatamente. Se um ponteiro contém ou não um endereço depende de como você define um endereço e como define um ponteiro.

A julgar por todas as respostas escritas, algumas pessoas assumem que (1) um endereço deve ser um número inteiro e (2) um ponteiro não precisa ser virtual para não ser dito na especificação. Com essas premissas, os indicadores claramente não contêm necessariamente endereços.

No entanto, vemos que, embora (2) seja provavelmente verdadeiro, (1) provavelmente não precisa ser verdadeiro. E o que fazer com o fato de o & ser chamado de endereço do operador conforme a resposta do @ CornStalks? Isso significa que os autores da especificação pretendem que um ponteiro contenha um endereço?

Então, podemos dizer que o ponteiro contém um endereço, mas um endereço não precisa ser um número inteiro? Talvez.

Eu acho que tudo isso é uma conversa semântica pedante de bobeira. É totalmente inútil na prática. Você consegue pensar em um compilador que gera código de tal maneira que o valor de um ponteiro não seja um endereço? Se sim, o que? Isso foi o que eu pensei...

Acho que o autor do livro (o primeiro trecho que afirma que ponteiros não são necessariamente apenas endereços) provavelmente está se referindo ao fato de que um ponteiro vem com ele as informações de tipo inerentes.

Por exemplo,

 int x;
 int* y = &x;
 char* z = &x;

y e z são ponteiros, mas y + 1 e z + 1 são diferentes. se forem endereços de memória, essas expressões não forneceriam o mesmo valor?

E aqui reside o pensamento sobre os ponteiros, como se fossem endereços, geralmente leva ao sofrimento . Os bugs foram escritos porque as pessoas pensam nos ponteiros como se fossem endereços , e isso geralmente leva ao sofrimento .

O 55555 provavelmente não é um ponteiro, embora possa ser um endereço, mas (int *) 55555 é um ponteiro. 55555 + 1 = 55556, mas (int *) 55555 + 1 é 55559 (diferença de +/- em termos de tamanho de (int)).


1
+1 para apontar a aritmética do ponteiro não é o mesmo que a aritmética nos endereços.
kutschkem

No caso do 8086 de 16 bits, um endereço de memória é descrito por um segmento base + deslocamento, ambos de 16 bits. Existem muitas combinações de segmento base + deslocamento que fornecem o mesmo endereço na memória. Este farponteiro não é apenas "um número inteiro".
vonbrand

@ vonbrand Eu não entendo por que você postou esse comentário. esse problema foi discutido como comentários em outras respostas. quase todas as outras respostas assumem que endereço = inteiro e qualquer coisa que não seja inteiro não é endereço. Simplesmente aponto isso e observe que ele pode ou não estar correto. meu ponto principal na resposta é que isso não é relevante. tudo é apenas pedante, e a questão principal não está sendo abordada nas outras respostas.
Thang

@tang, a idéia "ponteiro == endereço" está errada . Que todo mundo e sua tia favorita continuem dizendo que isso não está certo.
vonbrand

@ vonbrand, e por que você fez esse comentário no meu post? Eu não disse que é certo ou errado. De fato, é certo em certos cenários / suposições, mas nem sempre. Deixe-me resumir novamente o ponto da postagem (pela segunda vez). meu ponto principal na resposta é que isso não é relevante. tudo é apenas pedante, e a questão principal não está sendo abordada nas outras respostas. seria mais apropriado comentar as respostas que afirmam que o ponteiro == endereço ou endereço == número inteiro. veja meus comentários no post de Alexey em relação ao segmento: offset.
thang

15

Bem, um ponteiro é uma abstração que representa um local de memória. Observe que a citação não diz que pensar em ponteiros como se fossem endereços de memória está errado, apenas diz que "geralmente leva ao sofrimento". Em outras palavras, leva a ter expectativas incorretas.

A fonte mais provável de luto é certamente a aritmética dos ponteiros, que na verdade é um dos pontos fortes de C. Se um ponteiro fosse um endereço, você esperaria que a aritmética do ponteiro fosse aritmética do endereço; Mas isso não. Por exemplo, adicionar 10 a um endereço deve fornecer um endereço maior em 10 unidades de endereçamento; mas adicionar 10 a um ponteiro o incrementa em 10 vezes o tamanho do tipo de objeto para o qual aponta (e nem mesmo o tamanho real, mas arredondado para um limite de alinhamento). Com uma int *arquitetura comum com números inteiros de 32 bits, adicionar 10 a ele aumentaria em 40 unidades de endereçamento (bytes). Programadores experientes em C estão cientes disso e convivem com ele, mas seu autor evidentemente não é fã de metáforas desleixadas.

Há a questão adicional de como o conteúdo do ponteiro representa a localização da memória: Como muitas das respostas explicaram, um endereço nem sempre é um int (ou longo). Em algumas arquiteturas, um endereço é um "segmento" mais um deslocamento. Um ponteiro pode até conter apenas o deslocamento no segmento atual (ponteiro "near"), que por si só não é um endereço de memória exclusivo. E o conteúdo do ponteiro pode ter apenas um relacionamento indireto com um endereço de memória, conforme o hardware o entende. Mas o autor da citação citada nem menciona representação, então acho que foi a equivalência conceitual, e não a representação, que eles tinham em mente.


12

Eis como expliquei isso para algumas pessoas confusas no passado: Um ponteiro possui dois atributos que afetam seu comportamento. Ele possui um valor , que é (em ambientes típicos), um endereço de memória e um tipo , que informa o tipo e o tamanho do objeto em que aponta.

Por exemplo, dado:

union {
    int i;
    char c;
} u;

Você pode ter três ponteiros diferentes, todos apontando para o mesmo objeto:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Se você comparar os valores desses ponteiros, todos serão iguais:

v==i && i==c

No entanto, se você incrementar cada ponteiro, verá que o tipo para o qual eles apontam se torna relevante.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

As variáveis ie cterão valores diferentes neste momento, porque i++faz icom que contenha o endereço do próximo número acessível próximo e c++faz ccom que aponte para o próximo caractere endereçável. Normalmente, os números inteiros ocupam mais memória que os caracteres; portanto, ieles acabam com um valor maior do que cdepois que ambos são incrementados.


2
+1 Obrigado. Com indicadores, valor e tipo são tão inseparáveis ​​quanto se pode separar o corpo e a alma da pessoa.
Aki Suihkonen

i == cestá mal formado (você só pode comparar ponteiros com tipos diferentes se houver uma conversão implícita de um para o outro). Além disso, corrigir isso com uma conversão significa que você aplicou uma conversão e é discutível se a conversão altera o valor ou não. (Você pode afirmar que não, mas isso é apenas a mesma coisa que você estava tentando provar com este exemplo).
MM

8

Mark Bessey já disse isso, mas isso precisa ser enfatizado até que seja entendido.

O ponteiro tem mais a ver com uma variável do que um literal 3.

Ponteiro é uma tupla de um valor (de um endereço) e de um tipo (com propriedades adicionais, como somente leitura). O tipo (e os parâmetros adicionais, se houver) pode definir ou restringir ainda mais o contexto; por exemplo. __far ptr, __near ptr: qual é o contexto do endereço: pilha, pilha, endereço linear, deslocamento de algum lugar, memória física ou o quê.

É a propriedade do tipo que torna a aritmética do ponteiro um pouco diferente da aritmética de número inteiro.

Os exemplos de contador de um ponteiro de não ser uma variável são muitos para ignorar

  • fopen retornando um ponteiro FILE. (onde está a variável)

  • ponteiro de pilha ou ponteiro de quadro sendo registros normalmente não endereçáveis

    *(int *)0x1231330 = 13; - converter um valor inteiro arbitrário para um tipo pointer_of_integer e escrever / ler um número inteiro sem nunca introduzir uma variável

Durante a vida útil de um programa C, haverá muitas outras instâncias de ponteiros temporários que não possuem endereços - e, portanto, não são variáveis, mas expressões / valores com um tipo associado ao tempo de compilação.


8

Você está certo e são. Normalmente, um ponteiro é apenas um endereço, para que você possa convertê-lo em número inteiro e fazer qualquer aritmética.

Às vezes, porém, os ponteiros são apenas parte de um endereço. Em algumas arquiteturas, um ponteiro é convertido em um endereço com adição de base ou outro registro da CPU é usado.

Atualmente, porém, na arquitetura PC e ARM com um modelo de memória simples e linguagem C compilada nativamente, não há problema em pensar que um ponteiro é um endereço inteiro para algum lugar na RAM endereçável unidimensional.


PC ... modelo de memória plana? o que são seletores?
thang

Riight. E quando a próxima mudança de arquitetura ocorrer, talvez com código separado e espaços de dados, ou alguém voltar à arquitetura de segmento venerável (que faz muito sentido para a segurança, pode até adicionar alguma chave ao número do segmento + deslocamento para verificar as permissões) adoráveis ​​"ponteiros são apenas números inteiros" desabam.
vonbrand

7

Um ponteiro, como qualquer outra variável em C, é fundamentalmente uma coleção de bits que podem ser representados por um ou mais unsigned charvalores concatenados (como em qualquer outro tipo de referência, sizeof(some_variable)indicará o número de unsigned charvalores). O que torna um ponteiro diferente de outras variáveis ​​é que um compilador C interpretará os bits em um ponteiro como identificando, de alguma forma, um local onde uma variável pode ser armazenada. Em C, diferentemente de outros idiomas, é possível solicitar espaço para várias variáveis ​​e converter um ponteiro para qualquer valor desse conjunto em um ponteiro para qualquer outra variável desse conjunto.

Muitos compiladores implementam ponteiros usando seus bits armazenam endereços reais da máquina, mas essa não é a única implementação possível. Uma implementação pode manter uma matriz - não acessível ao código do usuário - listando o endereço de hardware e o tamanho alocado de todos os objetos de memória (conjuntos de variáveis) que um programa estava usando, e cada ponteiro conter um índice em uma matriz. com um deslocamento desse índice. Esse design permitiria que um sistema não apenas restringisse o código a operar apenas na memória que possuía, mas também garantisse que um ponteiro para um item de memória não pudesse ser acidentalmente convertido em um ponteiro para outro item de memória (em um sistema que usa hardware endereços, se fooe barsão matrizes de 10 itens armazenados consecutivamente na memória, um ponteiro para o item "décimo primeiro" defoo vez disso, apontar para o primeiro item debar, mas em um sistema em que cada "ponteiro" é um ID de objeto e um deslocamento, o sistema pode interceptar se o código tentar indexar um ponteiro fooalém do intervalo alocado). Também seria possível para esse sistema eliminar problemas de fragmentação de memória, pois os endereços físicos associados a qualquer ponteiro poderiam ser movidos.

Observe que, embora os ponteiros sejam abstratos, eles não são abstratos o suficiente para permitir que um compilador C totalmente compatível com os padrões implemente um coletor de lixo. O compilador C especifica que todas as variáveis, incluindo ponteiros, são representadas como uma sequência de unsigned charvalores. Dada qualquer variável, pode-se decompô-la em uma sequência de números e depois converter essa sequência de números novamente em uma variável do tipo original. Consequentemente, seria possível um programacallocalgum armazenamento (recebendo um ponteiro), armazene algo lá, decomponha o ponteiro em uma série de bytes, exiba-os na tela e apague todas as referências a eles. Se o programa aceitar alguns números do teclado, reconstituí-los em um ponteiro e tentar ler os dados desse ponteiro, e se o usuário digitar os mesmos números que o programa havia exibido anteriormente, o programa seria obrigado a enviar os dados que foram armazenados nacallocmemória. Como não há maneira concebível de o computador saber se o usuário fez uma cópia dos números exibidos, não seria possível que o computador soubesse se a memória mencionada anteriormente poderia ser acessada no futuro.


Em uma grande sobrecarga, talvez você possa detectar qualquer uso do valor do ponteiro que possa "vazar" seu valor numérico e fixar a alocação para que o coletor de lixo não o colete ou realoque (a menos que freeseja chamado explicitamente, é claro). Se a implementação resultante seria tão útil é outra questão, já que sua capacidade de coletar pode ser muito limitada, mas você poderia pelo menos chamá-lo de coletor de lixo :-) A atribuição do ponteiro e a aritmética não "vazariam" o valor, mas qualquer acesso a um char*de origem desconhecida teria que ser verificado.
Steve Jessop

@SteveJessop: Eu acho que esse projeto seria pior do que inútil, pois seria impossível para o código saber quais ponteiros precisavam ser liberados. Coletores de lixo que assumem que qualquer coisa que se parece com um ponteiro é algo que pode ser excessivamente conservador, mas geralmente coisas que parecem - mas não são - ponteiros têm a possibilidade de mudar, evitando assim vazamentos de memória "permanentes". Ter qualquer ação que pareça decompor um ponteiro em bytes congelando permanentemente o ponteiro é uma receita garantida para vazamentos de memória.
supercat

Eu acho que falharia de qualquer maneira por razões de desempenho - se você quiser que seu código seja executado lentamente, porque todo acesso é verificado, não o escreva em C ;-) Tenho mais esperanças quanto à engenhosidade dos programadores C do que você, já que, apesar de inconveniente, provavelmente não é implausível evitar fixar alocações desnecessariamente. De qualquer forma, o C ++ define "ponteiros derivados com segurança" precisamente para lidar com esse problema; portanto, sabemos o que fazer se quisermos aumentar a abstração dos ponteiros C para o nível em que eles oferecem suporte à coleta de lixo razoavelmente eficaz.
Steve Jessop

@SteveJessop: Para que um sistema de GC seja útil, ele deve poder liberar com segurança a memória na qual freenão foi chamado ou impedir que qualquer referência a um objeto liberado se torne referência a um objeto ativo [mesmo ao usar recursos que requerem gerenciamento explícito da vida útil, o GC ainda pode desempenhar com utilidade a última função]; um sistema de GC que às vezes considera falsamente objetos como tendo referências ativas a eles pode ser útil se a probabilidade de N objetos serem desnecessariamente fixados simultaneamente se aproximar de zero à medida que N aumenta . A menos que alguém esteja disposto a sinalizar um erro do compilador ... #
308

... para código que é C ++ válido, mas para o qual o compilador seria incapaz de provar que um ponteiro nunca pode ser convertido em um formato irreconhecível, não vejo como alguém poderia evitar o risco de um programa que de fato nunca usa ponteiros como números inteiros podem ser falsamente considerados como fazendo isso.
Supercat

6

Um ponteiro é um tipo de variável que está disponível nativamente em C / C ++ e contém um endereço de memória. Como qualquer outra variável, ela possui um endereço próprio e ocupa memória (a quantidade é específica da plataforma).

Um problema que você verá como resultado da confusão é tentar alterar o referente dentro de uma função simplesmente passando o ponteiro por valor. Isso fará uma cópia do ponteiro no escopo da função e qualquer alteração no local em que esse novo ponteiro "aponta" não mudará o referente do ponteiro no escopo que chamou a função. Para modificar o ponteiro real dentro de uma função, normalmente se passava um ponteiro para um ponteiro.


1
Geralmente, é um identificador / ID. Geralmente, é um endereço simples.
Alexey Frunze #

Ajustei minha resposta para ser um pouco mais PC para a definição de Handle na wikipedia. Eu gosto de me referir a ponteiros como uma instância específica de um identificador, pois um identificador pode ser simplesmente uma referência a um ponteiro.
Matthew Sanders

6

SUMÁRIO BREVE (que também colocarei no topo):

(0) Pensar em ponteiros como endereços geralmente é uma boa ferramenta de aprendizado e, muitas vezes, é a implementação real de ponteiros para tipos de dados comuns.

(1) Mas em muitos, talvez na maioria, os ponteiros de compiladores para funções não são endereços, mas são maiores que um endereço (normalmente 2x, às vezes mais), ou são realmente ponteiros para uma estrutura na memória que contém os endereços de funções e coisas como uma piscina constante.

(2) Ponteiros para membros de dados e ponteiros para métodos geralmente são ainda mais estranhos.

(3) Código x86 legado com problemas de ponteiro FAR e NEAR

(4) Vários exemplos, principalmente o IBM AS / 400, com "indicadores de gordura" seguros.

Tenho certeza que você pode encontrar mais.

DETALHE:

UMMPPHHH !!!!! Até agora, muitas das respostas são respostas típicas de "programador weenie" - mas não de compilador ou hardware. Desde que eu finjo ser um weenie de hardware, e muitas vezes trabalho com weenies de compilador, deixe-me colocar meus dois centavos:

Em muitos, provavelmente na maioria dos compiladores C, um ponteiro para dados do tipo Té, de fato, o endereço de T.

Bem.

Mas, mesmo em muitos desses compiladores, certos ponteiros NÃO são endereços. Você pode dizer isso olhando para sizeof(ThePointer).

Por exemplo, ponteiros para funções às vezes são bem maiores que endereços comuns. Ou, eles podem envolver um nível de indireção. Este artigofornece uma descrição, envolvendo o processador Intel Itanium, mas já vi outras. Normalmente, para chamar uma função, você deve conhecer não apenas o endereço do código da função, mas também o endereço do pool constante da função - uma região da memória na qual as constantes são carregadas com uma única instrução de carregamento, em vez de o compilador ter que gerar uma constante de 64 bits em várias instruções Load Immediate, Shift e OR. Portanto, em vez de um único endereço de 64 bits, você precisa de 2 endereços de 64 bits. Algumas ABIs (Application Binary Interfaces) movem isso em 128 bits, enquanto outras usam um nível de indireção, com o ponteiro de função sendo o endereço de um descritor de função que contém os 2 endereços reais mencionados. Qual é melhor? Depende do seu ponto de vista: desempenho, tamanho do código, e alguns problemas de compatibilidade - geralmente o código pressupõe que um ponteiro possa ser convertido em um comprimento longo ou muito longo, mas também pode assumir que o comprimento longo é exatamente de 64 bits. Esse código pode não estar em conformidade com os padrões, mas, no entanto, os clientes podem querer que ele funcione.

Muitos de nós têm lembranças dolorosas da antiga arquitetura segmentada Intel x86, com NEAR POINTERs e FAR POINTERS. Felizmente, eles já estão quase extintos, então apenas um resumo rápido: no modo real de 16 bits, o endereço linear real era

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Enquanto no modo protegido, pode ser

LinearAddress = SegmentRegister[SegNum].base + offset

com o endereço resultante sendo verificado em relação a um limite definido no segmento. Alguns programas não usaram declarações de ponteiro C / C ++ FAR e NEAR realmente padrão, mas muitos acabaram de dizer *T--- mas havia opções de compilador e vinculador, por exemplo, ponteiros de código podem estar próximos a ponteiros, apenas um deslocamento de 32 bits em relação ao que estiver em o registro CS (Segmento de código), enquanto os ponteiros de dados podem ser ponteiros FAR, especificando um número de segmento de 16 bits e um deslocamento de 32 bits para um valor de 48 bits. Agora, essas duas quantidades certamente estão relacionadas ao endereço, mas como não são do mesmo tamanho, qual é o endereço? Além disso, os segmentos também possuíam permissões - somente leitura, leitura e gravação, executável - além de itens relacionados ao endereço real.

Um exemplo mais interessante, IMHO, é (ou talvez fosse) a família IBM AS / 400. Este computador foi um dos primeiros a implementar um sistema operacional em C ++. Os ponteiros dessa máquina geralmente eram 2X o tamanho real do endereço - por exemplo, esta apresentaçãodiz, ponteiros de 128 bits, mas os endereços reais eram de 48 a 64 bits e, novamente, algumas informações extras, o que é chamado de recurso, que forneciam permissões como leitura, gravação e um limite para impedir o estouro do buffer. Sim: você pode fazer isso de forma compatível com C / C ++ - e, se isso fosse onipresente, o PLA chinês e a máfia eslava não invadiriam tantos sistemas de computadores ocidentais. Porém, historicamente, a maioria dos programas em C / C ++ negligenciou a segurança do desempenho. O mais interessante é que a família AS400 permitiu que o sistema operacional criasse indicadores seguros, que poderiam ser atribuídos a códigos não privilegiados, mas que o código não privilegiado não poderia forjar ou adulterar. Novamente, a segurança e, embora seja compatível com os padrões, o código C / C ++ muito desleixado e não compatível com os padrões não funcionará em um sistema tão seguro. Mais uma vez, existem padrões oficiais,

Agora, vou sair da minha caixa de sabão de segurança e mencionar algumas outras maneiras pelas quais ponteiros (de vários tipos) geralmente não são realmente endereços: ponteiros para membros de dados, ponteiros para métodos de funções de membro e suas versões estáticas são maiores que um endereço comum. Como este post diz:

Existem muitas maneiras de resolver isso [problemas relacionados à herança única versus múltipla e herança virtual]. Veja como o compilador do Visual Studio decide manipulá-lo: Um ponteiro para uma função de membro de uma classe herdada de multiplicação é realmente uma estrutura ". E eles continuam dizendo" Converter um ponteiro de função pode alterar seu tamanho! ".

Como você provavelmente pode adivinhar pela minha identificação em (in) segurança, estive envolvido em projetos de hardware / software C / C ++ em que um ponteiro era tratado mais como uma capacidade do que como um endereço bruto.

Eu poderia continuar, mas espero que você entenda.

BREVE RESUMO (que também colocarei no topo):

(0) pensar em ponteiros como endereços geralmente é uma boa ferramenta de aprendizado e geralmente é a implementação real de ponteiros para tipos de dados comuns.

(1) Mas em muitos, talvez na maioria, os ponteiros de compiladores para funções não são endereços, mas são maiores que um endereço (normalmente 2X, às vezes mais) ou são realmente ponteiros para uma estrutura na memória que contém os endereços de funções e coisas como uma piscina constante.

(2) Ponteiros para membros de dados e ponteiros para métodos geralmente são ainda mais estranhos.

(3) Código x86 legado com problemas de ponteiro FAR e NEAR

(4) Vários exemplos, principalmente o IBM AS / 400, com "indicadores de gordura" seguros.

Tenho certeza que você pode encontrar mais.


No modo real de 16 bits LinearAddress = SegmentRegister.Selector * 16 + Offset(note vezes 16, não mude 16). No modo protegido LinearAddress = SegmentRegister.base + offset(sem multiplicação de qualquer tipo; a base do segmento é armazenada no GDT / LDT e armazenada em cache no registro do segmento como está ).
Alexey Frunze #

Você também está correto sobre a base do segmento. Eu tinha me lembrado errado. É o limite do segmento que é opcionalmente múltiplo por 4K. A base do segmento só precisa ser decodificada pelo hardware quando carrega um descritor de segmento da memória em um registrador de segmento.
Krazy Glew

4

Um ponteiro é apenas outra variável que é usada para armazenar o endereço de um local de memória (geralmente o endereço de memória de outra variável).


Então, o pontapé é realmente um endereço de memória? Você discorda do autor? Apenas tentando entender.
precisa saber é o seguinte

A função principal do ponteiro é apontar para alguma coisa. Como exatamente isso é alcançado e se existe um endereço real ou não, não está definido. Um ponteiro pode ser apenas um ID / identificador, não um endereço real.
Alexey Frunze #

4

Você pode ver desta maneira. Um ponteiro é um valor que representa um endereço no espaço de memória endereçável.


2
Um ponteiro não precisa necessariamente conter o endereço de memória real. Veja minha resposta e os comentários abaixo.
Alexey Frunze #

o que .... o ponteiro para a primeira variável na pilha não imprime 0. ele imprime a parte superior (ou inferior) do quadro da pilha, dependendo de como é implementada.
thang

@thang Para a primeira variável, as partes superior e inferior são iguais. E qual é o endereço da parte superior ou inferior neste caso da pilha?
Valentin Radu

@ ValentinRadu, por que você não tenta .. obviamente você não tentou.
thang

2
@thang Você está certo, eu fiz algumas suposições muito ruins, para minha defesa são 5 da manhã aqui.
Valentin Radu

3

Um ponteiro é apenas outra variável que pode conter endereço de memória geralmente de outra variável. Um ponteiro sendo uma variável também possui um endereço de memória.


1
Não é necessariamente um endereço. Btw, você leu respostas e comentários existentes antes de postar sua resposta?
Alexey Frunze #

3

O ponteiro CA é muito semelhante a um endereço de memória, mas com os detalhes dependentes da máquina abstraídos, além de alguns recursos não encontrados no conjunto de instruções de nível inferior.

Por exemplo, um ponteiro C é digitado de forma relativamente rica. Se você incrementa um ponteiro através de uma matriz de estruturas, ele salta de uma estrutura para outra.

Os ponteiros estão sujeitos às regras de conversão e fornecem verificação do tipo de tempo de compilação.

Há um valor especial "ponteiro nulo" que é portátil no nível do código-fonte, mas cuja representação pode ser diferente. Se você atribuir uma constante inteira cujo valor é zero a um ponteiro, esse ponteiro assumirá o valor nulo do ponteiro. O mesmo se você inicializar um ponteiro dessa maneira.

Um ponteiro pode ser usado como uma variável booleana: testa true se for diferente de nulo e false se for nulo.

Em uma linguagem de máquina, se o ponteiro nulo for um endereço engraçado como 0xFFFFFFFF, talvez seja necessário que você tenha testes explícitos para esse valor. C esconde isso de você. Mesmo se o ponteiro nulo for 0xFFFFFFFF, você pode testá-lo usando if (ptr != 0) { /* not null! */}.

O uso de ponteiros que subvertem o sistema de tipos leva a um comportamento indefinido, enquanto código semelhante na linguagem de máquina pode estar bem definido. Os montadores montam as instruções que você escreveu, mas os compiladores C serão otimizados com base na suposição de que você não fez nada de errado. Se um float *pponteiro aponta para uma long nvariável e *p = 0.0é executado, o compilador não é necessário para lidar com isso. Um uso subseqüente de nnão será necessário ler o padrão de bits do valor flutuante, mas talvez seja um acesso otimizado, baseado na suposição de "aliasing estrito" que nnão foi tocada! Ou seja, a suposição de que o programa é bem-comportado e, portanto p, não deve ser apontado n.

Em C, ponteiros para código e ponteiros para dados são diferentes, mas em muitas arquiteturas, os endereços são os mesmos. Compiladores C podem ser desenvolvidos com ponteiros "gordos", mesmo que a arquitetura de destino não. Ponteiros de gordura significa que os ponteiros não são apenas endereços de máquinas, mas contêm outras informações, como informações sobre o tamanho do objeto apontado, para verificação de limites. Programas escritos de forma portável serão portados facilmente para esses compiladores.

Como você pode ver, existem muitas diferenças semânticas entre endereços de máquinas e ponteiros C.


Os ponteiros NULL não funcionam como você pensa em todas as plataformas - veja minha resposta ao CiscoIPPhone acima. NULL == 0 é uma suposição que só é válida em plataformas baseadas em x86. A Convenção diz que as novas plataformas devem corresponder ao x86, no entanto, particularmente no mundo incorporado, não é assim. Edit: Além disso, C não faz nada para abstrair o valor de um ponteiro do hardware - "ptr! = 0" não funcionará como um teste NULL em uma plataforma onde NULL! = 0.
DX-MON

1
DX-MON, isso é completamente errado para o padrão C. NULL é destinado a 0 e eles podem ser usados ​​de forma intercambiável em declarações. Se a representação do ponteiro NULL no hardware é de 0 bits, é irrelevante a forma como ela é representada no código-fonte.
Mark Bessey

@ DX-MON Acho que você não está trabalhando com os fatos corretos. Em C, uma expressão constante integral serve como uma constante de ponteiro nulo, independentemente de o ponteiro nulo ser o endereço nulo. Se você conhece um compilador C que ptr != 0não é um teste nulo, revele sua identidade (mas antes de fazer isso, envie um relatório de bug ao fornecedor).
Kaz

Entendo o que você está dizendo, mas seus comentários sobre ponteiros nulos são incoerentes porque você está confundindo ponteiros e endereços de memória - exatamente o que a citação citada na pergunta recomenda evitar! A instrução correta: C define o ponteiro nulo como zero, independentemente de um endereço de memória no deslocamento zero ser legal ou não.
Alexis

1
@alexis Capítulo e verso, por favor. C não define o ponteiro nulo como zero. C define zero (ou qualquer expressão constante integral cujo valor é zero) como uma sintaxe para denotar uma constante de ponteiro nulo. faqs.org/faqs/C-faq/faq (seção 5).
Kaz

3

Antes de entender os ponteiros, precisamos entender os objetos. Objetos são entidades que existem e possuem um especificador de local chamado endereço. Um ponteiro é apenas uma variável como qualquer outra variável Ccom um tipo chamado pointercujo conteúdo é interpretado como o endereço de um objeto que suporta a operação a seguir.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Um ponteiro é classificado com base no tipo de objeto ao qual está se referindo atualmente. A única parte da informação que importa é o tamanho do objeto.

Qualquer objeto suporta uma operação &(endereço de), que recupera o especificador de localização (endereço) do objeto como um tipo de objeto de ponteiro. Isso deve diminuir a confusão em torno da nomenclatura, pois isso faria sentido chamar &como uma operação de um objeto em vez de um ponteiro cujo tipo resultante é um ponteiro do tipo de objeto.

Nota Ao longo desta explicação, deixei de fora o conceito de memória.


Gosto da sua explicação sobre a realidade abstrata de um ponteiro geral em um sistema geral. Mas talvez discutir memória seja útil. De fato, falando por mim, eu sei que seria ...! Acho que discutir a conexão pode ser muito útil para entender o cenário geral. +1 de qualquer maneira :)
d0rmLife

@ d0rmLife: Você tem explicações suficientes nas outras respostas que abrangem a imagem maior. Eu só queria dar uma explicação abstrata matemática como outra visão. Também IMHO, isso criaria menos confusão ao chamar &como 'Endereço de`, pois isso é mais vinculado a um Objeto do que ao ponteiro em si'
Abhijit

Sem ofensa, mas vou decidir por mim mesmo o que é explicação suficiente. Um livro não é suficiente para explicar completamente as estruturas de dados e a alocação de memória. ;) .... de qualquer forma, sua resposta ainda é útil , mesmo que não seja nova.
precisa saber é o seguinte

Não faz sentido manipular ponteiros sem o conceito de memória . Se o objeto existir sem memória, ele deverá estar em um local onde não haja endereço - por exemplo, nos registros. Ser capaz de usar '&' pressupõe memória.
Aki Suihkonen

3

Um endereço é usado para identificar uma parte do armazenamento de tamanho fixo, geralmente para cada bytes, como um número inteiro. Isso é chamado precisamente como endereço de bytes , que também é usado pela ISO C. Pode haver outros métodos para construir um endereço, por exemplo, para cada bit. No entanto, apenas o endereço de bytes é usado com tanta frequência que geralmente omitimos "bytes".

Tecnicamente, um endereço nunca é um valor em C, porque a definição do termo "valor" em (ISO) C é:

significado preciso do conteúdo de um objeto quando interpretado como tendo um tipo específico

(Enfatizado por mim.) No entanto, não existe esse "tipo de endereço" em C.

Ponteiro não é o mesmo. Ponteiro é um tipo de tipo na linguagem C. Existem vários tipos de ponteiros distintos. Eles não necessariamente obedecem ao conjunto idêntico de regras da linguagem, por exemplo, o efeito de ++em um valor do tipo int*vs. char*.

Um valor em C pode ser do tipo ponteiro. Isso é chamado de valor do ponteiro . Para ser claro, um valor de ponteiro não é um ponteiro na linguagem C. Mas estamos acostumados a misturá-los, porque em C não é provável que seja ambíguo: se chamarmos uma expressão pcomo "ponteiro", é apenas um valor de ponteiro, mas não um tipo, pois um tipo nomeado em C não é expresso por uma expressão , mas por um nome de tipo ou um nome de tipo de typedef .

Algumas outras coisas são sutis. Como usuário C, primeiramente, deve-se saber o que objectsignifica:

região de armazenamento de dados no ambiente de execução, cujo conteúdo pode representar valores

Um objeto é uma entidade para representar valores, que são de um tipo específico. Um ponteiro é um tipo de objeto . Portanto, se declararmos int* p;, psignifica "um objeto do tipo ponteiro" ou um "objeto ponteiro".

Observe que não "variável" definida normativamente pelo padrão (na verdade, ele nunca está sendo usado como substantivo pela ISO C no texto normativo). No entanto, informalmente, chamamos um objeto de variável, como alguma outra linguagem. (Mas ainda não é exatamente assim, por exemplo, em C ++, uma variável pode ser do tipo de referência normativamente, o que não é um objeto.) As frases "objeto ponteiro" ou "variável ponteiro" às vezes são tratadas como "valor ponteiro" como acima, com um provável ligeira diferença. (Mais um conjunto de exemplos é "matriz").

Como o ponteiro é um tipo e o endereço é efetivamente "não digitado" em C, um valor do ponteiro aproximadamente "contém" um endereço. E uma expressão do tipo ponteiro pode gerar um endereço, por exemplo

ISO C11 6.5.2.3

3 O &operador unário fornece o endereço do seu operando.

Observe que esta redação é introduzida por WG14 / N1256, ou seja, ISO C99: TC3. Em C99 há

3 O &operador unário retorna o endereço do seu operando.

Isso reflete a opinião do comitê: um endereço não é um valor de ponteiro retornado pelo &operador unário .

Apesar da redação acima, ainda há alguma confusão até nos padrões.

ISO C11 6.6

9 Uma constante de endereço é um ponteiro nulo, um ponteiro para um valor l que designa um objeto com duração de armazenamento estático ou um ponteiro para um designador de função

ISO C ++ 11 5.19

3 ... Uma expressão constante de endereço é uma expressão constante de núcleo de valor-valor do tipo ponteiro que é avaliada para o endereço de um objeto com duração de armazenamento estático, para o endereço de uma função ou para um valor de ponteiro nulo ou para uma expressão constante de núcleo de valor-valor. de tipo std::nullptr_t. ...

(O rascunho recente do padrão C ++ usa outra redação, portanto não há esse problema.)

Na verdade, "endereçar constante" em C e "endereçar expressão constante" em C ++ são expressão constante de tipos de ponteiro (ou pelo menos tipos "semelhantes a ponteiros" desde C ++ 11).

E o &operador unário interno é chamado como "endereço de" em C e C ++; similarmente, std::addressofé introduzido no C ++ 11.

Esses nomes podem trazer conceitos errôneos. A expressão resultou é do tipo apontador, de modo que seria interpretada como: o resultado contém / produz um endereço, em vez do que é um endereço.


2

Diz "porque confunde aqueles que não sabem do que se trata os endereços" - também é verdade: se você aprender do que se trata os endereços, não ficará confuso. Teoricamente, ponteiro é uma variável que aponta para outra, praticamente mantém um endereço, que é o endereço da variável para a qual aponta. Não sei por que esconder esse fato, não é uma ciência de foguetes. Se você entender os ponteiros, estará um passo mais perto para entender como os computadores funcionam. Continue!


2

Venha para pensar sobre isso, acho que é uma questão de semântica. Eu não acho que o autor esteja certo, pois o padrão C se refere a um ponteiro como mantendo um endereço para o objeto referenciado, como outros já mencionaram aqui. No entanto, endereço! = Endereço de memória. Um endereço pode ser realmente qualquer coisa conforme o padrão C, embora eventualmente leve a um endereço de memória, o ponteiro em si pode ser um ID, um deslocamento + seletor (x86), realmente qualquer coisa, desde que possa descrever (após o mapeamento) qualquer memória endereço no espaço endereçável.


Um ponteiro contém um endereço (ou não, se for nulo). Mas isso está muito longe de ser um endereço: por exemplo, dois ponteiros para o mesmo endereço, mas com um tipo diferente, não são equivalentes em muitas situações.
Gilles 'SO- stop be evil' em

@ Gilles Se você vir "being", como em int i=5-> i é 5, o ponteiro é o endereço yes. Além disso, null também tem um endereço. Geralmente, um endereço de gravação inválido (mas não necessariamente, consulte o modo x86-real), mas um endereço, no entanto. Na verdade, existem apenas 2 requisitos para nulo: é garantido comparar desigual a um ponteiro com um objeto real e quaisquer dois ponteiros nulos serão iguais.
Valentin Radu

Pelo contrário, é garantido que um ponteiro nulo não seja igual ao endereço de qualquer objeto. Desreferenciar um ponteiro nulo é um comportamento indefinido. Um grande problema em dizer que "o ponteiro é o endereço" é que eles funcionam de maneira diferente. Se pé um ponteiro, p+1nem sempre o endereço é incrementado por 1.
Gilles 'SO- stop be evil'

Leia novamente o comentário, por favor it's guaranteed to compare unequal to a pointer to an actual object. Quanto à aritmética do ponteiro, não vejo o ponto, o valor do ponteiro ainda é um endereço, mesmo que a operação "+" não adicione necessariamente um byte a ele.
Valentin Radu

1

Uma outra maneira pela qual um ponteiro C ou C ++ difere de um endereço de memória simples devido aos diferentes tipos de ponteiros que eu não vi nas outras respostas (apesar do tamanho total, posso tê-lo ignorado). Mas é provavelmente o mais importante, porque mesmo programadores C / C ++ experientes podem tropeçar nele:

O compilador pode assumir que ponteiros de tipos incompatíveis não apontam para o mesmo endereço, mesmo que claramente, o que pode dar um comportamento que não seria possível com um simples ponteiro == modelo de endereço. Considere o seguinte código (supondo sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Observe que há uma exceção para char*, portanto, char*é possível manipular valores usando (embora não seja muito portátil).


0

Resumo rápido: o endereço CA é um valor, normalmente representado como um endereço de memória no nível da máquina, com um tipo específico.

A palavra não qualificada "ponteiro" é ambígua. C possui objetos de ponteiro (variáveis), tipos de ponteiro , expressões de ponteiro e valores de ponteiro .

É muito comum usar a palavra "ponteiro" para significar "objeto ponteiro", e isso pode levar a alguma confusão - e é por isso que tento usar "ponteiro" como adjetivo e não como substantivo.

O padrão C, pelo menos em alguns casos, usa a palavra "ponteiro" para significar "valor do ponteiro". Por exemplo, a descrição do malloc diz que "retorna um ponteiro nulo ou um ponteiro para o espaço alocado".

Então, o que é um endereço em C? É um valor de ponteiro, ou seja, um valor de algum tipo de ponteiro específico. (Exceto que um valor de ponteiro nulo não é necessariamente chamado de "endereço", pois não é o endereço de nada).

A descrição do padrão do &operador unário diz que "produz o endereço de seu operando". Fora do padrão C, a palavra "endereço" é comumente usada para se referir a um endereço de memória (físico ou virtual), geralmente com uma palavra em tamanho (qualquer que seja uma "palavra" em um determinado sistema).

O "endereço" CA normalmente é implementado como um endereço da máquina - assim como um intvalor C é normalmente implementado como uma palavra da máquina. Mas um endereço C (valor do ponteiro) é mais do que apenas um endereço da máquina. É um valor normalmente representado como um endereço de máquina e é um valor com algum tipo específico .


0

Um valor de ponteiro é um endereço. Uma variável de ponteiro é um objeto que pode armazenar um endereço. Isso é verdade porque é isso que o padrão define como um ponteiro. É importante contar aos novatos em C, porque os novatos em C geralmente não sabem a diferença entre um ponteiro e o que ele aponta (ou seja, eles não sabem a diferença entre um envelope e um prédio). A noção de um endereço (todo objeto tem um endereço e é isso que um ponteiro armazena) é importante porque classifica isso.

No entanto, o padrão fala em um nível específico de abstração. As pessoas com quem o autor fala sobre quem "sabe de que endereço se trata", mas quem é novo no C, devem necessariamente ter aprendido sobre endereços em um nível diferente de abstração - talvez programando a linguagem assembly. Não há garantia de que a implementação C use a mesma representação para endereços que os opcodes da CPU usam (referido como "o endereço de armazenamento" nesta passagem), sobre o qual essas pessoas já conhecem.

Ele continua falando sobre "manipulação de endereço perfeitamente razoável". No que diz respeito ao padrão C, basicamente não existe "manipulação de endereço perfeitamente razoável". A adição é definida nos ponteiros e é basicamente isso. Claro, você pode converter um ponteiro em número inteiro, fazer algumas operações aritméticas ou bit a bit e depois convertê-lo novamente. Não é garantido que funcione de acordo com o padrão; portanto, antes de escrever esse código, é melhor saber como sua implementação C específica representa ponteiros e executa essa conversão. Provavelmente, ele usa a representação de endereço que você espera, mas não é sua culpa, porque você não leu o manual. Isso não é confusão, é procedimento de programação incorreto ;-)

Em resumo, C usa um conceito mais abstrato de endereço do que o autor.

O conceito de endereço do autor, é claro, também não é a palavra de nível mais baixo sobre o assunto. Com os mapas de memória virtual e o endereçamento físico da RAM em vários chips, o número que você diz à CPU é "o endereço de armazenamento" que você deseja acessar basicamente não tem nada a ver com onde os dados que você deseja estão realmente localizados no hardware. São todas as camadas de indireção e representação, mas o autor escolheu uma para privilegiar. Se você fizer isso ao falar sobre C, escolha o nível C para privilegiar !

Pessoalmente, não acho que as observações do autor sejam úteis, exceto no contexto da introdução de C em programadores de montagem. Certamente não é útil para quem vem de idiomas de nível superior dizer que os valores dos ponteiros não são endereços. Seria muito melhor reconhecer a complexidade do que dizer que a CPU tem o monopólio de dizer o que é um endereço e, portanto, que os valores do ponteiro C "não são" endereços. São endereços, mas podem ser escritos em um idioma diferente dos endereços que ele quer dizer. Distinguir as duas coisas no contexto de C como "endereço" e "endereço da loja" seria adequado, eu acho.


0

Basta dizer que os ponteiros são realmente deslocados como parte do mecanismo de segmentação que se traduz em Endereço Linear após a segmentação e depois em Endereço Físico após a paginação. Os endereços físicos são realmente endereçados a partir do seu ram.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.