Enviado em 30/01/2023 - 10:13h 
		ApprenticeX escreveu:
Eu entendi o que o SamL explicou, mas não entendi esse tipo de referência!
Qdo declaro um ponteiro de 
char , eu estou apontando o ponteiro para o endereço de memória onde está 
VOL  ou seja VOL[0]
char *Ptr = "VOL";   
Cuidado com esse “
VOL[0] ”...  Não se aplica um índice ao conteúdo de uma 
string , mas sim ao endereço do primeiro elemento.  Se você tivesse dito “
"VOL"[0] ” (que terá o mesmo valor que 
'V' ), aí, sim, estaria correto.
Então porque não é a mesma coisa usando 
int ? Eu imaginava que eu estaria apontando o ponteiro para o endereço de memória onde está 2
int *Ptr = 2;  Mas segundo o que 
SamL  explica não é assim, pois entendi que 
SamL  diz que estou apontando o Ponteiro para o endereço de memória 2 e não para o endereço de memória onde está o valor 2. 
Lembra que eu disse em mensagens passadas que, no C, um texto entre aspas indica uma constante literal de 
string , cujo tipo de dados é 
array  de 
const char  com tantos elementos quanto houver caracteres entre as aspas mais um (para acomodar o byte terminador da 
string )?
Pois é... Se não lembrava ou não viu, lembrou ou viu agora.  E esse é um ponto importante, pois quando você faz algo como
const char *ptr="uma string qualquer";  o que o compilador vai fazer é:
    1) alocar na área de dados constantes (no mundo UNIX, tipicamente a seção chamada 
.rodata , de 
Read-Only DATA , dados apenas de leitura) espaço suficiente para acomodar vinte caracteres (os dezenove do texto entre aspas, mais um 
byte  nulo ao final);
    2) criar no local adequado (dependendo de se é uma variável global ou estática ou se é uma variável local) espaço para uma variável (no caso, chamada 
ptr ) do tipo 
ponteiro para caracteres constantes  (
const char * ), que é espaço suficiente para acomodar um endereço de memória (tipicamente 8 
bytes  (64 bits) num sistema de 64 bits, ou 4 
bytes  (32 bits) num sistema de 32 bits);
    3) copiar o endereço inicial da região alocada em (1) (o qual corresponde ao primeiro elemento do 
array  indicado na declaração) para 
dentro  do espaço alocado em (2).
Ou seja: na declaração acima, existe uma definição de 
array  com associação desse 
array  a um ponteiro, algo que é perfeitamente válido de fazer, uma vez que todo 
array  em C decai automaticamente para um ponteiro com tipo de dado correspondente ao dos elementos do 
array  e com o valor do endereço do primeiro elemento.
O análogo disso usando inteiros, portanto, tem de ter um 
array  de inteiros no lado direito do sinal de atribuição, não um valor escalar inteiro.
Tal análogo não era possível no C90 (também conhecido como ANSI C ou C89), mas é possível no C11 (padrão de 2011).
int *pi=(int []){1, 2, 3};  // Define um array com três elementos (calculados a partir da quantidade de itens na lista composta de literais), 
Note que nos exemplos acima não existe nenhuma mágica.  Essa sintaxe semelhante à de conversão de tipos (
type casting ) aplicada à compostos de constantes literais entre chaves nada mais faz do que criar 
arrays  anônimos e copiar os endereços dos primeiros elementos de cada um desses 
arrays  para os respectivos ponteiros.  Em termos de consumo de memória, isso seria completamente idêntico a criar um 
array  explicitamente, e depois criar uma variável ponteiro à qual seria atribuído o 
array . Isso pode ser demonstrado com uma comparação lado a lado do código gerado pelo compilador (GCC 11.3) para os dois seguintes programas.
// Arquivo a.c 
// Arquivo b.c 
$ gcc -O0 -S -c a.c gcc -O0 -S -c b.c paste <(expand -t 8 a.s) <(expand -t 8 b.s) | expand -t 70 | cat -n   # O que nos interessa está entre as linhas 3 e 17, mas todas as linhas são equivalentes.Mesma seção de dados (“.data”) Mesmo alinhamento (disposição em endereço que seja múltiplo de 8 bytes) Mesmo tipo, muda só o nome (em b.c, o compilador gerou um nome interno para o array anônimo) Mesmo tamanho (12 bytes: 3 elementos de 4 bytes) Mesma posição Mesmos...    ... três...       ... elementos Mesma variável ponteiro (ptr ) Mesma seção de dados relocáveis Mesmo alinhamento (disposição em endereço que seja múltiplo de 8 bytes) Mesmo tipo (e mesmo nome explícito) Mesmo tamanho (8 bytes, porque meu sistema é de 64 bits, logo ponteiros têm 64 bits) Mesma posição Mesmo valor atribuído (posição do começo do array disposto entre as linhas 3 e 10)  
A única referência que vi que aponta para o endereço de memória é quando se declara 
NULL , apontando para um valor Nulo, entendo que Nulo seria 0.
char *Ptr = NULL;  Essa estranheza é porque já li tanta coisa sobre ponteiros e ou não tive atenção a isso, ou ninguém explica isso em livros ou textos na internet! 
Pois é...  Tecnicamente falando, 
NULL  não é exatamente igual a zero, porque o tipo dele não é um tipo aritmético comum.  O valor de 
NULL  em C é 
((void *)0) .  Pelo fato de ser um 
void * , não é possível fazer aritmética de ponteiro com esse valor.
Mas esse tipo de coisa é explicada, sim. O mais provável é que você não tenha encontrado os lugares certos, ou que talvez ainda não tenha adquirido conhecimento suficiente para tirar algumas conclusões corretas sobre aspectos que ainda não tinha visto, mas a partir de informações que você já possui.
Eu nunca iria saber que um ponteiro int  não vai apontar para o valor atribuído diretamente! Visto que um ponteiro char  não tem esse comportamento! 
Acho que já expliquei acima, mas reiterando de uma outra forma — digamos — mais “bruta” (e com um ponto a mais, para incluir algo que foi explicado pelo SamL na resposta dele):
    • “
const char *nome="Paulo"; ” e “
int *ptr=2; ” não são análogos, trocando apenas o tipo de dado apontado, porque 
"Paulo"  é um 
array  de caracteres constantes (
const char ) com seis elementos, e 
2  é um constante literal inteira; como não existe uma regra definida sobre o armazenamento em memória de constantes literais, não existe uma maneira de o compilador reconhecer a validade da segunda declaração.
    • A primeira declaração acima é valida porque o 
array  "Paulo"  decai automaticamente para ponteiro para o seu primeiro elemento. Por analogia de construção, um ponteiro para inteiros pode receber como valor inicial no momento de sua declaração um 
array  de inteiros que decaia automaticamente para ponteiro para seu primeiro elemento.
    • A forma de obter 
arrays  sem ter de criar uma variável específica é usando a notação de literais compostos (§6.5.2.5 do padrão da linguagem C de 2011), que permite criar 
arrays  anônimos.
    • O análogo à declaração “
const char *nome="Paulo"; ” tendo um ponteiro para inteiros, portanto, seria algo como “
int *ptr_int=(int []){80, 97, 117, 108, 111, 0}; ”.  Aliás, um sinônimo da primeira declaração usando a sintaxe de literais compostos poderia ser “
const char *nome=(const char []){'P', 'a', 'u', 'l', 'o', '\0'}; ”.
    • O análogo da declaração errônea “
int *ptr_int=2; ” seria algo como “
char *pc='a'; ”, também errôneo.
    • A forma de apontar para endereços absolutos na memória, convertendo um valor inteiro diretamente para ponteiro (e.g. “char *p=(char *)5;” ou “int *pi=(int *)10;”) é válida, mas seu uso pode ser limitado, porque não necessariamente qualquer endereço possível de se obter com essa sintaxe vai estar disponível para uso pelo programa.
        • Nos nossos PCs de 64 bits com alguma versão de Linux, UNIX, Windows ou MacOS, provavelmente nossos programinhas vão executar com algum nível de memória virtual, podendo produzir ponteiros com valores de endereços completamente fora dos limites de memória física instalada na máquina. Além disso, em sucessivas execuções do programa, os endereços dos mesmos objetos podem ser diferentes, conforme ilustra o programa abaixo e as saídas de três execuções sucessivas do código compilado (note os valores absurdos dos ponteiros — eu GARANTO que meu micro não possui 128TiB de RAM (nem de HD) para suportar tais endereços, se fossem absolutos — e como os endereços são diferentes em cada execução).
#include <stdio.h> &i: 0x55969aae6018 (@94105328574488, @87642.416893027722835540771484375000GiB) &i: 0x564c0770c018 (@94884542332952, @88368.116256736218929290771484375000GiB) &i: 0x5592edc11018 (@94089542438936, @87627.714908622205257415771484375000GiB) 
        • Mesmo que você esteja programando mais próximo do acesso direto ao 
hardware  (por exemplo, fazendo programação de dispositivos embarcados, ou num PC antigo rodando MS-DOS, ou mesmo um MSX ou Apple II da vida), nem sempre qualquer endereço possível vai estar disponível para acesso (por exemplo: a máquina tem menos RAM do que o processador é capaz de endereçar, ou o endereço que você gerou para tentar fazer um acesso de escrita reside numa área de ROM, em vez de residir em RAM), ou mesmo um determinado endereço de memória pode estar mapeando um dispositivo ou 
soft swicth , cujo acesso direto pode causar efeitos indesejados se feito sem o devido cuidado.
O que também soou curioso é que se posso apontar um ponteiro diretamente para um local na memória, significa que eu poderia imprimir a memória! Um assunto que me interessa estudar no futuro! 
Não exatamente vai funcionar assim, conforme mostra o último programa de exemplo mostrado acima. Nos nossos PCs existe um sistema operacional se interpondo entre a memória física e os programas que nós, “reles mortais”, executamos.  Para examinar o conteúdo da memória do seu PC, você teria de criar um executável que não usasse um sistema operacionai (como costumava fazer, por exemplo, o Memtest86+, que era usado justamente para testar a memória física, mas que era carregado no momento do 
boot  em lugar dos nossos SOs do dia-a-dia), ou você teria de solicitar ao SO que fizesse o acesso para você, o que implica ter credenciais certas e usar APIs específicas de cada SO.
No Linux, creio que o jeito mais básico de se fazer algo assim é através de mapeamento em memória de seções do arquivo virtual 
/dev/mem .  Esta postagem no Stack Overflow pode lhe interessar: 
https://stackoverflow.com/questions/12040303/how-to-access-physical-addresses-from-user-space-in-lin... .
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)