mirror of
https://gitlab.com/blau_araujo/cblc.git
synced 2025-05-10 18:46:36 -03:00
Compare commits
3 commits
5aafa298ee
...
73c09b86d2
Author | SHA1 | Date | |
---|---|---|---|
73c09b86d2 | |||
0d609f3242 | |||
09d21771ce |
13 changed files with 341 additions and 318 deletions
|
@ -23,3 +23,4 @@ qualquer distribuição.
|
||||||
- 12.03.2025 [[./aulas/01-historia/README.org][Aula 1: História]] ([[https://youtu.be/wqJQL5W9FIw][vídeo]]) ([[./exercicios/01/README.org][exercícios]])
|
- 12.03.2025 [[./aulas/01-historia/README.org][Aula 1: História]] ([[https://youtu.be/wqJQL5W9FIw][vídeo]]) ([[./exercicios/01/README.org][exercícios]])
|
||||||
- 14.03.2025 [[./aulas/02-dados-e-instrucoes/README.org][Aula 2: Dados e instruções]] ([[https://youtu.be/2KsvRJjshQ0][vídeo]]) ([[./exercicios/02/README.org][exercícios]])
|
- 14.03.2025 [[./aulas/02-dados-e-instrucoes/README.org][Aula 2: Dados e instruções]] ([[https://youtu.be/2KsvRJjshQ0][vídeo]]) ([[./exercicios/02/README.org][exercícios]])
|
||||||
- 17.03.2025 [[./aulas/03-tipos-de-dados/README.org][Aula 3: Tipos de dados]] ([[https://youtu.be/iMiRzZCU7hE][vídeo]]) ([[./exercicios/03/README.org][exercícios]])
|
- 17.03.2025 [[./aulas/03-tipos-de-dados/README.org][Aula 3: Tipos de dados]] ([[https://youtu.be/iMiRzZCU7hE][vídeo]]) ([[./exercicios/03/README.org][exercícios]])
|
||||||
|
- 19.03.2025 [[./aulas/04-variaveis/README.org][Aula 4: Variaveis e ponteiros]] ([[https://youtu.be/i7RKtMgSSrM][vídeo]]) ([[./exercicios/04/README.org][exercícios]])
|
||||||
|
|
|
@ -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 <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 357 KiB |
Binary file not shown.
Before Width: | Height: | Size: 101 KiB |
|
@ -1,67 +0,0 @@
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
int a = 17, b = 25;
|
|
||||||
|
|
||||||
printf("a: %d @ %p\n", a, &a);
|
|
||||||
printf("b: %d @ %p\n", b, &b);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
181
aulas/04-variaveis/README.org
Normal file
181
aulas/04-variaveis/README.org
Normal file
|
@ -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 <stdio.h>
|
||||||
|
|
||||||
|
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 <stdio.h>
|
||||||
|
|
||||||
|
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.
|
14
aulas/04-variaveis/var1.c
Normal file
14
aulas/04-variaveis/var1.c
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
15
aulas/04-variaveis/var2.c
Normal file
15
aulas/04-variaveis/var2.c
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
16
aulas/04-variaveis/var3.c
Normal file
16
aulas/04-variaveis/var3.c
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
16
aulas/04-variaveis/var4.c
Normal file
16
aulas/04-variaveis/var4.c
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
98
exercicios/04/README.org
Normal file
98
exercicios/04/README.org
Normal file
|
@ -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 <stdio.h>
|
||||||
|
|
||||||
|
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 <stdio.h>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue