mirror of
https://gitlab.com/blau_araujo/cblc.git
synced 2025-05-09 10:16:35 -03:00
conteúdo da aula 4
This commit is contained in:
parent
5aafa298ee
commit
09d21771ce
17 changed files with 357 additions and 318 deletions
|
@ -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.
|
BIN
aulas/04-variaveis/a.out
Executable file
BIN
aulas/04-variaveis/a.out
Executable file
Binary file not shown.
17
aulas/04-variaveis/teste.c
Normal file
17
aulas/04-variaveis/teste.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <stdio.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
BIN
aulas/04-variaveis/var1
Executable file
BIN
aulas/04-variaveis/var1
Executable file
Binary file not shown.
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;
|
||||
}
|
BIN
aulas/04-variaveis/var2
Executable file
BIN
aulas/04-variaveis/var2
Executable file
Binary file not shown.
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;
|
||||
}
|
BIN
aulas/04-variaveis/var4
Executable file
BIN
aulas/04-variaveis/var4
Executable file
Binary file not shown.
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