diff --git a/aulas/04-mem-layout/README.org b/aulas/04-mem-layout/README.org deleted file mode 100644 index e79c5d4..0000000 --- a/aulas/04-mem-layout/README.org +++ /dev/null @@ -1,229 +0,0 @@ -#+title: Curso Básico da Linguagem C -#+subtitle: Aula 4: Layout de memória -#+author: Blau Araujo -#+startup: show2levels -#+options: toc:3 - -* Aula 4: Layout de memória - -- [[][Vídeo desta aula]] - -** Introdução - -No fundo, esta é uma aula sobre variáveis. Mas, principalmente para quem está -iniciando na programação em C, dizer que variáveis são como gavetas onde nós -guardamos e acessamos dados aleatoriamente é quase a mesma coisa que tentar -formar cirurgiões com o Jogo da Operação. - -#+CAPTION: Jogo da Operação -[[./jogo-taz.png]] - -Isso funciona muito bem com crianças em alfabetização, mas não com quem está -se preparando para assumir responsabilidades programando numa linguagem que -deixa por conta da pessoa que programa todos os cuidados com as potenciais -falhas e vulnerabilidades que podem por em risco, não apenas o programa que -está sendo escrito, como também todo o sistema em que ele será executado. - -Então, nós podemos definir variáveis como elementos da linguagem que serão -associados a valores manipuláveis na memória, podemos dizer que esses valores -serão dimensionados conforme seus tipos (assunto da aula passada), que eles -serão encontrados em endereços específicos na memória... Enfim, mas nada -disso fará sentido, nem dará uma noção realista das implicações do poder que -nós temos em mãos quando somos autorizados, pela linguagem C, a manipular -quase que livremente o espaço de memória. - -Em grande parte, é a falta dessa noção que nos leva a situações de risco, -como (se prepare para o inglês): - -- Heap Overflow -- Stack Overflow -- Buffer Overflow -- Use-After-Free (UAF) -- Double Free -- Dangling Pointer -- Memory Leak -- Uninitialized Memory Access -- Out-of-Bounds Read/Write -- Null Pointer Dereference -- Stack Corruption -- Heap Corruption -- Race Conditions - -E depois, vão dizer que a linguagem C é insegura, propensa a vulnerabilidades -de memória... E por aí vai. Sim, a linguagem C não tem mecanismos que nos -impeçam de cometer erros, mas não é ela que comete os erros. - -Por isso, este talvez seja o vídeo mais longo do nosso curso. Nele, nós vamos -demonstrar como o sistema operacional, o nosso GNU/Linux, lida com a execução -de programas, especificamente no que diz respeito ao espaço de memória que é -disponibilizado para eles. - -** Processos e memória - -- O kernel gerencia a execução de programas através de /processos/. -- Processos são estruturas de dados associadas a cada um dos programas - que estão sendo executados. -- Uma parte central dessa estrutura de dados é o /layout de memória/, que - é uma faixa de endereços mapeada pelo sistema para que os programas possam - acessar a memória através de endereços adjacentes. - -#+begin_quote -Essa faixa de endereços é /virtual/ porque são endereços que não correspondem -aos endereços reais da memória física e, por isso mesmo, os programas terão -acesso a uma faixa contínua de endereços em vez de localizações espalhadas -e dispersas ao longo dos endereços reais da memória. -#+end_quote - -** O espaço de endereços (layout de memória) - -A faixa de endereços atribuída a um processo é dividida em vários /segmentos -de memória/ com finalidades específicas. - -#+caption: Layout de memória -[[./mem-layout.png]] - - -*** Dados copiados do binário do programa - -Nos endereços mais baixos da memória virtual, serão copiados os dados -presentes nas /seções/ dos binários dos nossos programas: - -- =.text=: O conteúdo executável do programa (código). -- =.rodata=: Dados constantes. -- =.data=: Dados globais e estáticos inicializados. -- =.bss=: Dados globais e estáticos não inicializados. - -*** Dados dinâmicos - -A região intermediária dos endereços mapeados, chamada de /heap/, é reservada -ao uso com: - -- Dados que requeiram espaços alocados dinamicamente ao longo da execução - do programa (com a função =malloc=, por exemplo). -- Mapeamento do conteúdo de arquivos e grandes volumes de dados. -- Conteúdo de bibliotecas carregadas dinamicamente, como a =glibc=, o - carregador dinâmico (=ld-linux=) e a biblioteca =vdso=, do Linux. - -#+begin_quote -A localização dos dados dinâmicos é aleatória dentro da faixa do /heap/ -que, conforme a necessidade, se expande na direção dos endereços mais -altos, ou seja, em direção à pilha. -#+end_quote - -*** Pilha (stack) - -Os endereços mais altos da memória virtual são reservados à /pilha de -execução/ do programa. Uma /pilha/, ou /stack/, é uma estrutura onde os dados -são, literalmente, empilhados uns sobre os outros. No GNU/Linux, a base -da pilha está no seu endereço mais alto, enquanto que os novos dados serão -empilhados na direção dos endereços mais baixos. - -Ao ser iniciada, a pilha recebe, da sua base para o topo: - -- Lista das variáveis exportadas para o processo (/ambiente/ / /envp/). -- Lista dos argumentos de linha de comando que invocaram o programa (/argv/). -- Um valor inteiro relativo à quantidade de argumentos (/argc/). - -No caso de programas escritos em C, ao serem iniciados, o dado no topo da -pilha, a quantidade de argumentos, é removido e, a partir daí, são -empilhados os dados locais da função =main=, o que inclui: - -- Variáveis declaradas nos parâmetros da função. -- Variáveis declaradas no corpo da função. - -À medida em que o programa é executado, os dados das outras funções -chamadas também serão empilhados até serem removidos após seus respectivos -términos. - -** Resumo do mapeamento de memória - -O kernel expõe diversas informações sobre os processos em execução na -forma de arquivos de texto no diretório virtual =/proc=. Nele, cada processo -terá um diretório e, nesses diretórios, nós encontramos o arquivo =maps=, -que contém uma versão resumida de todas as faixas de endereços mapeados. - -Para visualizar o mapeamento de um processo de número =PID=: - -#+begin_example -cat /proc/PID/maps -#+end_example - -** Programa =memlo.c= - -Para demonstrar como os dados de um programa são mapeados na memória virtual, -nós vamos utilizar o programma =memlo.c=: - -#+begin_src c -#include -#include -#include - -#define APGL_HINT 0x4c475041 -#define BLAU_HINT 0x55414c42 - -int bss_var; -int data_var = APGL_HINT; -const int ro_data = BLAU_HINT; - -int func(void) { - return 42; -} - -int main(int argc, char **argv, char **envp) { - - static int lsni_var; - static int lsi_var = BLAU_HINT; - - int lni_var; - int li_var = APGL_HINT; - - char *str_ptr = "Salve!"; - - void *heap_ptr = malloc(16); - - puts("[stack]"); - printf("%p início do vetor envp (%s)\n", *envp, *envp); - printf("%p início do vetor argv (%s)\n", *argv, *argv); - printf("%p envp ponteiro para envp (%p)\n", envp, *envp); - printf("%p argv ponteiro para argv (%p)\n", argv, *argv); - printf("%p lni_var variável não inicializada\n", &lni_var); - printf("%p li_var variável inicializada\n", &li_var); - printf("%p &str_ptr ponteiro com endereço da string (%p)\n", &str_ptr, "Salve!"); - printf("%p &heap_ptr ponteiro com endereço na heap (%p)\n", &heap_ptr, heap_ptr); - printf("%p argc quantidade de argumentos (%d)\n", &argc, argc); - - puts("[mmap]"); - printf("%p malloc() função da glibc\n", (void *)malloc); - printf("%p printf() função da glibc\n", (void *)printf); - - puts("[heap]"); - printf("%p heap_ptr espaço alocado dinamicamente na heap\n", heap_ptr); - - puts("[.bss]"); - printf("%p lsni_var variável local estática não inicializada\n", &lsni_var); - printf("%p bss_var variável global não inicializada\n", &bss_var); - - puts("[.data]"); - printf("%p lsi_var variável local estática inicializada\n", &lsi_var); - printf("%p data_var variável global inicializada\n", &data_var); - - puts("[.rodata]"); - printf("%p str_ptr endereço de uma string (%s)\n", str_ptr, str_ptr); - printf("%p ro_data constante global\n", &ro_data); - - puts("[.text]"); - printf("%p main() função main\n", (void *)main); - printf("%p func() função func\n", (void *)func); - - free(heap_ptr); - - sleep(300); - - return EXIT_SUCCESS; -} -#+end_src - -#+begin_quote -A análise do programa em si ficará como parte dos execícios desta aula. -#+end_quote - diff --git a/aulas/04-mem-layout/jogo-taz.png b/aulas/04-mem-layout/jogo-taz.png deleted file mode 100644 index 20fcd6c..0000000 Binary files a/aulas/04-mem-layout/jogo-taz.png and /dev/null differ diff --git a/aulas/04-mem-layout/mem-layout.png b/aulas/04-mem-layout/mem-layout.png deleted file mode 100644 index 8ebd5f2..0000000 Binary files a/aulas/04-mem-layout/mem-layout.png and /dev/null differ diff --git a/aulas/04-mem-layout/memlo.c b/aulas/04-mem-layout/memlo.c deleted file mode 100644 index 74a43f2..0000000 --- a/aulas/04-mem-layout/memlo.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include - -#define APGL_HINT 0x4c475041 -#define BLAU_HINT 0x55414c42 - -int bss_var; -int data_var = APGL_HINT; -const int ro_data = BLAU_HINT; - -int func(void) { - return 42; -} - -int main(int argc, char **argv, char **envp) { - - static int lsni_var; - static int lsi_var = BLAU_HINT; - - int lni_var; - int li_var = APGL_HINT; - - char *str_ptr = "Salve!"; - - void *heap_ptr = malloc(16); - - puts("[stack]"); - printf("%p início do vetor envp (%s)\n", *envp, *envp); - printf("%p início do vetor argv (%s)\n", *argv, *argv); - printf("%p envp ponteiro para envp (%p)\n", envp, *envp); - printf("%p argv ponteiro para argv (%p)\n", argv, *argv); - printf("%p lni_var variável não inicializada\n", &lni_var); - printf("%p li_var variável inicializada\n", &li_var); - printf("%p &str_ptr ponteiro com endereço da string (%p)\n", &str_ptr, "Salve!"); - printf("%p &heap_ptr ponteiro com endereço na heap (%p)\n", &heap_ptr, heap_ptr); - printf("%p argc quantidade de argumentos (%d)\n", &argc, argc); - - puts("[mmap]"); - printf("%p malloc() função da glibc\n", (void *)malloc); - printf("%p printf() função da glibc\n", (void *)printf); - - puts("[heap]"); - printf("%p heap_ptr espaço alocado dinamicamente na heap\n", heap_ptr); - - puts("[.bss]"); - printf("%p lsni_var variável local estática não inicializada\n", &lsni_var); - printf("%p bss_var variável global não inicializada\n", &bss_var); - - puts("[.data]"); - printf("%p lsi_var variável local estática inicializada\n", &lsi_var); - printf("%p data_var variável global inicializada\n", &data_var); - - puts("[.rodata]"); - printf("%p str_ptr endereço de uma string (%s)\n", str_ptr, str_ptr); - printf("%p ro_data constante global\n", &ro_data); - - puts("[.text]"); - printf("%p main() função main\n", (void *)main); - printf("%p func() função func\n", (void *)func); - - free(heap_ptr); - - sleep(300); - - return EXIT_SUCCESS; -} diff --git a/aulas/04-mem-layout/var-global.c b/aulas/04-mem-layout/var-global.c deleted file mode 100644 index b382999..0000000 --- a/aulas/04-mem-layout/var-global.c +++ /dev/null @@ -1,12 +0,0 @@ -#include - -int b = 0x5a; - -int main(void) { - int a = 0x58; - - printf("a: %d @ %p\n", a, &a); - printf("b: %d @ %p\n", b, &b); - - return 0; -} diff --git a/aulas/04-mem-layout/var-local.c b/aulas/04-mem-layout/var-local.c deleted file mode 100644 index 0e3e7c9..0000000 --- a/aulas/04-mem-layout/var-local.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main(void) { - int a = 17, b = 25; - - printf("a: %d @ %p\n", a, &a); - printf("b: %d @ %p\n", b, &b); - - return 0; -} diff --git a/aulas/04-variaveis/README.org b/aulas/04-variaveis/README.org new file mode 100644 index 0000000..0f19fe4 --- /dev/null +++ b/aulas/04-variaveis/README.org @@ -0,0 +1,181 @@ +#+title: Curso Básico da Linguagem C +#+subtitle: Aula 4: Variáveis e ponteiros +#+author: Blau Araujo +#+startup: show2levels +#+options: toc:3 + +* Variáveis e ponteiros + +- *Variáveis* são elementos da linguagem que representam dados na memória. +- *Ponteiros* são variáveis que representam endereços de dados na memória. + +** Declaração e definição + +Variáveis precisam ser declaradas e definidas antes de serem utilizadas. + +Exemplo (=var1.c=): + +#+begin_src c +#include + +int main(void) { + + int a = 10; /* Variável declarada e inicializada (definida). */ + int b; /* Variável declarada, mas não inicializada! */ + + printf("a = %d\n", a); + printf("b = %d\n", b); + printf("a + b = %d\n", a + b); + + return 0; +} +#+end_src + +Compilando e executando: + +#+begin_example +:~$ gcc -o var1 var1.c +:~$ ./var1 +a = 10 +b = 361680176 +a + b = 361680186 +#+end_example + +Observe: + +- A compilação não emitiu avisos e nem terminou com erro. +- A variável =b= (não inicializada) avaliou um valor qualquer. + +*** Por que isso aconteceu? + +Ao ser declarada, a variável recebeu um endereço e um tipo (=int=). Como não +foi inicializada, seu valor será o que quer que esteja nos 4 bytes da +memória a partir do endereço designado para =b=. + +Por exemplo: + +#+begin_example +a -> 0x7ffd45c33fdc: 0a 00 00 00 (10) +b -> 0x7ffd45c33fd8: 3a cd 8e 15 (361680176) <-- LIXO! +#+end_example + +*** Como evitar esse erro? + +A primeira (e mais óbvia) opção é não se esquecer de atribuir um valor +à variável antes de usá-la, mas a compilação poderia ter sido feita com +as opções de avisos: + +- =-Wall=: Habilita todos os avisos sobre problemas evitáveis no código. +- =-Wextra=: Habilita avisos adicionais. +- =-Werror=: Trata todos os avisos como erros fatais. + +Exemplo: + +#+begin_example +:~$ gcc -o var1 var1.c -Wall -Wextra -Werror +var1.c: In function ‘main’: +var1.c:10:5: error: ‘b’ is used uninitialized [-Werror=uninitialized] + 10 | printf("b = %d\n", b); + | ^~~~~~~~~~~~~~~~~~~~~ +var1.c:7:9: note: ‘b’ was declared here + 7 | int b; /* Variável declarada, mas não inicializada! */ + | ^ +cc1: all warnings being treated as errors +#+end_example + +** Atributos das variáveis + +Toda variável tem como atributos... + +- Um nome (identificador); +- Um valor associado; +- O endereço do valor associado. + +O nome da variável expressa o seu valor: + +#+begin_src c +int a = 21; +int b = a; // O valor de 'b' é o valor de 'a' (21). +int c = a + b; // O valor de 'c' é 21+21 (42). +#+end_src + +Para que uma variável expresse o endereço do valor associado, nós utilizamos +o operador unário =&= antes de seu nome: + +#+begin_src c +int a = 123; + +printf("Valor de a : %d", a); +printf("Endereço de a: %p", &a); // Imprime o endereço do dado em 'a'. +#+end_src + +O tamanho do tipo da variável é expresso com o operador =sizeof(VAR)=: + +#+begin_src c +int a = 10; +int b = sizeof(a); // O valor de 'b' será 4 (bytes). +#+end_src + +#+begin_quote +O tipo do valor expresso por =sizeof= é =size_t=, um apelido para =unsigned long=. +#+end_quote + +** Escopo de variáveis + +- Toda variável declarada numa função só é visível na própria função (escopo /local/). +- Toda variável declarada fora de funções é visível em todo o programa (escopo global). + +Exemplo (=var3.c=): + +#+begin_src c +#include + +int c = 17; // Variável global. + +// As variáveis 'num' e 'b' são locais à função 'soma10'. +int soma10(int num) { + int b = 10; + return b + num; +} + +int main(void) { + + int a = 25; // A variável 'a' é local à função 'main'. + + // Isso causará um erro na compilação! + printf("a = %d\nb = %d\nc = %d\nnum = %d\n", a, b, c, num); + + return 0; +} +#+end_src + +** Ponteiros + +Antes de qualquer coisa, os ponteiros são simplesmente variáveis, embora +possuam algumas particularidades: + +- São declarados com o operador de derreferência (ou indireção) =*NOME=. +- Recebem endereços como valores. +- Seus tipos são suas unidades aritméticas. +- Seus nomes expressam os endereços associados. +- Quando derreferenciados (=*NOME=), expressam os valores nos endereços. + +*** Declaração do ponteiro + +#+begin_src c +int a = 25; +int *pa = &a; // O valor de 'pa' será o endereço de 'a'. +int b = *pa; // O valor de 'b' será o valor no endereço em 'pa'. +#+end_src + +** Perguntas da aula + +*** A cada execução do programa, os endereços das variáveis mudam? + +Sim, porque o /espaço de endereços/ é designado pelo sistema operacional +assim que o processo para executar o programa é criado. + +*** O ponto da declaração de uma variável global faz diferença? + +Sim, toda variável tem que ser declarada antes do ponto no código +em que é utilizada. diff --git a/aulas/04-variaveis/a.out b/aulas/04-variaveis/a.out new file mode 100755 index 0000000..3822693 Binary files /dev/null and b/aulas/04-variaveis/a.out differ diff --git a/aulas/04-variaveis/teste.c b/aulas/04-variaveis/teste.c new file mode 100644 index 0000000..c12f3e0 --- /dev/null +++ b/aulas/04-variaveis/teste.c @@ -0,0 +1,17 @@ +#include + +int a = 23; + +int b; + +void print_b(void) { + b = 10; + printf("b=%d\n", b); +} + + +int main(void) { + printf("a=%d\n", a); + print_b(); +} + diff --git a/aulas/04-variaveis/var1 b/aulas/04-variaveis/var1 new file mode 100755 index 0000000..0ad208d Binary files /dev/null and b/aulas/04-variaveis/var1 differ diff --git a/aulas/04-variaveis/var1.c b/aulas/04-variaveis/var1.c new file mode 100644 index 0000000..9c72564 --- /dev/null +++ b/aulas/04-variaveis/var1.c @@ -0,0 +1,14 @@ +#include + + +int main(void) { + + int a = 10; /* Variável declarada e inicializada (definida). */ + int b; /* Variável declarada, mas não inicializada! */ + + printf("a = %d\n", a); + printf("b = %d\n", b); + printf("a + b = %d\n", a + b); + + return 0; +} diff --git a/aulas/04-variaveis/var2 b/aulas/04-variaveis/var2 new file mode 100755 index 0000000..59429d5 Binary files /dev/null and b/aulas/04-variaveis/var2 differ diff --git a/aulas/04-variaveis/var2.c b/aulas/04-variaveis/var2.c new file mode 100644 index 0000000..86cc2d1 --- /dev/null +++ b/aulas/04-variaveis/var2.c @@ -0,0 +1,15 @@ +#include + + +int main(void) { + + int a = 17; + int b = 25; + + puts("Var Address Size Value"); + + printf("a -> %p %4zu %5d\n", &a, sizeof(a), a); + printf("b -> %p %4zu %5d\n", &b, sizeof(b), b); + + return 0; +} diff --git a/aulas/04-variaveis/var3.c b/aulas/04-variaveis/var3.c new file mode 100644 index 0000000..8f1fbf9 --- /dev/null +++ b/aulas/04-variaveis/var3.c @@ -0,0 +1,16 @@ +#include + +int c = 17; + +int soma10(int num) { + int b = 10; + return b + num; +} + +int main(void) { + + int a = 25; + printf("a = %d\nb = %d\nc = %d\nnum = %d\n", a, b, c, num); + + return 0; +} diff --git a/aulas/04-variaveis/var4 b/aulas/04-variaveis/var4 new file mode 100755 index 0000000..2741e84 Binary files /dev/null and b/aulas/04-variaveis/var4 differ diff --git a/aulas/04-variaveis/var4.c b/aulas/04-variaveis/var4.c new file mode 100644 index 0000000..6b55c12 --- /dev/null +++ b/aulas/04-variaveis/var4.c @@ -0,0 +1,16 @@ +#include + +int main(void) { + + int a = 25; + int b = 17; + int *p = &b; + + printf("Endereço (b) : %p\n", p); + printf("Valor : %d\n", *p); + printf("Endereço (a) : %p\n", &a); + printf("Endereço (p + 1): %p\n", p + 1); + printf("Valor : %d\n", *(p + 1)); + + return 0; +} diff --git a/exercicios/04/README.org b/exercicios/04/README.org new file mode 100644 index 0000000..4e14fb7 --- /dev/null +++ b/exercicios/04/README.org @@ -0,0 +1,98 @@ +#+title: Curso Básico da Linguagem C +#+subtitle: Exercícios +#+author: Blau Araujo +#+startup: show2levels +#+options: toc:3 + +* Exercícios da aula 4: Variáveis e ponteiros + +- [[../aulas/04-variaveis/README.org][Anotações da aula]] +- [[https://youtu.be/i7RKtMgSSrM][Vídeo]] + + +** 1. Pesquise e responda + +- Existe uma forma para alterar uma variável em uma função a partir de + outra função: como fazer isso? +- Por que as variáveis de uma função, em princípio, são locais à própria + função? +- Se o valor associado a um ponteiro é um endereço, o que termos com a + avaliação de =&NOME_DO_PONTEIRO=? + +** 2. Analise o código, pesquise e responda + +Este é mais um "Olá, mundo": + +#+begin_src c +#include + +char *msg = "Salve, simpatia!"; + +int main(void) { + puts(msg); + return 0; +} +#+end_src + +Se ponteiros recebem endereços como valores, por que eu fiz a atribuição +de uma string e o meu programa funcionou? + +** 3. Compile, analise e demonstre + +#+begin_src c +#include + +int main(void) { + + int a = 0; + int b = 875569217; + int c = 1280655661; + int d = 1129071171; + + char *p = (char *)&d; + + int i = 0; + while (*(p + i) != '\0') { + putchar(*(p + i)); + i++; + } + putchar('\n'); + + // printf("%p\n%p\n%p\n%p\n", &d, &c, &b, &a); + + return 0; +} +#+end_src + +- Como o código funciona? +- O que estou tentando imprimir? +- Com o =printf= comentado, eu consigo imprimir o que eu quero? +- Se eu tirar o comentário, eu tenho a impressão que eu quero? +- Por que acontece essa diferença de comportamento? +- Como imprimir corretamente apenas o que eu quero? + +** 4. Pesquise e responda + +- Para que serve e como usar a função =putchar=? +- Quando e por que utilizar =putchar('\n')= em vez de =puts("")=? +- Como funciona a estrutura de repetição =while=? +- Para que servem os especificadores de formato =%zu= e =%p=? + +** 5. Desafio: quadrado de um número + +Crie um programa que peça a digitação de um número inteiro ao usuário +e imprima o quadrado desse número. + +*Requisitos:* + +- O programa deve testar se o número digitado é um inteiro válido. +- Se não for, o programa deve terminar com uma mensagem enviada para + a saída padrão de erros (=stderr=) e estado de término =1=. + +*Dicas:* + +- Utilize a função =scanf= para ler o número digitado. +- Existe uma variante do =printf= que possibilita imprimir na saída de erros + em vez da saída padrão. +- Existe um cabeçalho que define constante com os limites dos tipos inteiros. +