# Memória, funções e objetos em C

Duração dos objetos, stack frames, propriedade da memória e apontadores para funções.

Página: https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/

Uma função pode receber o endereço de uma posição e alterar as suas coordenadas. Esse endereço só permite acesso enquanto o objeto existir. Se a função devolver o endereço de uma variável local, o chamador recebe um endereço cujo objeto já deixou de existir. Distinguir âmbito, duração e propriedade evita este erro e permite construir objetos que sobrevivem a várias chamadas.

## Organização da memória

Um processo tem um espaço de endereçamento virtual. Uma organização habitual separa código executável, dados globais e estáticos, memória de alocação dinâmica e pilhas das chamadas. Esta organização não fixa endereços universais nem uma posição obrigatória para cada variável: o compilador pode guardar valores em registos e eliminar objetos que não precisam de existir na memória.

| Objeto | Quando existe | Quem controla a duração |
| --- | --- | --- |
| Variável automática de um bloco | Da entrada no bloco até à saída | A execução do bloco |
| Global ou `static` | Durante a execução do programa | O programa e o ambiente |
| Memória de `malloc` | Da alocação bem-sucedida até `free` | O código que a possui |

**Âmbito** é onde podes usar um nome no código. **Duração** é durante quanto tempo o objeto existe. Uma variável `static` dentro de uma função tem âmbito local, mas conserva o valor entre chamadas.

## Passagem de parâmetros

C passa os argumentos por valor. Uma função que recebe um apontador recebe uma cópia do endereço; desreferenciar essa cópia permite alterar o objeto original.

```
#include <stdio.h>

static int by_value(int x) { x = 9; return x; }
static void by_pointer(int *x) { *x = 9; }

int main(void) {
  int n = 3;
  by_value(n);
  printf("%d\n", n);
  by_pointer(&n);
  printf("%d\n", n);
  return 0;
}
```

Segue os dois casos. Em `by_value(n)`, a função recebe 3, altera a sua cópia para 9 e devolve 9, mas o chamador ignora o retorno e `n` continua 3. Em `by_pointer(&n)`, a função recebe o endereço de `n`, escreve 9 através dele e o chamador lê 9 a seguir. O apontador também passa por valor: a função recebe uma cópia do endereço, mas essa cópia continua a designar o objeto do chamador.

A mesma distinção explica por que `p = NULL` dentro de uma função que recebe `int *p` não põe a variável do chamador a `NULL`. Para substituir o apontador do chamador, devolve o novo apontador ou recebe o endereço desse apontador, de tipo `int **`.

## Chamada, stack frame e retorno

Uma chamada guarda o endereço onde o chamador continuará depois do retorno. No modelo IA-32 habitual com argumentos na pilha, cada chamada cria um **stack frame**.

![Modelo de um stack frame IA-32: argumentos, endereço de retorno, frame pointer anterior e variáveis locais. A convenção de chamada determina a disposição concreta.](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/figura-1.svg)

O frame pointer pode servir de referência fixa para encontrar os argumentos e variáveis locais, enquanto o stack pointer acompanha a ocupação da pilha. O código chamado conserva os registos que a convenção exige e coloca o resultado no local definido por essa convenção. No retorno, desfaz o frame e continua no endereço guardado.

Este é um modelo da convenção de chamada, não uma exigência de C. Noutras arquiteturas ou com otimização, alguns argumentos vão em registos e o frame pointer pode não existir. Em recursão, cada chamada tem os seus próprios objetos automáticos: a variável local da chamada exterior não é a da chamada interior.

Nunca devolvas o endereço de uma variável automática:

```
/* Incorreto: o objeto local deixa de existir quando a função retorna. */
int *bad_result(void) {
  int result = 42;
  return &result;
}
```

Mesmo que um teste imprima 42, o acesso já não é válido, porque o objeto local deixou de existir. Para devolver um inteiro, devolve o inteiro. Para produzir vários resultados, usa uma `struct` por valor ou recebe um apontador para armazenamento que o chamador possui.

## Alocação e propriedade

`malloc` devolve armazenamento sem inicialização ou `NULL` se falhar. Calcula o tamanho a partir do objeto apontado, valida a falha, inicializa antes de ler e liberta exatamente uma vez. Depois de `free`, não leias nem escrevas o objeto. O valor dos apontadores que o designavam fica indeterminado; não os uses sequer para testar se a memória ainda existe. Podes atribuir `NULL` à variável do apontador sem ler o seu valor anterior, mas isso não altera outros aliases.

Este exemplo completo cria cinco inteiros e imprime `soma=150`, porque soma 10, 20, 30, 40 e 50 antes de libertar o vetor.

**main.c**

```c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  size_t n = 5;
  int *values = malloc(n * sizeof *values);
  if (values == NULL) return 1;
  int sum = 0;
  for (size_t i = 0; i < n; ++i) {
    values[i] = (int)(i + 1) * 10;
    sum += values[i];
  }
  printf("soma=%d\n", sum);
  free(values);
  values = NULL;
  return 0;
}
```

Para tamanhos recebidos de fora, confere também que `n * sizeof *values` não transborda `SIZE_MAX`. Um cast ao resultado de `malloc` é desnecessário em C quando incluis `<stdlib.h>`.

A **propriedade** responde a duas perguntas: quem pode usar o objeto e quem o deve libertar? Por exemplo, um sprite pode possuir o seu pixmap. Destruir o sprite liberta os dois. Se vários sprites partilham um pixmap, cada um libertá-lo causaria uma dupla libertação. Nesse caso, o módulo de recursos mantém o pixmap e os sprites apenas o usam.

Para aumentar uma alocação, `realloc` pode mover os dados para outro endereço. Guarda primeiro o resultado numa variável temporária. Se falhar para um tamanho não nulo, devolve `NULL` e a alocação anterior continua válida. Fazer `values = realloc(values, size)` perde o único endereço da alocação antiga se a chamada falhar. Se tiver sucesso, usa apenas o endereço devolvido; os apontadores anteriores, incluindo os que apontavam para elementos, deixam de ser válidos. Valida o produto do tamanho antes da chamada e trata o tamanho zero separadamente.

Num construtor com várias alocações, uma falha na segunda obriga a libertar a primeira. Não publiques um objeto parcialmente inicializado. A regra também vale para recursos de hardware: se a subscrição do rato falhar depois da do timer, remove a subscrição do timer antes de sair.

## Estruturas, unions e alinhamento

Uma `struct` reserva espaço para todos os campos. O compilador pode acrescentar bytes de alinhamento entre campos e no fim. Por isso, a soma dos tamanhos dos campos pode ser menor do que `sizeof(struct)`.

Uma `union` partilha armazenamento entre alternativas. Num valor com alternativas, guarda também uma etiqueta que indique qual delas está ativa:

```
enum event_kind { KEY_EVENT, MOVE_EVENT };
struct event {
  enum event_kind kind;
  union {
    unsigned key;
    struct { int x, y; } move;
  } data;
};
```

Se `kind == MOVE_EVENT`, interpreta `data.move`. Não uses `data.key` para descobrir automaticamente a alternativa. Esta estrutura chama-se **tagged union** e evita reservar os dados de todos os eventos ao mesmo tempo.

Uma estrutura de um protocolo ou da BIOS pode exigir uma disposição exata. Nesses casos, o apoio da cadeira usa estruturas empacotadas. Isso pode criar campos desalinhados e é dependente do compilador. Não envies uma `struct` C crua pela UART: padding, endianness e tamanhos podem mudar. Serializa cada campo num formato definido.

### Calcular o tamanho com alinhamento

Suponhamos uma ABI em que `char` tem tamanho e alinhamento 1, `short` tem tamanho e alinhamento 2, e `int` tem tamanho e alinhamento 4. São hipóteses explícitas do exemplo; dizer apenas «máquina de 32 bits» não fixa todas as regras de C.

```
struct separados { char texto[10]; int numero; short codigo; };
union alternativas { char texto[10]; int numero; short codigo; };
```

Na estrutura, o array ocupa os offsets 0 a 9. O inteiro precisa de começar num múltiplo de 4, por isso ficam dois bytes de padding e `numero` começa em 12. Depois dos seus quatro bytes, `codigo` começa em 16 e termina em 17. O alinhamento da estrutura é 4; o tamanho tem de permitir que o objeto seguinte num array comece também alinhado. Dois bytes finais levam o total a **20**.

| Campo ou espaço | Offset | Bytes |
| --- | --- | --- |
| texto | 0 | 10 |
| Padding antes de numero | 10 | 2 |
| numero | 12 | 4 |
| codigo | 16 | 2 |
| Padding final | 18 | 2 |

Na union, as alternativas começam no offset 0 e partilham armazenamento. A maior exige 10 bytes, mas o alinhamento é 4: arredondar o tamanho para um múltiplo de 4 dá **12**, não 10 nem a soma 16. Três unions num array ocupam 36 bytes; três estruturas ocupam 60.

Confere a ABI concreta com `sizeof`, `_Alignof` e `offsetof`, de `<stddef.h>`. Não uses este cálculo para serializar diretamente uma estrutura ou adivinhar o formato de um registo: o contrato externo pode exigir outra disposição.

## Filas e tipos genéricos

Uma fila conserva a ordem de chegada. Numa fila circular com capacidade $C$, o índice de entrada aponta para o próximo lugar onde escrever, o de saída aponta para o próximo elemento e a quantidade distingue cheio de vazio. Depois de avançar, usa índice módulo $C$.

Com capacidade 3, começa em `in = out = count = 0`. Enfileirar A e B coloca-os nas posições 0 e 1. Retirar A deixa `out = 1` e `count = 1`. Enfileirar C usa a posição 2 e faz `in` voltar a 0. Enfileirar D usa a posição 0. A fila está cheia e a ordem de saída é B, C, D, apesar de a ordem física no array ser D, B, C.

![Fila circular com três posições, numeradas de 0 a 2. Depois de inserir A e B: [A, B, livre], in = 2, out = 0, count = 2. Depois de retirar A e inserir C: [livre, B, C], in = 0, out = 1, count = 2. Depois de inserir D: [D, B, C], in = out = 1, count = 3. As saídas percorrem as posições 1, 2 e 0, devolvendo B, C e D.](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/figura-2.svg)

Segue a fila a partir de `out`, não do início do array. Quando `in = out`, é `count` que indica se está vazia ou cheia. Na figura, «livre» significa que a posição não pertence à fila; retirar um elemento não apaga os seus bytes.

Para guardar valores de diferentes tipos, conserva o tamanho de um elemento e copia bytes. Um `void *` não permite aritmética de apontadores no C padrão, porque `void` não tem tamanho. Usa um apontador para `unsigned char` na representação interna e calcula `index * element_size`.

Este programa completo guarda dois pares de coordenadas numa fila genérica de capacidade 3. A fila copia os valores, não conserva apontadores para as variáveis locais de quem insere, por isso imprime `2,3` e `5,8`.

```
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct queue {
  unsigned char *bytes;
  size_t capacity, element_size, in, out, count;
};

static struct queue *queue_new(size_t capacity, size_t element_size) {
  if (capacity == 0 || element_size == 0 || capacity > SIZE_MAX / element_size)
    return NULL;
  struct queue *q = malloc(sizeof *q);
  if (q == NULL) return NULL;
  q->bytes = malloc(capacity * element_size);
  if (q->bytes == NULL) { free(q); return NULL; }
  q->capacity = capacity;
  q->element_size = element_size;
  q->in = q->out = q->count = 0;
  return q;
}

static bool queue_put(struct queue *q, const void *value) {
  if (q->count == q->capacity) return false;
  memcpy(q->bytes + q->in * q->element_size, value, q->element_size);
  q->in = (q->in + 1) % q->capacity;
  ++q->count;
  return true;
}

static bool queue_get(struct queue *q, void *value) {
  if (q->count == 0) return false;
  memcpy(value, q->bytes + q->out * q->element_size, q->element_size);
  q->out = (q->out + 1) % q->capacity;
  --q->count;
  return true;
}

static void queue_delete(struct queue *q) {
  if (q == NULL) return;
  free(q->bytes);
  free(q);
}

int main(void) {
  struct point { int x, y; } a = {2, 3}, b = {5, 8}, value;
  struct queue *q = queue_new(3, sizeof a);
  if (q == NULL) return 1;
  if (!queue_put(q, &a) || !queue_put(q, &b)) {
    queue_delete(q);
    return 1;
  }
  while (queue_get(q, &value)) printf("%d,%d\n", value.x, value.y);
  queue_delete(q);
  return 0;
}
```

O chamador tem de fornecer objetos com pelo menos `element_size` bytes, porque a fila copia bytes sem conhecer o tipo. A cópia é superficial: se um elemento contiver um apontador, copia o endereço, não o objeto apontado. Define separadamente quem possui esse objeto.

Uma API de biblioteca pode ocultar `struct queue` no `.c` e expor apenas o tipo incompleto no `.h`. Uma fila com crescimento dinâmico exige ainda tratar falhas de realocação e reconstruir a ordem quando os elementos atravessam o fim do array. Um `realloc` bem-sucedido conserva bytes, mas não reorganiza sozinho uma fila circular. O exemplo usa capacidade fixa e devolve falha quando fica cheia.

## Apontadores para funções

`int (*operation)(int)` declara um apontador para uma função que recebe um `int` e devolve um `int`. Os parênteses são necessários: `int *operation(int)` declararia uma função que devolve um apontador.

```
#include <stdio.h>

static int twice(int n) { return 2 * n; }
static int increment(int n) { return n + 1; }

int main(void) {
  int (*operations[])(int) = {twice, increment};
  for (size_t i = 0; i < 2; ++i)
    printf("%d\n", operations[i](4));
  return 0;
}
```

A saída mostra `8` e `5`, porque a tabela seleciona `twice(4)` e depois `increment(4)`. Confere o índice antes de aceder e mantém todas as funções com a assinatura declarada. Converter apontadores incompatíveis não adapta os argumentos.

Um tipo opaco permite esconder os campos de um objeto: o cabeçalho declara `struct sprite;`, expõe operações com `struct sprite *` e o `.c` define os campos. O utilizador não precisa do tamanho interno para conservar o apontador. Precisa de conhecer o contrato de criação, uso e destruição.

## Argumentos variáveis

Uma função com `...` não recebe informação automática sobre o número e o tipo dos argumentos. `printf` usa a cadeia de formato; um construtor de animações pode receber uma contagem explícita. A leitura usa `va_list`, `va_start`, `va_arg` e `va_end` de `<stdarg.h>`.

Os argumentos variáveis sofrem promoções: `float` passa como `double` e tipos inteiros pequenos passam normalmente como `int`. Pedir `va_arg(args, float)` para um `float` recebido assim é errado. Quando não precisas de tipos variáveis, um array mais um comprimento costuma ter um contrato mais fácil de conferir.

## Exercícios

Libertação e cópia de apontadores

Uma função recebe `int` \*p, chama `free(p)` e depois faz p = NULL. O chamador passou a sua variável values. Depois da chamada, pode testar values != NULL para saber se a memória ainda existe? Justifica e propõe uma interface adequada.

Pista

A função recebe uma cópia do valor de values.

Mais uma pista

Alterar p não altera o objeto values do chamador.

Solução

Não. `free` termina a duração do objeto e deixa indeterminado o valor dos apontadores que o designavam. Comparar esse valor com NULL não é uma verificação válida em C portátil. O chamador pode fazer values = `destroy(values)`, com a função a devolver NULL, ou a função pode receber `int` \*\* e alterar \*p depois da libertação. Outros aliases continuam a exigir uma política de propriedade.

Confere a tua resposta:

*   Expliquei a cópia do apontador.
*   Identifiquei o valor indeterminado depois de free.
*   Propus retorno de `NULL` ou parâmetro duplo.

Erros frequentes

Um apontador não se torna automaticamente NULL quando o objeto é libertado. Pôr um alias a NULL também não corrige os restantes.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#passagem-de-par%C3%A2metros)

Devolver um resultado válido

Uma função cria `int` result = 17 e devolve &result, ou seja devolve o endereço de um objeto local. Num teste, o chamador imprime 17. Esse resultado prova que a função está correta? Dá duas alternativas sem esse erro de duração.

Pista

A memória pode ainda conter os mesmos bits depois do retorno.

Solução

O objeto automático deixa de existir no retorno. Desreferenciar o endereço devolvido é inválido, mesmo que os bits ainda mostrem 17. Devolve o inteiro por valor ou recebe um apontador para um `int` que o chamador conserva durante toda a chamada e escreve nesse objeto.

Confere a tua resposta:

*   Identifiquei o fim da duração do objeto.
*   Não usei um teste isolado como prova de validade.
*   Propus retorno por valor ou armazenamento do chamador.

Erros frequentes

Declarar result como apontador não prolonga a duração de um objeto automático. Usar static mudaria o contrato para armazenamento partilhado entre chamadas.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#chamada-stack-frame-e-retorno)

Despacho por tabela e índice inválido

Uma tabela `int (*table[])(int) = {f, g}` contém `f(n) = 3*n` e `g(n) = n+7`. O índice vem de um byte recebido.

1.  Que resultado dá `table[1](4)`?
2.  O byte recebido vale 2. Que validação impede uma chamada inválida?
3.  Seria correto acrescentar uma função `void h(void)` através de um cast para `int (*)(int)`? Justifica.

Pista

Separa a escolha da entrada do contrato da função escolhida.

Solução

O índice 1 escolhe `g`, que devolve `4+7=11`.

A tabela tem apenas os índices 0 e 1. Confere `index < sizeof table / sizeof table[0]` antes de avaliar `table[index]`; para 2, rejeita a entrada sem chamar nenhuma função.

O cast não cria um adaptador. Chamar uma função com um tipo incompatível causa comportamento indefinido. Usa funções com a assinatura declarada ou uma função adaptadora com essa assinatura e um contrato de retorno definido.

Confere a tua resposta:

*   Obtive 11 na chamada válida ao índice 1.
*   Rejeitei o índice 2 antes de aceder à tabela.
*   Expliquei por que um cast não adapta assinaturas de funções.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#apontadores-para-fun%C3%A7%C3%B5es)

Seguir uma fila cheia

Uma fila de capacidade 3 começa vazia. Insere 10, 20 e 30, retira um elemento e insere 40. Dá os índices in e out, a quantidade e a ordem das próximas saídas. Uma nova inserção deve ter sucesso?

Pista

Cada índice avança módulo 3.

Solução

Depois das três inserções, in = out = 0 e count = 3. Retirar 10 faz out = 1 e count = 2. Inserir 40 usa a posição 0 e faz in = 1 e count = 3. As saídas são 20, 30 e 40. A fila está cheia; uma nova inserção falha sem alterar dados nem índices.

Confere a tua resposta:

*   Obtive in = out = 1 e count = 3.
*   Mantive a ordem 20, 30, 40.
*   Rejeitei inserir numa fila cheia sem perder dados.

Erros frequentes

in = out também ocorre quando a fila está cheia. Sem count ou outra convenção, os índices não distinguem os dois casos.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#filas-e-tipos-gen%C3%A9ricos)

Conservar uma alocação na falha

values é o único apontador para uma alocação válida. A instrução values = `realloc(values, 200)` devolve NULL. O bloco antigo foi libertado? Explica o problema e uma sequência segura, sem executar uma falha deliberada de memória.

Pista

O tamanho pedido é não nulo.

Solução

O bloco antigo continua alocado. A atribuição perde o seu único endereço e cria uma fuga de memória. Usa `int` \*`next` = `realloc(values, 200)`; se `next` for NULL, conserva values e trata o erro; caso contrário, atribui values = `next`. Depois do sucesso, não uses aliases do endereço anterior. Os 200 são bytes, não 200 inteiros.

Confere a tua resposta:

*   Mantive o bloco antigo alocado na falha.
*   Usei um apontador temporário.
*   Distingui tamanho em bytes e invalidação de aliases no sucesso.

Erros frequentes

Libertar values depois da atribuição falhada chama `free(NULL)` e não recupera o bloco perdido. Não confundir falha com o sucesso que pode mover o bloco.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#aloca%C3%A7%C3%A3o-e-propriedade)

Duração de um recurso partilhado

O módulo de recursos possui um pixmap alocado dinamicamente. Dois elementos de uma fila guardam estruturas com um apontador para esse pixmap; a fila copia os bytes de cada estrutura. Os elementos usam o pixmap sem adquirir a sua propriedade.

O primeiro elemento é retirado e destruído. Deve libertar o pixmap? Quando pode o módulo de recursos libertá-lo? Explica também por que copiar a estrutura não cria duas imagens independentes.

Pista

Identifica o objeto copiado e o objeto apenas referido.

Solução

A fila copia as estruturas, incluindo o valor do apontador. O pixmap continua a ser um único objeto partilhado.

Destruir o primeiro elemento não liberta o pixmap: o segundo ainda o usa e nenhum dos dois o possui. O módulo de recursos liberta-o uma única vez, depois de terminar todos os seus utilizadores, incluindo os elementos ainda na fila. Libertá-lo antes deixaria apontadores inválidos; libertá-lo em cada elemento causaria dupla libertação.

Confere a tua resposta:

*   Reconheci que as cópias partilham o mesmo pixmap.
*   Mantive o pixmap vivo enquanto algum elemento da fila o utiliza.
*   Atribuí uma única libertação ao módulo de recursos.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#filas-e-tipos-gen%C3%A9ricos)

Maior membro não é sempre o tamanho da union

Assume uma ABI com tamanho/alinhamento de char igual a 1/1, short 2/2 e int 4/4. O alinhamento de uma struct ou union é o maior dos alinhamentos dos membros, e o tamanho é arredondado para esse alinhamento.

```
struct S { char texto[10]; int numero; short codigo; };
union U { char texto[10]; int numero; short codigo; };
```

Calcula os offsets dos campos e sizeof S, sizeof U e sizeof de um array com três U. Num array de S, a quantos bytes do início fica numero do segundo elemento? Dizer apenas «arquitetura de 32 bits» chegaria para justificar estas contas?

Solução

Na struct, texto começa em 0 e ocupa dez bytes. Numero começa em 12 para respeitar alinhamento 4 e ocupa quatro; codigo começa em 16 e ocupa dois. A estrutura termina os campos em 18, mas precisa de dois bytes finais: sizeof S é 20.

Na union, todos os membros começam em 0. O maior ocupa dez bytes, arredondados ao alinhamento quatro: sizeof U é 12. Três elementos ocupam 36 bytes. O segundo S começa no offset 20 e o seu numero em 20 + 12 = 32.

As hipóteses da ABI determinam estes resultados. O número de bits do endereço, por si só, não fixa tamanhos nem alinhamentos de todos os tipos. Confere sizeof, \_Alignof e offsetof no ambiente concreto sem os confundir com um formato externo empacotado.

Confere a tua resposta:

*   Calculei offsets 0, 12 e 16 e tamanho 20 para a struct.
*   Arredondei o maior membro 10 ao alinhamento 4, obtendo union de 12.
*   Obtive 36 bytes para três unions e offset 32 para numero no segundo elemento da struct.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lc/memoria-funcoes/#calcular-o-tamanho-com-alinhamento)
