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.
fonte
Respostas:
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.
fonte
Não tenho certeza da sua fonte, mas o tipo de linguagem que você está descrevendo é do padrão C:
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:
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:
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 ...
fonte
Nesta foto,
pointer_p é um ponteiro localizado em 0x12345 e está apontando para uma variável variable_v em 0x34567.
fonte
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:p
contiver o endereço dex
, a expressão terá*p
o mesmo valor quex
);NULL
é um exemplo;p
não tiver um valor válido,*p
poderá 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 paraint
(int *p;
),p + 1
aponta para um número inteiro que ésizeof(int)
bytes depoisp
(supondo quep + 1
ainda seja um ponteiro válido). Seq
é um ponteiro parachar
esse ponto para o mesmo endereço quep
(char *q = p;
), entãoq + 1
não é o mesmo endereço quep + 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 ep == q
ainda assim .p
q
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
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
int
requer alinhamento de 4 bytes, 0x7654321 não pode ser umint*
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:
Você pode esperar que em uma máquina comum onde
sizeof(int)==4
esizeof(short)==2
, isso imprima1 = 1?
(little endian) ou65536 = 1?
(big endian). Mas no meu PC Linux de 64 bits com GCC 4.4: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
p
possui um tipo diferente de&x
, alterar para quaisp
pontos não pode afetar para que&x
pontos (fora de algumas exceções bem definidas). Portanto, o compilador tem a liberdade de manter o valor dex
em um registro e não atualizar esse registro como*p
alteraçõ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.
fonte
*p = 3
seja garantido um êxito quando p não tiver sido inicializado.NULL
nã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 queNULL
pode ser implementado como algo que não diz "ponteiro" não aparece com frequência (passando principalmenteNULL
para 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).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:
É isso aí. É simples assim.
Um programa para demonstrar o que estou dizendo e sua saída está aqui:
http://ideone.com/rcSUsb
O programa:
fonte
fopen
em uma variável se precisar usá-lo mais de uma vez (o que, porfopen
enquanto, é praticamente o tempo todo).É 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,
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)).
fonte
far
ponteiro não é apenas "um número inteiro".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.
fonte
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:
Você pode ter três ponteiros diferentes, todos apontando para o mesmo objeto:
Se você comparar os valores desses ponteiros, todos serão iguais:
No entanto, se você incrementar cada ponteiro, verá que o tipo para o qual eles apontam se torna relevante.
As variáveis
i
ec
terão valores diferentes neste momento, porquei++
fazi
com que contenha o endereço do próximo número acessível próximo ec++
fazc
com que aponte para o próximo caractere endereçável. Normalmente, os números inteiros ocupam mais memória que os caracteres; portanto,i
eles acabam com um valor maior do quec
depois que ambos são incrementados.fonte
i == c
está 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).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ávelDurante 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.
fonte
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.
fonte
Um ponteiro, como qualquer outra variável em C, é fundamentalmente uma coleção de bits que podem ser representados por um ou mais
unsigned char
valores concatenados (como em qualquer outro tipo de referência,sizeof(some_variable)
indicará o número deunsigned char
valores). 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
foo
ebar
sã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 ponteirofoo
alé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 char
valores. 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 programacalloc
algum 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 nacalloc
memó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.fonte
free
seja 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 umchar*
de origem desconhecida teria que ser verificado.free
nã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 ... #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.
fonte
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 deT
.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
Enquanto no modo protegido, pode ser
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:
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.
fonte
LinearAddress = SegmentRegister.Selector * 16 + Offset
(note vezes 16, não mude 16). No modo protegidoLinearAddress = 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á ).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).
fonte
Você pode ver desta maneira. Um ponteiro é um valor que representa um endereço no espaço de memória endereçável.
fonte
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.
fonte
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 *p
ponteiro aponta para umalong n
variável e*p = 0.0
é executado, o compilador não é necessário para lidar com isso. Um uso subseqüente den
nã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" quen
não foi tocada! Ou seja, a suposição de que o programa é bem-comportado e, portantop
, não deve ser apontadon
.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.
fonte
ptr != 0
não é um teste nulo, revele sua identidade (mas antes de fazer isso, envie um relatório de bug ao fornecedor).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
C
com um tipo chamadopointer
cujo conteúdo é interpretado como o endereço de um objeto que suporta a operação a seguir.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.
fonte
&
como 'Endereço de`, pois isso é mais vinculado a um Objeto do que ao ponteiro em si'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 é:
(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 tipoint*
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
p
como "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
object
significa: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;
,p
significa "um objeto do tipo ponteiro" ou um "objeto ponteiro".Observe que não há "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
Observe que esta redação é introduzida por WG14 / N1256, ou seja, ISO C99: TC3. Em C99 há
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
ISO C ++ 11 5.19
(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.
fonte
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!
fonte
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.
fonte
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.p
é um ponteiro,p+1
nem sempre o endereço é incrementado por 1.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.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)
):Observe que há uma exceção para
char*
, portanto,char*
é possível manipular valores usando (embora não seja muito portátil).fonte
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
int
valor 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 .fonte
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.
fonte
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.
fonte