diff --git a/aulas/06-vetores/01-media.c b/aulas/06-vetores/01-media.c new file mode 100644 index 0000000..d00af3d --- /dev/null +++ b/aulas/06-vetores/01-media.c @@ -0,0 +1,23 @@ +#include + +int main(void) { + + float nota1 = 7.5; + float nota2 = 9.0; + float nota3 = 8.3; + float nota4 = 8.6; + + float soma = nota1 + nota2 + nota3 + nota4; + float media = soma / 4; + + puts("============"); + printf("Nota 1: %.1f\n", nota1); + printf("Nota 2: %.1f\n", nota2); + printf("Nota 3: %.1f\n", nota3); + printf("Nota 4: %.1f\n", nota4); + puts("------------"); + printf("Média : %.1f\n", media); + puts("============"); + + return 0; +} diff --git a/aulas/06-vetores/02-addr.c b/aulas/06-vetores/02-addr.c new file mode 100644 index 0000000..40aa440 --- /dev/null +++ b/aulas/06-vetores/02-addr.c @@ -0,0 +1,16 @@ +#include + +int main(void) { + + int v[] = {1, 2, 3, 4}; // 4 elementos x 4 bytes = 16 bytes + long a = 10; // 1 valor de 8 bytes + + puts("Saltos pelo tipo dos elementos (4 bytes)..."); + for (int i = 0; i < 4; i++) printf("%p\n", v + i); + puts("Saltos pelo tamanho total do vetor (4 x 4 bytes)..."); + for (int i = 0; i < 4; i++) printf("%p\n", &v + i); + puts("Equivale ao comportamento de variáveis (8 bytes)..."); + for (int i = 0; i < 4; i++) printf("%p\n", &a + i); + + return 0; +} diff --git a/aulas/06-vetores/03-itera.c b/aulas/06-vetores/03-itera.c new file mode 100644 index 0000000..8f1b2e9 --- /dev/null +++ b/aulas/06-vetores/03-itera.c @@ -0,0 +1,21 @@ +#include + +int main() { + + int v[5]; + + v[0] = 23; + v[4] = 19; + + puts("Conteúdo antes da definição dos elementos..."); + for (int i = 0; i < 5; i ++) + printf("v[%d]: %d\n", i, v[i]); + + puts("Conteúdo depois da definição dos elementos..."); + for (int i = 0; i < 5; i ++) { + v[i] = i + 1; + printf("v[%d]: %d\n", i, v[i]); + } + + return 0; +} diff --git a/aulas/06-vetores/04-func.c b/aulas/06-vetores/04-func.c new file mode 100644 index 0000000..303e6aa --- /dev/null +++ b/aulas/06-vetores/04-func.c @@ -0,0 +1,18 @@ +#include + + +void print_array(int v[], int size) { + for (int i = 0; i < size; i++) + printf("v[%d] => %d\n", i, v[i]); +} + +int main() { + int vetor[5]; + + for (int i = 0; i < 5; i ++) vetor[i] = i + 1; + + print_array(vetor, 5); + + return 0; +} + diff --git a/aulas/06-vetores/05-pointer.c b/aulas/06-vetores/05-pointer.c new file mode 100644 index 0000000..cffbf79 --- /dev/null +++ b/aulas/06-vetores/05-pointer.c @@ -0,0 +1,14 @@ +#include + +int main() { + + int v[4] = {0}; + int *p = v; + + printf("vetor : %p\n", v); + printf("&vetor : %p\n", &v); + printf("ponteiro : %p\n", p); + printf("&ponteiro: %p\n", &p); + + return 0; +} diff --git a/aulas/06-vetores/06-string.c b/aulas/06-vetores/06-string.c new file mode 100644 index 0000000..97c7247 --- /dev/null +++ b/aulas/06-vetores/06-string.c @@ -0,0 +1,13 @@ +#include + +int main() { + + char vstr[] = {'b', 'a', 'c', 'a', 'n', 'a', '\0'}; + // char vstr[] = "bacana"; + char *pstr = "calado"; + + printf("%p --> %s\n", vstr, vstr); + printf("%p --> %s\n", pstr, pstr); + + return 0; +} diff --git a/aulas/06-vetores/README.org b/aulas/06-vetores/README.org new file mode 100644 index 0000000..ee4f1b0 --- /dev/null +++ b/aulas/06-vetores/README.org @@ -0,0 +1,445 @@ +#+title: Curso Básico da Linguagem C +#+subtitle: Aula 6: Vetores +#+author: Blau Araujo +#+startup: show2levels +#+options: toc:3 + +* Aula 6: Vetores + +[[][Vídeo desta aula]] + +Vetores, são estruturas de dados compostas por valores do mesmo tipo, o que +possibilita agrupar coleções de dados relacionados entre si em um mesmo +elemento da linguagem. + +- Cada valor em um vetor é um /elemento/. +- O nome do vetor é o endereço do seu primeiro elemento. +- Cada elemento é identificado por um índice escrito entra colchetes após o nome. +- A representação do índice, incluindo os colchetes, é chamada de /subscrito/. + +#+begin_quote +Embora os termos sejam intercambiáveis, um /vetor/ é, mais precisamente, uma +/matriz unidimensional/, ou seja, uma lista de valores. Já o termo /matriz/ e +mais utilizado quando nos referimos a um vetor cujos elementos são outros +vetores, o que forma uma /matriz multidimensional/. Finalmente, /array/ é só +o termo em inglês para /matriz/. +#+end_quote + +** Utilidade dos vetores + +Quando estamos lidando com conjuntos de dados relacionados, é preferível +trabalhar com vetores em vez de variáveis, porque isso possibilita uma +melhor organização e estruturação dos dados, além de facilitar a sua +manipulação. + +*Exemplo:* + +#+begin_src c +float nota1 = 7.5; +float nota2 = 9.0; +float nota3 = 8.3; +float nota4 = 8.6; + +float soma = nota1 + nota2 + nota3 + nota4; +float media = soma / 4; +#+end_src + +*Com vetores...* + +#+begin_src c +float notas[4]; + +notas[0] = 7.5; +notas[1] = 9.0; +notas[2] = 8.3; +notas[3] = 8.6; + +float soma = 0; +for (int i = 0; i < 4; i++) soma += notas[i]; + +float media = soma / 4; +#+end_src + +Nós também poderíamos inicializar o vetor na declaração: + +#+begin_src c +float notas[4] = {7.5, 9.0, 8.3, 8.6}; + +float soma = 0; +for (int i = 0; i < 4; i++) soma += notas[i]; + +float media = soma / 4; +#+end_src + +As diferenças entre os exemplos com vetores e com variáveis separadas são... + +*Semanticamente:* + +- Uma variável expressa um valor na memória. +- Um vetor expressa o endereço do início de uma cadeia de valores (elementos). + +*Sintaticamente:* + +- Para obter o valor associado à variável, nós avaliamos o nome da variável. +- Para obter o valor do primeiro elemento de um vetor, nós derreferenciamos o + nome do vetor (=*=). +- Para obter o endereço do valor associado a uma variável, nós usamos =&=. +- Para obter o endereço do primeiro elemento do vetor, nós avaliamos o nome do + vetor. +- A operação =&= avalia o endereço de todo o conjunto de elementos do + vetor. +- Para obter os valores dos demais elementos, nós somamos =*( + índice)=. +- A linguagem C implementa uma notação mais semântica para acesso aos valores + dos elementos na forma de um /subscrito/ para a escrita de um índice após + o nome do vetor (=[índice]=). + +*Pragmaticamente:* + +Como os índices são numéricos, nós podemos iterar os elementos através +de operações com expressões que avaliem valores compatíveis com o tipo =int=. +Isso facilita muito o acesso e a manipulação de valores, especialmente em +situações onde temos que lidar com grandes quantidades de dados. + +#+begin_quote +Quantidade de dados não é, necessariamente, a mesma coisa que /volume de dados/! +#+end_quote + +** Declaração e inicialização de vetores + +Para declarar um vetor, nós definimos os tipos dos seus elementos, seu nome +e um /subscrito/ (um par de colchetes) contendo a quantidade de elementos que +o vetor receberá: + +#+begin_src c +float notas[4]; +#+end_src + +Enquanto não forem definidos, os valores nos elementos será o que houver na +quantidade de bytes reservada a partir do endereço do vetor (/lixo de memória/). + +Nós também podemos inicializar um vetor na sua declaração: + +#+begin_src c +float notas[4] = {7.5, 9.0, 8.3, 8.6}; +#+end_src + +*** Tamanho de um vetor em tempo de compilação + +Quando um vetor é inicializado sem a definição da quantidade de elementos +em seu subscrito, o compilador obtém essa quantidade da lista de valores: + +#+begin_src c +float notas[] = {7.5, 9.0, 8.3, 8.6}; // 4 elementos... +#+end_src + +*Importante!* + +Isso só vale para a inicialização de vetores nas suas declarações: + +#+begin_src c +float notas[]; // Errado! +#+end_src + +*** Inicialização parcial + +Quando a quantidade de elementos é definida na inicialização do vetor, +a ausência de valores correspondentes na lista de elementos é preenchida +com zeros: + +#+begin_src c +int num[5] = {1, 2}; // => {1, 2, 0, 0, 0} +#+end_src + +O que possibilita, inclusive, inicializar um vetor /zerado/: + +#+begin_src c +int num[5] = {0}; // => {0, 0, 0, 0, 0} +#+end_src + +** Acesso aos elementos de um vetor + +O acesso para manipulação dos elementos de um vetor é feita através +de seus índices, levando em conta que o primeiro elemento sempre +terá índice =0=: + +#+begin_src c +int num_list[5]; +/* ... */ +num_list[0] = 23; // Definição do valor do elemento 0 (primeiro elemento). +/* ... */ +num_list[4] = 19; // Alteração do elemento 4 (último elemento). +#+end_src + +** Iteração pelos elementos + +Nós podemos iterar os elementos de um vetor em /loops/: + +#+begin_src c +#include + +int main() { + + int v[5] = {0}; + + v[0] = 23; + v[4] = 19; + + for (int i = 0; i < 5; i ++) + printf("v[%d]: %d\n", i, v[i]); + + putchar('\n'); + + for (int i = 0; i < 5; i ++) { + v[i] = i + 1; + printf("v[%d]: %d\n", i, v[i]); + } + + return 0; +} +#+end_src + +*Compilando e executando...* + +#+begin_example +:~$ testes.c +:~$ a.out +v[0]: 23 +v[1]: 0 +v[2]: 0 +v[3]: 0 +v[4]: 19 + +v[0]: 1 +v[1]: 2 +v[2]: 3 +v[3]: 4 +v[4]: 5 +#+end_example + +** Vetores e funções + +Vetores não podem ser passados para funções, mas os seus endereços podem: + +#+begin_src c +#include + +void print_array(int v[], int size) { + for (int i = 0; i < size; i++) + printf("v[%d] => %d\n", i, v[i]); +} + +int main() { + + int vetor[5]; + + for (int i = 0; i < 5; i ++) vetor[i] = i + 1; + + print_array(vetor, 5); +} +#+end_src + +*Compilando e executando...* + +#+begin_example +:~$ testes.c +:~$ a.out +v[0] => 1 +v[1] => 2 +v[2] => 3 +v[3] => 4 +v[4] => 5 +#+end_example + +#+begin_quote +*Importante!* É preciso ter cuidado com a quantidade de elementos de um vetor, +pois o =gcc= não emite avisos nem erros quando tentamos ler ou escrever dados +fora dos limites do vetor, o que pode causar muitos problemas. +#+end_quote + +Ainda no exemplo, observe que a função =print_array= tem o parâmetro =int v[]=, que +se parece com a notação de um vetor, mas esta é só uma sintaxe utilizada na +linguagem C para representar que o argumento esperado é o *endereço* de um valor +de determinado tipo (no caso, =int=) e que esse endereço, semanticamente, será o +de um vetor. + +Essa sintaxe tem exatamente o mesmo efeito de um ponteiro como parâmetro: + +#+begin_src c +void print_array(int *v, int size) { + for (int i = 0; i < size; i++) + printf("v[%d] => %d\n", i, v[i]); // Notação de vetor... +} +#+end_src + +Isso também vale para todas as expressões que utilizem a notação de vetores, +porque =nome[índice]= é uma representação semântica que será interpretada como +=*(nome + índice)= pelo compilador. + +Logo, a função também poderia ser escrita assim: + +#+begin_src c +void print_array(int *v, int size) { + for (int i = 0; i < size; i++) + printf("v[%d] => %d\n", i, *(v + i)); // Notação de ponteiro... +} +#+end_src + +*** Relação entre vetores e ponteiros + +Um equívoco muito comum, é a ideia de que vetores /"decairiam"/ para +ponteiros. Isso simplesmente não é verdade: ponteiros são *variáveis* que +recebem endereços como valores, enquanto vetores são, e sempre serão, +endereços, onde quer que seus nomes sejam utilizados. + +#+begin_src c +#include + +int main() { + + int v[4] = {0}; + int *p = v; + + printf("vetor : %p\n", v); + printf("&vetor : %p\n", &v); + printf("ponteiro : %p\n", p); + printf("&ponteiro: %p\n", &p); + + return 0; +} +#+end_src + +*Compilando e executando...* + +#+begin_example +:~$ gcc testes.c +:~$ ./a.out +vetor : 0x7fff16832290 <-- Endereço de v[0] +&vetor : 0x7fff16832290 <-- Endereço expresso por v +ponteiro : 0x7fff16832290 <-- Valor no ponteiro (endereço expresso por v) +&ponteiro: 0x7fff16832288 <-- Endereço do valor no ponteiro +#+end_example + +Nos parâmetros de funções, =TIPO nome[]= é uma sintaxe alternativa para +=TIPO *nome= e ambas representam a declaração de um ponteiro que receberá, +como sempre, um endereço, não uma cópia de um vetor. + +Do mesmo modo, o uso da notação de vetores em expressões é apenas uma +sintaxe alternativa para operações realizadas com endereços: é por isso +que =vetor[i]= é o mesmo que =*(vetor + i)=. + +** Vetores de caracteres (strings) + +Strings são vetores de caracteres (cadeias de bytes) terminados com o +caractere nulo (=\0=). + +Portanto... + +#+begin_src c +char string[] = "banana"; +#+end_src + +É o mesmo que... + +#+begin_src c +char string[] = {'b', 'a', 'n', 'a', 'n', 'a', '\0}; +#+end_src + +*** Mais diferenças entre vetores e ponteiros + +Observe o exemplo... + +#+begin_src c +#include + +int main() { + + char vstr[] = "bacana"; + char *pstr = "calado"; + + printf("%p --> %s\n", vstr, vstr); + printf("%p --> %s\n", pstr, pstr); + + return 0; +} +#+end_src + +Essas strings são diferentes: + +#+begin_src c +char vstr[] = "bacana"; +char *pstr = "calado"; +#+end_src + +Enquanto o vetor =vstr= recebeu uma lista de caracteres, o ponteiro =pstr= +recebeu o endereço para um dado numa região do espaço de memória, +atribuído pelo sistema operacional ao programa, chamada de =.rodata=, +onde são armazenados os dados que não podem ser alterados (/read only/). + +Isso pode ser verificado despejando o conteúdo do binário do programa +com o utilitário =readelf=: + +#+begin_example +:~$ readelf -x .rodata a.out + +Despejo máximo da secção ".rodata": + 0x00002000 01000200 63616c61 646f0025 70202d2d ....calado.%p -- + 0x00002010 3e202573 0a00 > %s.. +#+end_example + +Veja que apenas a string =calado= aparece nesta seção do binário. + +A string =bacana=, por sua vez poderá ser encontrada na seção =.text= +do binário, que é onde nós encontramos o código da função =main=: + +#+begin_example +:~$ readelf -x .text a.out + +Despejo máximo da secção ".text": + 0x00001050 31ed4989 d15e4889 e24883e4 f0505445 1.I..^H..H...PTE + 0x00001060 31c031c9 488d3dce 000000ff 154f2f00 1.1.H.=......O/. + 0x00001070 00f4662e 0f1f8400 00000000 0f1f4000 ..f...........@. + 0x00001080 488d3d91 2f000048 8d058a2f 00004839 H.=./..H.../..H9 + 0x00001090 f8741548 8b052e2f 00004885 c07409ff .t.H.../..H..t.. + 0x000010a0 e00f1f80 00000000 c30f1f80 00000000 ................ + 0x000010b0 488d3d61 2f000048 8d355a2f 00004829 H.=a/..H.5Z/..H) + 0x000010c0 fe4889f0 48c1ee3f 48c1f803 4801c648 .H..H..?H...H..H + 0x000010d0 d1fe7414 488b05fd 2e000048 85c07408 ..t.H......H..t. + 0x000010e0 ffe0660f 1f440000 c30f1f80 00000000 ..f..D.......... + 0x000010f0 f30f1efa 803d1d2f 00000075 2b554883 .....=./...u+UH. + 0x00001100 3dda2e00 00004889 e5740c48 8b3dfe2e =.....H..t.H.=.. + 0x00001110 0000e829 ffffffe8 64ffffff c605f52e ...)....d....... + 0x00001120 0000015d c30f1f00 c30f1f80 00000000 ...]............ + 0x00001130 f30f1efa e977ffff ff554889 e54883ec .....w...UH..H.. + 0x00001140 10c745f1 62616361 c745f461 6e610048 ..E.baca.E.ana.H <-- Aqui! + 0x00001150 8d05ae0e 00004889 45f8488d 55f1488d ......H.E.H.U.H. + 0x00001160 45f14889 c6488d05 9f0e0000 4889c7b8 E.H..H......H... + 0x00001170 00000000 e8b7feff ff488b55 f8488b45 .........H.U.H.E + 0x00001180 f84889c6 488d0580 0e000048 89c7b800 .H..H......H.... + 0x00001190 000000e8 98feffff b8000000 00c9c3 ............... +#+end_example + +#+begin_quote +As seções do binário e os segmentos do layout de memória serão assunto +de uma aula futura. +#+end_quote + +As consequências disso, são: + +- Os caracteres de =calado= não poderão ser alterados por aritmética + de ponteiros. +- A tentativa de alterar dados em =.rodata= não resultará em erros + de compilação, mas causará /falha de segmentação/ (escrita em + segmentos não autorizados da memória). +- Enquanto o endereço em =pstr= será de um dado em =.rodata=, o endereço + de =vstr[0]= estará na /pilha/ do espaço de memória, onde alterações + são permitidas. + +A compilação e execução do exemplo deixa isso bem claro: + +#+begin_example +:~$ gcc testes.c +:~$ a.out +0x7ffc25b8c0b1 --> bacana <-- endereço na pilha +0x558add3cc004 --> calado <-- endereço em .rodata +#+end_example +