Enviar um café pro programador

Pode me ajudar a transformar cafeína em código?

A função sizeof() e os blocos vizinhos de memória

No artigo introdutório desta seção sobre ponteiros nossa apostila de C, demos uma ideia sobre o que são ponteiros em C, além de falarmos sobre os endereços de memória.

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. 


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.

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).

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:

Anônimo disse...

uma coisa que percebi é que a primeira variável declarada sempre o numero do seu endereço é maior que a da segunda, por quê?

Anônimo disse...

é 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...

eduardo disse...

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á.

eduardo disse...

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á.

Anônimo disse...

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

Anônimo disse...

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.

Unknown disse...

Os blocos de memória são separados em ordem decrescente.

Stanley Sathler disse...

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

Anônimo disse...

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.

Gabriel disse...

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.

Christopher disse...

Para quem quer ficar livre dos warnings, use o %p ao invés de %d. Abs!

Unknown disse...


/* 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

Anônimo disse...

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.

sarah disse...

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

Unknown disse...

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 &