image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Fernando Araujo
Fernando Araujo31/07/2024 10:04
Compartilhe

<Direto ao Ponto 33> Relação entre as variáveis e a memória

  • #Informática Básica

Artigos desta série: ( < ) Anterior | Índice | Seguinte ( > )

 

 Olá, dev!

 

 Este é mais um artigo da série DIRETO AO PONTO, que eu estou escrevendo para a DIO. Ele vai tratar da relação entre as variáveis das linguagens de programação a memória dos computadores.

 

Sumário

1.   Introdução

2.  As variáveis na programação

3.  Armazenamento de variáveis na memória

4.  Exemplo de programa em execução

5.  Organização da memória

6.  Considerações finais

7.   Referências

 

1 – Introdução

 

Eu criei a série de artigos DIRETO AO PONTO com o objetivo de apresentar, de forma simples e direta, conhecimentos básicos da programação e de computação, principalmente, para os iniciantes.

 

Aqui, são tratados de temas como lógica de programação, linguagens, hardware dos computadores, história da computação e assuntos relacionados à plataforma da DIO, como a escrita de artigos e os desafios de código.

 

Neste artigo, será tratada a relação entre as variáveis de linguagens de programação e a memória do computador.

 

Desde as primeiras máquinas mecânicas de realizar cálculos, havia uma maneira de armazenar os resultados das operações e dos cálculos intermediários.

 

O cientista von Neuman estabeleceu a arquitetura de juntar os programas e os dados na memória, de forma que as alterações ocorridas durante a execução de um programa fossem feitas diretamente na memória.

 

Da mesma forma, desde as primeiras linguagens de programação, a forma encontrada para armazenar estas operações se baseava em variáveis.

 

Os paradigmas de programação começaram com o tipo código “spaghetti” (que usava a instrução “GO TO” em todo canto, depois passou para a programação estruturada, procedural, orientada a objetos, funcional, e as variáveis ainda continuam sendo uma parte essencial da programação.

 

Resumindo, isso mostra a importância das variáveis para a programação e sua relação com a memória do computador. Vamos ver tudo em detalhes agora.

 

 

2 – As variáveis na programação

 

Como já foi dito, as variáveis são essenciais em qualquer linguagem de programação e são usadas para armazenar os valores temporários e os resultados das operações que são executadas.

 

Uma variável de uma linguagem de programação é uma entidade que pode guardar um valor durante o programa, de forma que ele possa ser usado posteriormente por outras instruções do programa. Uma variável está associada a um nome e pode armazenar um valor dentre os vários tipos de dados oferecidos pela linguagem.

 

O valor armazenado em uma variável pode ser lido (copiado) para uso em outras partes do programa. Uma variável pode armazenar um novo valor, que SUBSTITUIRÁ o valor atualmente armazenado nela.

 

O valor armazenado em uma variável fica guardado na memória do computador, até que outro valor o substitua.

 

As linguagens costumam ter muitos tipos primitivos para definir as variáveis, como inteiro, real, caractere e booleano, podendo ter também tipos complexos, como strings, listas etc. Cada tipo tem suas próprias características, como:

 

·        Inteiro (int) – permite representar valores inteiros, sem casas decimais;

·        Real (float) – pode representar números reais, com casas decimais;

·        Caractere (char) – pode representar caracteres alfanuméricos, de controle e outros;

·        Booleano (boolean) – Pode representar os dois valores lógicos verdadeiro e falso;

·      Cadeia de caracteres (string) – permite armazenar sequências de caracteres, numa espécie de vetor de caracteres. Algumas linguagens não os oferecem como tipos primitivos, mas estruturas complexas, como vetores de caracteres;

·        Vetores (list) – permite armazenar uma sequência de dados, sejam eles caracteres alfanuméricos, valores numéricos ou outros tipos mais avançados (objetos). Da mesma forma que os strings, algumas linguagens os oferecem como estruturas complexas;

 

 

3 – Armazenamento de variáveis na memória

 

Para ser usada nos programas, uma variável precisa guardar um valor e, para isso, este valor é armazenado na memória do computador.

 

O armazenamento de variáveis na memória é diretamente relacionado ao tamanho da palavra do processador. Antigamente, ela era de 8 bits, depois, passou para 16 bits, 32 e hoje, é comum um computador ter palavra de 64 bits.

 

Segundo DEITEL [1], cada tipo citado anteriormente ocupa a memória de diferentes formas:

 

·        Inteiro – em geral, ocupa o tamanho de uma palavra do computador. A quantidade possível de números representados (2n) é determinada pelo número de bits da palavra do computador (n), enquanto os valores representados vão de 0 até 2n – 1, para inteiros sem sinal (unsigned), e de –2(n-1) até 2(n-1) – 1, para inteiros com sinal.

 

Por exemplo, para n = 16 bits (216), os valores vão de 0 até 65535 (sem sinal), ou de -32768 até 32767, com sinal. Outros tipos de inteiros, como short int e long int, oferecidos por algumas linguagens, são representados por 8 bits e 32 bits, respectivamente, e apresentam números diferentes destes;

 

·        Real – geralmente, usam-se 32 bits para representar números reais nos computadores de palavra de 32 bits, e os números mínimo e máximo que podem ser representados por um tipo float são -1.4 x 10-45 e 3.4 x 1038. Um tipo double, com dupla precisão, pode representar muito mais números que o float;

 ·        Caractere – um tipo caractere usa 8 bits para sua representação, portanto, ele permite (28) 256 caracteres diferentes, do código ASCII, por exemplo; se ele usar 16 bits, permite representar até 216 caracteres, do código Unicode, por exemplo.

 ·        Booleano – Um tipo booleano (ou lógico) armazena 0 ou 1, bastando 1 bit para isso, mas, por questões técnicas, ele usa todo um byte para armazenar cada valor;

 ·        Vetores (list) – um vetor (ou lista), seja de dados numéricos ou caracteres, é armazenado na memória em posições contíguas, sequencialmente, um após o outro. Esta forma sequencial de armazenamento permite um acesso iterativo muito rápido aos itens do vetor. A quantidade de itens que podem ser armazenados depende dos limites da memória ou das especificações de cada linguagem de programação.

 

 

Por mais complexa que seja a implementação da memória de um computador dentro dos circuitos integrados, é comum se usar uma representação simplificada, imaginando-a como uma coluna de registros sequenciais de tamanho da palavra do computador (8, 16, 32, 64), como uma série de registradores com este tamanho um abaixo do outro.

 

Eles são identificados univocamente por um endereço inteiro (0, 1, 2...), representado em binário.

 

É como uma planilha do Excel com apenas uma coluna, sendo os conteúdos de cada linha representados por um número fixo e definido de caracteres.

 

Um tipo simples, como um caractere, usaria apenas 1 registrador (ou 1 linha da planilha), sendo representado por um número binário por n bits, o tamanho da palavra do computador. Um tipo maior, como um inteiro, seria representado por 2 registradores contíguos (2 linhas seguidas da planilha), e assim por diante

 

 

4 – Exemplo de programa em execução

 

Para ficar mais claro, vamos ver um exemplo de um código, na linguagem C, e ilustrar como ficaria a representação dos valores das variáveis na memória do computador.

 

Por simplificação didática, vou usar os seguintes requisitos:

·        A palavra do computador é 8 bits, ou seja, a menor representação de um dado é de 8 bits;

·        A memória ocupada por um caractere é de 1 posição (8 bits);

·        Um inteiro ocupa 2 posições de memória (16 bits);

·        Um tipo float ocupa 4 posições de memória (32 bits);

·        Um booleano ocupa 1 posição de memória (8 bits);

·        Um vetor de números inteiros ocupa 2 posições para cada valor inteiro no vetor.

 

Dados do programa

 char letra = ‘A’;

 int ano = 2024;

 float temperatura = 28.7;

 boolean calor = true – em binário de 8 bits, fica 00000001

 

 Código do programa

 

 /* programa com declaração de variáveis de diversos tipos */
 
#include <stdio.h>
#include <stdbool.h>
 
int main(void) {
 char letra = 'A';
 int ano = 2024;
 float temperatura = 28.7;
 bool calor = true;
 int notas[] = {7, 8, 9, 10};
 
 printf("letra = %c\n", letra);
 printf("ano = %d\n", ano);
 printf("temperatura = %5.2f\n", temperatura);
 printf("calor = ");
 calor? printf("true\n") : printf("false\n");
 printf("notas = [ %d, %d, %d, %d ]\n",notas[0], notas[1], notas[2], notas[3]); 
 
 return 0;
}

  

Saída deste programa:

  letra = A

 ano = 2024

 temperatura = 28.7

 calor = true

 notas = [ 7, 8, 9, 10 ]

 


Representação dos dados em binário, para armazenamento na memória:

 

Agora, vamos representar estes mesmos dados em código binário e mostrar como eles seriam armazenados em uma memória.

 

·        char letra = ‘A’: caractere A maiúsculo, do código ASCII (decimal 61, hexa x41);

·        int ano = 2024: em binário de 16 bits fica (0000011111101000), em hexa, x07E8;

·    float temperatura = 28.7: no padrão IEEE 754 (sinal, expoente, mantissa), de 32 bits, fica [0, 01111100, 00101110001101100011011], ou [00111110 00010111 00011011 00011011], em grupos de 8 bits;

 ·        boolean calor = true: em binário de 8 bits, fica 00000001;

 

A figura abaixo mostra como seria a representação destes dados na memória do computador, organizada em termos de ocupação em bytes.


image


A próxima figura mostra uma representação destes mesmos dados em uma estrutura simplificada de memória, organizada como uma coluna de registros do mesmo tamanho (1 byte), na qual os conteúdos das variáveis são dispostos sequencialmente, podendo ocupar vários bytes, dependendo do tipo de variável.


image


Agora vamos pegar parte desta memória para exemplificar como funcionam os processos de utilização das variáveis de um programa em execução na memória do computador.

  

/* programa para exemplo de uso da memória em execução */
 
#include <stdio.h>
 
int main(void) {
 int a = 5;
 int b = 10;
 int soma;
 
 soma = 0;
 soma = a + b;
 
 printf("A soma de a + b é = %d\n", soma);
 
 return 0;
}

 

 

Saída deste programa:

 A soma de a + b é = 15

  

A figura abaixo mostra o estado atual da memória logo após a execução de cada instrução.

 

image

 

 

A execução se dá da seguinte forma, passo a passo:

 

·        int a = 5; são reservados 2 bytes na memória, identificados pelo símbolo “a”, onde é armazenado o valor decimal 5;

·        int b = 10; são reservados mais 2 bytes, em seguida aos anteriores, identificados pelo símbolo “b”, onde é armazenado o valor decimal 10;

·        int soma; são reservados mais 2 bytes, em seguida aos anteriores, identificados pelo símbolo “soma” e não é armazenado nenhum valor;

·        soma = 0; é armazenado o valor decimal 0 na posição de memória identificada pelo símbolo “soma”;

·        soma = a + b; o conteúdo da posição de memória identificada pelo símbolo “a” é copiado, depois o conteúdo da posição de memória identificada pelo símbolo “b” também é copiado, os dois valores são somados e o valor da soma (15) é armazenado na posição de memória identificada pelo símbolo “soma”, SUBSTITUINDO o valor que estava armazenado lá (0). OBS: O símbolo de igual não significa igualdade matemática, mas a atribuição do resultado da operação à variável “soma”;

·        printf("A soma de a + b é = %d\n", soma); o valor armazenado na posição de memória identificada com o símbolo “soma” é copiado e usado no lugar dos termos %d da instrução de impressão. Será impresso na tela o texto “A soma de a + b é = 15”.

  

OBSERVAÇÕES IMPORTANTES: Para este exemplo, foram consideradas muitas simplificações importantes, por questões didáticas:

 

·        Ao iniciar a execução de um programa (ou ao ligar um computador), o conteúdo das células da memória não é vazio, limpo. Cada flip-flop (1 bit) de cada registrador apresenta um valor de tensão alto ou zero, representando um bit lógico 1 ou 0, respectivamente. E isso ocorre de forma aleatória, para cada bit, de forma que o conteúdo de cada byte ou de cada posição de memória não podem ser identificados.


É o que de chama de lixo dos conteúdos iniciais das variáveis, por isso é importante inicializar as variáveis ao declará-las. Algumas linguagens de programação zeram automaticamente o conteúdo de variáveis de alguns tipos ao declará-las, mas é bom não confiar no conteúdo de variáveis não inicializadas explicitamente, principalmente no caso de variáveis que armazenam endereços (como os apontadores, na linguagem C, por exemplo).

·        Os endereços iniciais de um bloco de memória usado por um programa não se iniciam no endereço zero (00000000), mas algum endereço bem mais para a frente, definido como um offset para o bloco inteiro. Nos endereços iniciais da memória são armazenados os códigos de programas usados pelo sistema operacional, utilitários usados por ele e drivers de diversos tipos, e não é permitido a nenhum programa escrever (ou acessar o conteúdo) destes endereços.

 ·        As variáveis usadas (e definidas) em um programa não são armazenadas todas em um mesmo bloco único de memória. Dependendo do tipo de memória ou de sua alocação, elas são armazenadas em blocos diferentes, de forma organizada e pré-definida, que depende de cada sistema operacional. É o que vamos ver na seção seguinte.

 

 

5 – Organização da memória de um programa em execução

 

Segundo MAZIERO [2], A ocupação da memória pelas variáveis de um programa em execução é organizada de forma que alguns tipos de dados sejam armazenados separadamente, em blocos contíguos de memória.

 

Basicamente, o espaço de endereços é dividido em áreas, das quais destaco:

 

  • TEXTarmazena o código do programa e suas constantes. Esta área é alocada durante o início da execução e não altera de tamanho;
  • DATA - armazena as variáveis globais e estáticas, inicializadas. Também mantém seu tamanho inalterado durante a execução;
  • BSS - áreas de armazenamento das variáveis globais e estáticas, não inicializadas. Seu tamanho não é alterado durante a execução;
  • STACK - É a pilha de execução, que armazena os parâmetros, endereços de retorno e variáveis locais de funções. Pode variar de tamanho durante a execução do processo;
  • HEAPusada para armazenamento de memória alocada explicitamente por instruções de alocação de memória. Seu tamanho também pode variar durante a vida do processo.

 

A figura abaixo mostra a disposição destas áreas na memória.


image

 

 

Note que o bloco referente ao HEAP cresce para cima (endereços crescentes), enquanto a pilha cresce para baixo (endereços decrescentes), dentro da área livre. Em geral, um programa suporta três tipos de alocação de memória:

 

  • Alocação automática – usada na declaração das variáveis locais e parâmetros de funções;
  • Alocação estática – empregada na declaração de variáveis globais ou estáticas;
  • Alocação dinâmica – usada na requisição explícita de um bloco de memória para armazenar dados.

  

Nos dois primeiros casos, a memória é totalmente liberada após o final da execução do programa; no último, o programador é responsável por liberar as áreas alocadas dinamicamente.

 

 

6 – Considerações finais

 

Este é mais um artigo da série DIRETO AO PONTO, que eu estou escrevendo para a DIO.

 

Desta vez, foi apresentada a relação entre as variáveis de linguagens de programação e a memória do computador.

 

Muitas vezes, o programador codifica o seu programa e vê a simplicidade de declarar as variáveis do seu programa e nem imagina que, por trás destas ações simples, existe toda uma organização lógica para permitir que a execução correta das instruções.

 

Além disso, existe toda uma precisão eletrônica, de sincronização de sinais, que permite o correto e preciso funcionamento dos componentes eletrônicos que implementam os chips de memória.

 

Nos próximos artigos, vamos dar um tempo nos assuntos de hardware e vamos tratar de assuntos de programação. Ainda tem alguns temas sobre hardware que quero tratar, como os processadores, a placa-mãe e as placas de vídeo.

 

No próximo artigo, vou falar sobre os algoritmos, assunto essencial à programação, muito mais importante que a codificação, mas negligenciado por muitos programadores, que partem direto para os códigos, deixando a lógica do programa em segundo plano.

 

 

7 – Referências

 

 [1] DEITEL, Paul, DEITEL, Harvey. “C How to program, Ninth Edition, Pearson Education Limited, 2023.

 

[2] MAZIERO, Carlos. Alocação de memória. Disponível em: https://wiki.inf.ufpr.br/maziero/doku.php?id=c:alocacao_de_memoria>. Acessado em: 27/07/2024.

 

 

Artigos desta série: ( < ) Anterior | Índice | Seguinte ( > )

 

Compartilhe
Recomendados para você
Microsoft 50 Anos - Prompts Inteligentes
Microsoft 50 Anos - GitHub Copilot
Microsoft 50 Anos - Computação em Nuvem com Azure
Comentários (2)
Fernando Araujo
Fernando Araujo - 31/07/2024 11:54

Exato, Luis!

É um conhecimento que o programador de sistemas para IOT ou de sistemas embarcados vai precisar ter para aproveitar ao máximo as limitações de memória.

Um programador de linguagens de baixo nível também precisa muito saber destes detalhes, como programadores em Assembly ou em linguagens como C.

Luis Zancanela
Luis Zancanela - 31/07/2024 11:36

É um assunto que considero bem relevante, mesmo que a linguagem de programação não exija explicitamente esse conhecimento ele é importante para o programador entender como funciona por debaixo dos panos e até aprimorar para o seu produto conforme necessidade.