Eu sei que os ponteiros contêm endereços. Eu sei que os tipos de ponteiros são "geralmente" conhecidos com base no "tipo" de dados para os quais eles apontam. Porém, os ponteiros ainda são variáveis e os endereços que eles mantêm devem ter um "tipo" de dados. Segundo as minhas informações, os endereços estão no formato hexadecimal. Mas ainda não sei que tipo de dados é esse hexadecimal. (Observe que eu sei o que é um hexadecimal, mas quando você diz 10CBA20
, por exemplo, essa sequência de caracteres é? Inteiros? O quê? Quando eu quero acessar o endereço e manipulá-lo ... em si, preciso conhecer seu tipo. é por isso que estou perguntando.)
30
Respostas:
O tipo de uma variável de ponteiro é .. ponteiro.
As operações que você está formalmente autorizado a fazer em C são compará-lo (com outros ponteiros, ou o valor NULL / zero especial), adicionar ou subtrair números inteiros ou convertê-lo em outros ponteiros.
Depois de aceitar um comportamento indefinido , você pode ver qual é realmente o valor. Geralmente, é uma palavra de máquina, o mesmo tipo de número inteiro e geralmente pode ser convertida sem perdas para e de um tipo inteiro. (Muitos códigos do Windows fazem isso ocultando os ponteiros nos typedefs DWORD ou HANDLE).
Existem algumas arquiteturas em que os ponteiros não são simples porque a memória não é plana. DOS / 8086 'próximo' e 'distante'; Espaços diferentes de memória e código do PIC.
fonte
p1-p2
. O resultado é um valor integral assinado. Em particular, #&(array[i])-&(array[j]) == i-j
intptr_t
euintptr_t
com garantia de ser "grande o suficiente" para valores de ponteiro.p
especificador ao printf faz com que a obtenção de uma representação legível por humanos de um ponteiro nulo seja um comportamento definido, se dependente da implementação, em c.Você está complicando demais as coisas.
Endereços são apenas números inteiros, ponto final. Idealmente, eles são o número da célula de memória referenciada (na prática, isso fica mais complicado por causa de segmentos, memória virtual etc.).
A sintaxe hexadecimal é uma ficção completa que existe apenas para a conveniência dos programadores. 0x1A e 26 são exatamente o mesmo número do mesmo tipo e nem o que o computador usa - internamente, o computador sempre usa 00011010 (uma série de sinais binários).
Se um compilador permite ou não tratar ponteiros como números depende da definição de linguagem - as linguagens de "programação de sistemas" são tradicionalmente mais transparentes sobre como as coisas funcionam sob o capô, enquanto as linguagens de "alto nível" costumam tentar ocultar o bare metal do programador - mas isso não muda nada sobre o fato de os ponteiros serem números e, geralmente, o tipo de número mais comum (aquele com tantos bits quanto a arquitetura do processador).
fonte
Um ponteiro é apenas isso - um ponteiro. Não é outra coisa. Não tente pensar que é outra coisa.
Em linguagens como C, C ++ e Objective-C, os ponteiros de dados têm quatro tipos de valores possíveis:
Também existem ponteiros de função, que identificam uma função ou são ponteiros de função nulos ou têm um valor indeterminado.
Outros ponteiros são "ponteiro para membro" em C ++. Estes são mais definitivamente não endereços de memória! Em vez disso, eles identificam um membro de qualquer instância de uma classe. No Objective-C, você tem seletores, que são algo como "ponteiro para um método de instância com um nome de método e nomes de argumento". Como um ponteiro de membro, ele identifica todos os métodos de todas as classes, desde que pareçam iguais.
Você pode investigar como um compilador específico implementa ponteiros, mas essa é uma pergunta totalmente diferente.
fonte
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
A variávelpmi
não seria muito útil se não contivesse um endereço de memória, a saber, como a última linha do código estabelece, o endereço do membronum
da instânciaa
da classeA
. Você pode converter isso em umint
ponteiro comum (embora o compilador provavelmente lhe dê um aviso) e desreferenciá-lo com êxito (provando que é um açúcar sintático para qualquer outro ponteiro).Um ponteiro é um padrão de bits que endereça (identificando exclusivamente para fins de leitura ou gravação) uma palavra de armazenamento na RAM. Por razões históricas e convencionais, a unidade de atualização é de oito bits, conhecida em inglês como "byte" ou em francês, mais logicamente, como um octeto. Isso é onipresente, mas não inerente; outros tamanhos existem.
Se bem me lembro, havia um computador que usava uma palavra de 29 bits; não é apenas uma potência de dois, é também primordial. Eu pensei que era SILLIAC, mas o artigo pertinente da Wikipedia não suporta isso. O CAN BUS usa endereços de 29 bits, mas, por convenção, os endereços de rede não são referidos como ponteiros, mesmo quando são funcionalmente idênticos.
As pessoas continuam afirmando que os ponteiros são números inteiros. Isso não é intrínseco nem essencial, mas se interpretarmos os padrões de bits como números inteiros, a qualidade útil da ordinalidade emerge, permitindo a implementação muito direta (e, portanto, eficiente em hardware pequeno) de construções como "string" e "array". A noção de memória contígua depende da adjacência ordinal, e o posicionamento relativo é possível; comparação de números inteiros e operações aritméticas podem ser aplicadas significativamente. Por esse motivo, quase sempre existe uma forte correlação entre o tamanho da palavra para endereçamento de armazenamento e a ALU (o que faz matemática inteira).
Às vezes os dois não correspondem. Nos primeiros PCs, o barramento de endereços tinha 24 bits de largura.
fonte
char*
por exemplo, para fins de cópia / comparação de memória esizeof char==1
conforme definido pelo padrão C), não palavra (a menos que o tamanho da palavra da CPU seja igual ao tamanho do byte).Basicamente, todo computador moderno é uma máquina que empurra pouco. Geralmente ele empurra bits em grupos de dados, chamados bytes, palavras, dwords ou qwords.
Um byte consiste em 8 bits, uma palavra 2 bytes (ou 16 bits), uma palavra dword 2 (ou 32 bits) e uma palavra qword 2 dwords (ou 64 bits). Essa não é a única maneira de organizar bits. A manipulação de 128 e 256 bits também ocorre, geralmente nas instruções do SIMD.
As instruções de montagem operam nos registradores e os endereços de memória geralmente operam em uma das formas acima.
A ALU (unidades lógicas aritméticas) opera em pacotes de bits como se representassem números inteiros (geralmente no formato Complemento de Dois) e FPUs como se estivessem em valores de ponto flutuante (geralmente no estilo IEEE 754
float
edouble
). Outras partes agirão como se fossem dados agrupados de algum formato, caracteres, entradas da tabela, instruções da CPU ou endereços.Em um computador típico de 64 bits, pacotes de 8 bytes (64 bits) são endereços. Exibimos esses endereços convencionalmente como em um formato hexadecimal (como
0xabcd1234cdef5678
), mas essa é apenas uma maneira fácil para os humanos lerem os padrões de bits. Cada byte (8 bits) é escrito como dois caracteres hexadecimais (equivalentemente, cada caractere hexadecimal - 0 a F - representa 4 bits).O que realmente está acontecendo (para alguns níveis) é que existem bits, geralmente armazenados em um registro ou armazenados em locais adjacentes em um banco de memória, e estamos apenas tentando descrevê-los para outro ser humano.
Seguir um ponteiro consiste em pedir ao controlador de memória que nos forneça alguns dados nesse local. Você normalmente solicita ao controlador de memória um certo número de bytes em um determinado local (bem, implicitamente, um intervalo de locais, geralmente contíguo), e ele é entregue através de vários mecanismos nos quais não vou entrar.
O código geralmente especifica um destino para os dados a serem buscados - um registro, outro endereço de memória etc. - e geralmente é uma má idéia carregar dados de ponto flutuante em um registro esperando um número inteiro ou vice-versa.
O tipo de dados em C / C ++ é algo que o compilador controla e altera o código gerado. Geralmente, não há nada intrínseco nos dados que os torne realmente de qualquer tipo. Apenas uma coleção de bits (organizados em bytes) que são manipulados como um número inteiro (ou como um flutuador ou como um endereço) pelo código.
Há exceções para isto. Existem arquiteturas em que certas coisas são de tipos diferentes de bits. O exemplo mais comum são as páginas de execução protegidas - enquanto as instruções informando à CPU o que fazem são bits, no tempo de execução, as páginas (de memória) contendo o código a ser executado são marcadas especialmente, não podem ser modificadas e você não pode executar páginas que não estão marcadas. como páginas de execução.
Também existem dados somente leitura (às vezes armazenados na ROM que não podem ser gravados fisicamente!), Problemas de alinhamento (alguns processadores não podem carregar
double
s da memória, a menos que estejam alinhados de maneiras particulares, ou instruções SIMD que exijam certo alinhamento) e inúmeras outras peculiaridades da arquitetura.Até o nível de detalhe acima é uma mentira. Os computadores não estão "realmente" pressionando bits, estão pressionando tensões e correntes. Essas tensões e correntes às vezes não fazem o que "deveriam" fazer no nível de abstração dos bits. Os chips são projetados para detectar a maioria desses erros e corrigi-los sem que a abstração de nível superior precise estar ciente disso.
Até isso é mentira.
Cada nível de abstração oculta o nível abaixo e permite que você pense em resolver problemas sem ter que se lembrar dos diagramas de Feynman para imprimir
"Hello World"
.Portanto, com um nível suficiente de honestidade, os computadores pressionam os bits, e esses bits recebem significado pela maneira como são usados.
fonte
As pessoas fizeram uma grande diferença se os ponteiros são inteiros ou não. Na verdade, existem respostas para essas perguntas. No entanto, você terá que dar um passo na terra das especificações, o que não é para os fracos de coração. Vamos dar uma olhada na especificação C, ISO / IEC 9899: TC2
6.3.2.3 Ponteiros
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.
Qualquer tipo de ponteiro pode ser convertido em um tipo inteiro. Exceto conforme especificado anteriormente, o resultado é definido pela implementação. Se o resultado não puder ser representado no tipo inteiro, o comportamento será indefinido. O resultado não precisa estar no intervalo de valores de nenhum tipo inteiro.
Agora, você precisará conhecer alguns termos comuns de especificações. "Implementação definida" significa que todo compilador pode defini-lo de maneira diferente. De fato, um compilador pode até defini-lo de maneiras diferentes, dependendo das suas configurações. Comportamento indefinido significa que o compilador pode fazer absolutamente qualquer coisa, desde dar um erro no tempo de compilação a comportamentos inexplicáveis, até funcionar perfeitamente.
A partir disso, podemos ver que o formulário de armazenamento subjacente não está especificado, exceto que pode haver uma conversão para um tipo inteiro. Agora, verdade seja dita, praticamente todo compilador sob o sol representa ponteiros sob o capô como endereços inteiros (com alguns casos especiais em que pode ser representado como 2 inteiros em vez de apenas 1), mas a especificação permite absolutamente qualquer coisa, como representar endereços como uma sequência de 10 caracteres!
Se sairmos rapidamente do C e examinarmos as especificações do C ++, obteremos um pouco mais de clareza
reinterpret_cast
, mas essa é uma linguagem diferente; portanto, o valor para você pode variar:ISO / IEC N337: rascunho de especificação C ++ 11 (só tenho o rascunho em mãos)
5.2.10 Reinterpretar elenco
Um ponteiro pode ser explicitamente convertido em qualquer tipo integral grande o suficiente para retê-lo. A função de mapeamento é definida pela implementação. [Nota: Ele não é surpreendente para quem conhece a estrutura de endereçamento da máquina subjacente. —End note] Um valor do tipo std :: nullptr_t pode ser convertido em um tipo integral; a conversão tem o mesmo significado e validade que uma conversão de (vazio *) 0 para o tipo integral. [Nota: Um reinterpret_cast não pode ser usado para converter um valor de qualquer tipo no tipo std :: nullptr_t. - end note]
Um valor do tipo integral ou do tipo de enumeração pode ser explicitamente convertido em um ponteiro. Um ponteiro convertido em um número inteiro de tamanho suficiente (se houver algum na implementação) e retornado ao mesmo tipo de ponteiro terá seu valor original; mapeamentos entre ponteiros e números inteiros são definidos pela implementação. [Nota: Exceto conforme descrito em 3.7.4.3, o resultado dessa conversão não será um valor de ponteiro derivado com segurança. - end note]
Como você pode ver aqui, com mais alguns anos, o C ++ descobriu que era seguro assumir que existia um mapeamento para números inteiros; portanto, não se fala mais em comportamento indefinido (embora exista uma contradição interessante entre as partes 4 e 5 com o fraseado "se houver algum na implementação")
Agora, o que você deve tirar disso?
A melhor aposta: converter para um (caractere *). As especificações C e C ++ estão cheias de regras que especificam o empacotamento de matrizes e estruturas, e ambas sempre permitem a conversão de qualquer ponteiro para um caractere *. char é sempre 1 byte (não garantido em C, mas, pelo C ++ 11, ele se tornou uma parte obrigatória da linguagem, portanto, é relativamente seguro assumir que é de 1 byte em todos os lugares). Isso permite que você faça alguma aritmética de ponteiro no nível de byte a byte sem precisar realmente conhecer as representações específicas da implementação dos ponteiros.
fonte
char *
? Estou pensando em uma máquina hipotética com espaços de endereço separados para código e dados.char
é sempre 1 byte em C. Citando o padrão C: "O operador sizeof produz o tamanho (em bytes) de seu operando" e "Quando sizeof é aplicado a um operando que possui o tipo char, char não assinado ou char assinado, (ou uma versão qualificada), o resultado é 1. " Talvez você esteja pensando que um byte tem 8 bits. Isso não é necessariamente o caso. Para estar em conformidade com o padrão, um byte deve conter pelo menos 8 bits.Na maioria das arquiteturas, o tipo de ponteiro deixa de existir depois de traduzido em código de máquina (exceto talvez "ponteiros gordos"). Portanto, um ponteiro para um
int
seria indistinguível de um ponteiro para umdouble
, pelo menos por si só. *[*] Embora você ainda possa fazer suposições com base nos tipos de operações que você aplica a ele.
fonte
Uma coisa importante a entender sobre C e C ++ é que tipos são realmente. Tudo o que eles realmente fazem é indicar ao compilador como interpretar um conjunto de bits / bytes. Vamos começar com o seguinte código:
Dependendo da arquitetura, um número inteiro geralmente recebe 32 bits de espaço para armazenar esse valor. Isso significa que o espaço na memória em que var está armazenado será semelhante a "11111111 11111111 11111010 11000111" ou em hexadecimal "0xFFFFFAC7". É isso aí. Isso é tudo o que é armazenado nesse local. Todos os tipos fazem é dizer ao compilador como interpretar essas informações. Ponteiros não são diferentes. Se eu fizer algo assim:
Em seguida, o compilador obterá a localização de var e, em seguida, armazene esse endereço da mesma maneira que o primeiro trecho de código salva o valor -1337. Não há diferença em como eles são armazenados, apenas em como eles são usados. Nem importa que eu tenha feito var_ptr um ponteiro para um int. Se você quisesse, você poderia fazer.
Isso copiará o valor hexadecimal acima de var (0xFFFFFAC7) no local que armazena o valor de var2. Se usarmos var2, descobriríamos que o valor seria 4294965959. Os bytes em var2 são iguais a var, mas o valor numérico é diferente. O compilador as interpretou de maneira diferente, porque dissemos que esses bits representam um longo tempo sem sinal. Você também pode fazer o mesmo com o valor do ponteiro.
Você acabaria interpretando o valor que representa o endereço de var como um int não assinado neste exemplo.
Espero que isso esclareça as coisas para você e ofereça uma melhor compreensão de como o C funciona. Observe que você NÃO DEVE fazer nenhuma das coisas loucas que eu fiz nas duas linhas abaixo no código de produção real. Isso foi apenas para demonstração.
fonte
Inteiro.
O espaço de endereço em um computador é numerado seqüencialmente, iniciando em 0 e incrementado em 1. Portanto, um ponteiro retém um número inteiro que corresponde a um endereço no espaço de endereço.
fonte
Tipos combinam.
Em particular, certos tipos se combinam, quase como se tivessem sido parametrizados com espaços reservados. Os tipos de matriz e ponteiro são assim; eles têm um espaço reservado, que é o tipo do elemento da matriz ou a coisa apontada, respectivamente. Os tipos de função também são assim; eles podem ter vários espaços reservados para os parâmetros e um espaço reservado para o tipo de retorno.
Uma variável declarada para conter um ponteiro para char possui o tipo "ponteiro para char". Uma variável declarada para manter um ponteiro para ponteiro para int possui o tipo "ponteiro para ponteiro para int".
Um (valor do) tipo "ponteiro para ponteiro para int" pode ser alterado para "ponteiro para int" por uma operação de desreferência. Portanto, a noção de tipo não é apenas palavras, mas uma construção matematicamente significativa, ditando o que podemos fazer com valores do tipo (como desreferência, ou passar como parâmetro ou atribuir a variável; também determina o tamanho (contagem de bytes) de operações de indexação, aritmética e incremento / decremento).
PS Se você quiser se aprofundar nos tipos, tente este blog: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/
fonte