Piada obrigatória do XKCD:
Cada variável em um programa C é armazenada em algum lugar na memória
A linguagem possui um operador para obter esses endereços: o operador &
Por exemplo, vamos exibir o endereço de uma variável:
int main()
{
int b = 200;
printf("%p\n", &b);
}
O modificador %p exibe o valor como um end. de memória em hexa
Definição: uma variável que armazena um endereço de memória (usualmente de outra variável)
Por exemplo: criando um ponteiro para a variável b (chamado a):
int main()
{
int b = 200;
int* a = &b;
}
O operador * é usado para declarar um ponteiro ou acessar os conteúdos de um endereço de memória:
int main()
{
int b = 200;
int* a = &b; // a armazenará o endereço de b
printf("Valor de b: %d\n", b);
printf("Valor de a: %p\n", a);
printf("Conteúdo apontado por a: %d", *a);
}
Quando usado à esquerda de uma variável, o operador * é chamado de operador de dereferência
Precisamos ter certeza de sempre usar ponteiros válidos
Um ponteiro válido é um ponteiro que aponta para uma área de memória válida (usualmente uma variável no programa)
Se esquecermos de atribuir um valor ao ponteiro e tentarmos usá-lo, coisas ruins podem acontecer!
int main()
{
int a = 200;
int* b;
printf("Conteúdo apontado por b: %d\n", *b); // oops!
}
Este código provavelmente irá gerar uma falha de segmentação no Linux
É considerada uma prática saudável armazenar o valor NULL em ponteiros não inicializados
No código, pode ser usado com segurança para testar se um ponteiro está inicializado ou não:
int main()
{
int a = 200;
int* b = NULL;
...
if(b != NULL)
printf("Conteúdo apontado por b: %d\n", *b); // b é válido
else
printf("Ponteiro inválido!\n");
}
Em C, um array é armazenado como um ponteiro para o seu primeiro elemento
Os demais elementos são armazenados contiguamente a partir do end. inicial
Por exemplo:
char c[10];
c é um ponteiro para uma área de memória capaz de armazenar 10 char
Pontanto, o tamanho da área de memória é 10 * sizeof(char) = 10 bytes
Uma consequência interessante dessa implementação é que os arrays são sempre passados por referência:
void incArray(int v[]) {
int c;
for(c=0; c<10; c++)
v[c]++;
}
int main() {
int data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
incArray(data);
}
Ou podemos escrever a função desta forma:
void incArray(int* v) {
...
Considerando o seguinte array de char:
char c[10] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' };
Podemos declarar um ponteiro p para ele:
char* p = c; // não precisa &, c já é um ponteiro
E agora p é o mesmo que c:
printf("%c\n", *p); // exibe o primeiro elemento do array
printf("%c\n", p[0]); // idem
printf("%p\n", p); // exibe o endereço do primeiro elemento
printf("%p\n", c); // idem
printf("%p\n", &c[0]); // idem
char* p = c;
printf("%c\n", *p); // exibe 'a'
printf("%c\n", *(p+1)); // exibe 'b'
printf("%c\n", *(p+2)); // exibe 'c'
printf("%c\n", *(p+3)); // exibe 'd'
...
Se o array armazena ints, o pŕóximo elemento estará a 4 bytes de distância
O tamanho da área de memória será 10 * sizeof(int) = 40 bytes
int m[] = { 3, 2, 1, 4, 5, 6, 9, 8, 7, 10 };
printf("%p\n", m); // end. do primeiro elem., e.g. 0x7fff5fbfebf8
printf("%p\n", &m[0]); // idem
printf("%p\n", m+1); // end. do segundo elem., e.g. 0x7fff5fbfebfc
printf("%p\n", &m[1]); // idem
#include <stdio.h>
int main()
{
int vet[] = { 4, 9, 12 };
int i, *ptr;
ptr = vet;
for(i =0; i<3; i++)
printf("%d ", *ptr++);
return 1;
}
#include <stdio.h>
int main()
{
int vet[] = { 4, 9, 12 };
int i, *ptr;
ptr = vet;
for(i =0; i<3; i++)
printf("%d ", (*ptr)++);
return 1;
}
Essa é uma técnica amplamente usada: é rápida e eficiente, se você souber o que está fazendo!
Um exemplo prático: a função strlen (calcula o comprimento de uma string null-terminated)
Esta é a implementação do FreeBSD 6.2 (não necessariamente a melhor ou mais eficiente)
size_t strlen(const char * str)
{
const char *s;
for (s = str; *s; ++s) {}
return s - str;
}
Você é capaz de explicar como ela funciona?
Um array de ponteiros pode ser usado, por exemplo, para armazenar uma lista de strings:
#include <stdio.h>
int main()
{
char* nomes[] = { "Tyrion", "Stannis", "Tywin", "Eddard" };
int i;
for(i=0; i<4; i++)
printf("%s\n", nomes[i]);
}
Observe que a linguagem irá automaticamente criar todos os ponteiros a partir das strings de inicialização
Para obter os parâmetros passados através da linha de comando, podemos escrever uma função main modificada, com dois parâmetros:
argv é um array de ponteiros para strings
// argumentos.c
int main(int argc, char* argv[])
{
printf("No. de argumentos: %d\n", argc);
int i;
for(i=0; i<argc; i++)
printf("Argumento %d: %s\n", i, argv[i]);
}
// Teste no shell:
./argumentos Esta é uma lista de parâmetros 1 2 3
Considere o seguinte código, que exibe o conteúdo do array junto ao endereço de memória de cada elemento:
int main()
{
int nums[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr = nums;
int i, bytes;
for(i=0, bytes=0; i<10; i++, bytes+=4)
printf("%d: %p + %d bytes = %p ==> %d\n",
i, ptr, bytes, (ptr+i), *(ptr+i));
return 0;
}
Você consegue exibir o conteúdo das seguintes varíaveis, usando apenas ponteiros para char (bypes)?
int main() {
char* text = "Prog. Sofware Basico";
int v[] = { 0, 1, 2, 3, 4 };
int matriz[4][4] = {
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15
};
}