Aprenda ponteiros em C por meio de visualização de memória. Endereços e desreferenciação explicados com diagramas.
&x pega o endereço, *p desreferenciaint *p = &x;& (pegar o endereço) com * (desreferenciar). Em uma declaração, * marca "tipo ponteiro". Em uma expressão, *p significa "o que p aponta". Fixe essa distinção.
int ocupa 4 bytes, a próxima variável cai 4 endereços depois. A memória é alocada de forma contígua, com o tamanho determinado por cada tipo de variável.
int tem 4 bytes. Os tamanhos dos tipos são definidos pela implementação, então verifique com sizeof(int) no seu próprio sistema. Endereços reais também variam entre sistemas operacionais e entre execuções, e as variáveis nem sempre são organizadas de forma tão certinha.
& para pegar um endereço: escrever &n te dá o endereço da variável n (algo como 0x1000). Você até consegue imprimir com printf("%p\n", &n);.
n dentro da caixa.tipo *nome;, em que o tipo é o tipo da coisa apontada. Então int *p significa "p é um ponteiro para uma variável int".& que você vem usando em scanf.n e o ponteiro p se relacionam.p vai alternar entre apontar para n e apontar para m.p = &n; e p = &m; redirecionam a seta para um alvo diferente.*p = valor; reescreve o que estiver na outra ponta da seta.p em si guarda um endereço (0x1000 ou 0x1004) — você precisa de * para chegar ao valor real.& no scanfscanf("%d", &a); o tempo todo. Esse & é exatamente o mesmo operador — ele está passando um endereço.& é necessário aqui?scanf precisa saber onde escrever o valor de entrada, então você passa o endereço da variável.scanf usa um ponteiro para escrever o valor de entrada naquele endereço.
& e escrever scanf("%d", a);, o scanf vai tratar o conteúdo (indeterminado) de a como um endereço e escrever em algum local aleatório da memória — uma forma garantida de quebrar o seu programa.
void swap(int a, int b){ ... } não afeta as variáveis de quem chamou — apenas as cópias locais dentro da função são trocadas.
int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; // points to arr[0] printf("%d\n", *p); // 10 (arr[0]) printf("%d\n", *(p + 1)); // 20 (arr[1]) printf("%d\n", *(p + 3)); // 40 (arr[3]) p += 2; // p now points to arr[2] printf("%d\n", *p); // 30
p + 1 significa "o próximo elemento", não "o próximo byte".int (4 bytes), p + 1 avança o endereço em 4 bytes.| Notação de array | Notação de ponteiro | Significado |
|---|---|---|
arr[0] | *arr | Primeiro elemento |
arr[i] | *(arr + i) | i-ésimo elemento |
&arr[i] | arr + i | Endereço do i-ésimo elemento |
arr[i] e *(arr+i) referem-se exatamente ao mesmo elemento. Arraste o controle e veja as duas notações acendendo a mesma célula.arr[2]*(arr + 2)arr[i] é só açúcar sintático para *(arr + i). Engraçado que isso significa que i[arr] (!) também compila — é *(i + arr) = *(arr + i). Não escreva isso de verdade, mas mostra bem que acesso a array é, na raiz, aritmética de ponteiros.int arr[5] = {10, 20, 30, 40, 50}; int *p1 = &arr[1]; int *p2 = &arr[4]; printf("%ld\n", p2 - p1); // 3 (difference in number of elements)
const avisa ao compilador e aos colegas desenvolvedores que uma variável não vai ser modificada. Isso evita bugs e torna sua intenção explícita.const int MAX = 100; printf("%d\n", MAX); // OK: read freely MAX = 200; // ❌ Compile error: cannot modify const
#define é que você ganha checagem de tipo.const é notoriamente confuso. O significado depende de qual lado do asterisco o const está.| Declaração | O que pode mudar | Significado |
|---|---|---|
const int *pou int const *p |
✅ p pode mudar❌ *p não pode mudar |
Um ponteiro para um valor constante. |
int * const p |
❌ p não pode mudar✅ *p pode mudar |
Um ponteiro constante — não pode apontar para outro lugar. |
const int * const p |
❌ p não pode mudar❌ *p não pode mudar |
Totalmente travado — nada pode mudar. |
int a = 10, b = 20; // ① const int *p : pointed-to value is read-only const int *p1 = &a; p1 = &b; // ✅ OK: pointer itself can change *p1 = 30; // ❌ Error: pointed-to value is immutable // ② int * const p : pointer itself is read-only int * const p2 = &a; *p2 = 30; // ✅ OK: can modify 'a' through p2 p2 = &b; // ❌ Error: p2 can't be redirected // ③ const int * const p : everything locked const int * const p3 = &a; *p3 = 30; // ❌ Error p3 = &b; // ❌ Error
const int *p = "p é um ponteiro para um int const"int * const p = "p é um ponteiro const para int"const para deixar essa intenção clara.// Just reads the string → mark as const int my_strlen(const char *s) { int n = 0; while (*s) { s++; n++; } return n; } // Sums array elements, doesn't modify them → const int sum(const int arr[], int n) { int total = 0; for (int i = 0; i < n; i++) total += arr[i]; return total; }
const quando a função não for modificá-los.strlen e strcmp seguem essa convenção.char *s1 = "Hello"; // ⚠️ Discouraged (some compilers warn) s1[0] = 'J'; // ❌ Undefined behavior! const char *s2 = "Hello"; // ✅ Correct way // s2[0] = 'J'; ← caught at compile time char s3[] = "Hello"; // ✅ Array: modification OK s3[0] = 'J'; // → "Jello"
"Hello") vivem em memória somente leitura. Sempre receba-os por meio de const char *.const funciona como uma barreira de prevenção contra bugs. Seu código ainda roda sem ele, mas usá-lo pega erros em tempo de compilação e deixa sua intenção cristalina.if (p != NULL)p + 1*(&x)*p = 99; reescreve o valor de n. Isso é o que significa alterar um valor indiretamente, por meio de um ponteiro.R. Ponteiros permitem fazer coisas que simplesmente são impossíveis de outra forma: 1. modificar uma variável de quem chama de dentro de uma função (passagem por referência), 2. alocar memória dinamicamente, 3. construir estruturas de dados complexas como listas e árvores, e 4. trabalhar com strings. Muitos recursos de C não funcionam sem eles.
& (endereço-de) te dá o endereço — "onde a variável mora". * (desreferência) vai até esse endereço e olha "o que está guardado lá". Em uma declaração de ponteiro como int *p;, o * só significa "p é um ponteiro para int".
R. Ajuda distinguir três estados:
① Ponteiro NULL — um ponteiro intencionalmente definido como NULL (o valor 0), significando "não aponta para nada". Desreferenciá-lo quebra de forma confiável, o que o torna uma sentinela segura e detectável.
② Ponteiro não inicializado — declarado mas nunca atribuído. Seu valor é indeterminado (pode estar em qualquer lugar da memória), e desreferenciá-lo é comportamento indefinido.
③ Ponteiro pendurado (dangling) — um ponteiro que um dia se referia a um objeto válido, mas esse objeto não é mais válido (foi liberado, saiu do escopo, etc.). Os bits ainda podem parecer válidos, mas qualquer acesso é comportamento indefinido.
Bons hábitos defensivos: inicialize ponteiros na declaração (int *p = NULL;), defina p = NULL; logo após free(p) e nunca retorne o endereço de uma variável local.
R. Um ponteiro também é só uma variável, então a própria variável ponteiro vive na memória — o que significa que você pode fazer "um ponteiro para um ponteiro": int **pp = &p;. Ponteiros de múltiplos níveis parecem complicados, mas você só está aplicando o mesmo princípio repetidamente. Na prática, raramente são necessários.
Confira sua compreensão desta aula!
int *p;, o que p guarda?int *p é um ponteiro para int, então guarda o endereço de memória de uma variável int.
*p desreferencia o ponteiro. Como p aponta para x, *p é 10.
Desreferenciar um ponteiro NULL causa um erro de execução como uma falha de segmentação. Sempre verifique se é NULL antes de usar um ponteiro.