Agora, nesse artigo, vamos entrar mais a fundo no estudo
da memória e ver, de fato, onde as variáveis estão sendo declaradas e um importante relação entre ponteiros e variáveis que ocupam vários bytes.
- Baixe este conteúdo: Apostila C Progressivo
Quando cada variável ocupa em memória: A função sizeof()
Como dissemos, o C vê sua memória RAM como um vetor
enorme de bytes, que vão desde o número 0 até o tamanho dela (geralmente alguns
Gb).
Sempre que declaramos uma variável em C, estamos
guardando, selecionando ou alocando um espaço de bytes desses, e dependendo do
tipo de variável, o tanto de memória reservada varia.
Por exemplo, embora as variáveis do tipo float e double sejam usadas para representar números em sua forma decimal,
as variáveis do tipo double têm, como
o próprio nome sugere, o dobro de precisão. Ou seja, podemos colocar muito mais
casas decimais em variáveis desse tipo. E para que isso aconteça, é óbvio que
vamos precisar de um espaço maior em memória.
Podemos descobrir quantos bytes certa variável ocupa
através da função sizeof().
Essa função recebe uma variável como argumento, ou as palavras
reservadas que representam as variáveis: char, int, float etc.
Como você poderá ver ao longo de nossa apostila de C, a função sizeof() é MUITO importante e será MUITO usada. Ela é tão importante que foi definido um novo 'tipo' de variável para sua saída.
Sempre que usamos a função sizeof(), ela retorna variáveis do tipo: size_t
Lembre-se bem desse tipo. Vamos usar bastante na seção de strings e de alocação dinâmica de memória.
Como você poderá ver ao longo de nossa apostila de C, a função sizeof() é MUITO importante e será MUITO usada. Ela é tão importante que foi definido um novo 'tipo' de variável para sua saída.
Sempre que usamos a função sizeof(), ela retorna variáveis do tipo: size_t
Lembre-se bem desse tipo. Vamos usar bastante na seção de strings e de alocação dinâmica de memória.
Vamos ver um exemplo que mostra o uso da sizeof.
Exemplo de código: Como saber quantos bytes cada variável
ocupa em memória
Faça um programa em C que mostra quantos bytes ocupam cada
uma das variáveis: char, int, float e double.
Existem duas maneiras de fazer isso, a primeira é
simplesmente colocando as palavras reservadas dentro da função sizeof(). A segunda maneira é declarando
variáveis e colando ela dentro da função sizeof(),
como faremos no próximo exemplo.
#include <stdio.h> // Curso C Progressivo: www.cprogessivo.net // O melhor curso de C! Online e gratuito ! // Artigos, apostilas, tutoriais e vídeo-aulas sobre // a linguagem de programação C !int main(void){ printf("Char: %d bytes\n", sizeof(char)); printf("Int: %d bytes\n", sizeof(int)); printf("Float: %d bytes\n", sizeof(float)); printf("Double: %d bytes\n", sizeof(double));
return 0;
}
Exemplo: Mostrar o endereço e número de bytes que cada
variável ocupa
Agora, além de mostrar quantos bytes cada variável ocupa,
mostre o endereço dela.
Para isso, declare 4 variáveis: uma char, um int, um
float e um double.
#include <stdio.h> // Curso C Progressivo: www.cprogessivo.net // O melhor curso de C! Online e gratuito ! // Artigos, apostilas, tutoriais e vídeo-aulas sobre // a linguagem de programação C !int main(void){ char caractere; int inteiro; float Float; double Double; printf("Caractere: %d bytes \t em %d\n", sizeof(caractere), &caractere); printf("Inteiro: %d bytes \t em %d\n", sizeof(inteiro), &inteiro); printf("Float: %d bytes \t em %d\n", sizeof(Float), &Float); printf("Double: %d bytes \t em %d\n", sizeof(Double), &Double);
return 0;
}
Endereço de um bloco de variáveis
Agora que você já viu o tamanho e localização que cada
variável ocupa, vamos ensinar um fato importante, que será muito usado em nosso
estudo de strings e na compreensão da
passagem de parâmetro por referência, em funções.
Quando declaramos uma variável ou vetor de variáveis, os
blocos de memória são vizinhos, são separados em fila, numa ordem.
Você viu no exemplo passado que, tirando o tipo char, as outras variáveis ocupam mais de
uma posição em memória (lembrando que 1 byte é uma posição, ou bloco de
memória).
O tipo double, por
exemplo, ocupa 8 bytes. Então, esses 8 espaços de memória são alocados de forma
contígua (um ao lado do outro).
Sempre que declaramos uma variável que ocupa mais de um
bloco de memória, o seu endereço será o endereço
do primeiro bloco. Ou seja, se um inteiro ocupa 4 bytes (4 blocos), e
usamos o operador & para ver seu endereço, o endereço que vemos é o
endereço do primeiro byte. E qual o
endereço dos outros blocos? Ué, são vizinhos. Então, ocupam bytes, ou posições
de memória vizinha.
Se o primeiro byte está em 2112, o segundo vai estar na
posição 2113, o terceiro na posição 2114 e o quarto na posição 2115.
Vamos supor que queiramos declarar 1 milhão de inteiros em um vetor:
int numero_zao[1000000];
O endereço de 'numero_zao' é o endereço de 'numero_zao[0]', pois o endereço de uma variável ou vetor, é o endereço do primeiro bloco. E quantos bytes essa variável ocupa?
1 milhão * 4 bytes. = 4 000 000 bytes
Teste com : sizeof(numero_zao)
(Por ser um número muito grande de memória, há a possibilidade de sua máquina ficar lenta, de dar erro no programa, ou mesmo sua máquina travar. Lembre-se, a linguagem C é poderosa, você está manipulando e controlando cada pedacinho de sua máquina, por isso é fácil fazer alguma coisa que possa travar seu sistema. Não é à toa que C é a linguagem favorita dos hackers e a mais usada na criação de vírus).
Vamos supor que queiramos declarar 1 milhão de inteiros em um vetor:
int numero_zao[1000000];
O endereço de 'numero_zao' é o endereço de 'numero_zao[0]', pois o endereço de uma variável ou vetor, é o endereço do primeiro bloco. E quantos bytes essa variável ocupa?
1 milhão * 4 bytes. = 4 000 000 bytes
Teste com : sizeof(numero_zao)
(Por ser um número muito grande de memória, há a possibilidade de sua máquina ficar lenta, de dar erro no programa, ou mesmo sua máquina travar. Lembre-se, a linguagem C é poderosa, você está manipulando e controlando cada pedacinho de sua máquina, por isso é fácil fazer alguma coisa que possa travar seu sistema. Não é à toa que C é a linguagem favorita dos hackers e a mais usada na criação de vírus).
O mesmo ocorre para um ponteiro. Sabemos que os ponteiros, ou apontadores, armazenam o endereço de apenas um bloco de memória.
Quando um ponteiro aponta para uma variável que ocupa vários bytes, adivinha pra qual desses bytes o ponteiro realmente aponta? Ou seja, o endereço que o tipo ponteiro armazena, guarda o endereço de qual byte?
Do primeiro. Sempre do primeiro.
E como ele sabe o endereço dos outros? Os outros estão em posições vizinhas!
Vamos mostrar isso na prática com um exemplo.
Para isso, temos que declarar duas variáveis de um mesmo
tipo, e ver seus endereços de memória.
Exemplo de código: Mostrando as
posições dos blocos de variáveis
Crie duas variáveis de cada
tipo: char, int, float e double - e mostre o endereço destas.
Ao declarar e ver os endereços,
note que as variáveis do mesmo tipo, que foram declaradas juntas, estão em
endereços de memória contínuos.
No caso das chars, elas estão ao lado da outra, pois só ocupam 1 byte cada.
No caso dos inteiros e floats,
o espaço de endereço de uma variável pra outra é de 4 unidades, pois cada
variável desta ocupa 4 bytes.
Nas variáveis do tipo double, seus
endereços estão distantes em 8 unidades, bytes, um do outro.
Rode o seguinte código e veja:
#include <stdio.h> // Curso C Progressivo: www.cprogessivo.net // O melhor curso de C! Online e gratuito ! // Artigos, apostilas, tutoriais e vídeo-aulas sobre // a linguagem de programação C !int main(void){ char caractere1, caractere2; int inteiro1, inteiro2; float Float1, Float2; double Double1, Double2; printf("Caracteres: %d e %d\n", &caractere1, &caractere2); printf("Inteiros: %d e %d\n", &inteiro1, &inteiro2); printf("Floats: %d e %d\n", &Float1, &Float2); printf("Doubles: %d e %d\n", &Double1, &Double2);
return 0;
}
15 comentários:
uma coisa que percebi é que a primeira variável declarada sempre o numero do seu endereço é maior que a da segunda, por quê?
é que cada variável ocupa 4 Bytes de memória, então soma mais quatro de uma para outra, se for Double soma mais oito, char soma mais uma...
No gcc do ubuntu o primeiro exemplo da função sizeof dá esta mensagem de erro para os quatro tipos - char,int,float,double.
format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf("Double: %d bytes\n",sizeof(double))
No dev - c não dá.
no primeiro exemplo da função sizeof()
o gcc dá esta mensagem de erro:
sizeof.c:15:3: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf("Char: %d bytes\n",sizeof(char));
^
sizeof.c:16:3: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf("Int: %d bytes\n",sizeof(int));
^
sizeof.c:17:3: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf("Float: %d bytes\n",sizeof(float));
^
sizeof.c:18:3: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf("Double: %d bytes\n",sizeof(double));
no DEV - C não dá.
quanto aos erros, para int é %d, char é %c, float e double é %f, se usar %d em todos vai dar erro em char,float e double, só vai funcionar para int
Realmente %d, %c e %f são pra int, char e float, respectivamente. Pra double é %lf.
Mas o que se está mandando imprimir é endereço de memória, não char ou float ou outra coisa.
Outra coisa, warning não é erro.
Os blocos de memória são separados em ordem decrescente.
Um detalhe interessante que pude notar é que o C não indexa na memória por ordem de declaração no código.
Isto é, se eu declaro dois ints e depois dois chars, ele não indexa primeiro os ints e depois os chars, mas o contrário.
Pelo que vi, a ordem fica:
- char
- int
- float
- double
Na verdade, quando se trata de endereços de memória, alguns compiladores não aceitam o especificador de formato %d, nesse caso como no próprio aviso informa tem que ser usado um numero inteiro sem sinal, ou seja o %u.
Uma coisa interessante é que independente de onde você declare ou realize o print das variáveis a alocação de memória é conforme descrito no texto por agrupamento de tipo, sendo sequencial.
Acredito que seja devido ao "compile and executing".
O compilador lê o programa inteiro e depois o executa por pilha de atividades, sendo assim ordenando automáticamente as posições de memória. (Posso estar errado em meus argumentos, se alguém souber complementar ou corrigir minha análise lógica... )
Enfim, bons estudos a todos e obrigado pelo curso, estou aprendendo bastante.
Para quem quer ficar livre dos warnings, use o %p ao invés de %d. Abs!
/* Eu NÃO percebi nenhum sequênciamento nos endereços de memória. Pra mim, continuaram aleatórios. O que houve com o endereço para 'Caracteres' ?? */
// As saídas foram essas:
// Caracteres: u@ ( e ïu@ (
// Inteiros: 0x28ff18 e 0x28ff14
// Floats: 0x28ff10 e 0x28ff0c
// Doubles: 0x28ff00 e 0x28fef8
Até onde eu saiba, se criar int X, y; não necessariamente ficarão um ao lado do outro, embora provavelmente fiquem. Deve ficar ao lado se declarar um vetor int x[10]; aí, sim, x[0] Ficar ao lado de x[1]. É o maior endereço vai depender da arquitetura do processador. Alguns processadores almoçam os bits invertidos.
Pode ser que eu tenha deixado algo passar (estou lendo esse blog na correria por razões de fim de semestre), mas não entendi o motivo de no primeiro código as variáveis do tipo char (caractere1 e caractere2) são escritas como '' %d '' no printf, já que %d é leitor de inteiro. Alguém sabe me dizer o porquê? No livro de C do Luís Damas, ele diz que é perigoso fazer isso e pode resultar em erros
Sarah, acredito que a resposta nao esta vindo a tempo, mas que isso seja um lembrete para nao deixar as coisas pra fazer de ultima hora.
O motivo de usar " %d" eh que nao eh o conteudo das variaveis do tipo char que esta sendo exibido, mas sim o endereco das variaveis do tipo char, que sempre eh um valor inteiro.
& sempre vai retornar um inteiro que corresponde ao endereco da variavel que vem logo apos o &
Postar um comentário