# Variáveis em C

Como já vimos no capítulo [A base](https://mentebinaria.gitbook.io/assembly/a-base), variáveis nada mais são do que um espaço de memória que pode ser manipulado pelo programa. Em C existem diversas nuances em como variáveis são alocadas e mantidas pelo compilador e aqui vamos entender essas diferenças.

{% hint style="info" %}
Na linguagem C existem palavra-chaves que são chamadas de *storage-class specifiers*, onde elas determinam o *storage-class* de uma variável. Na prática isso determina como a variável deve ser armazenada no programa. No C11 existem os seguintes *storage-class specifiers*:

* extern
* static
* \_Thread\_local
* auto (esse é o padrão)
* register
  {% endhint %}

## Variáveis globais

As variáveis globais em C são alocadas na seção `.data` ou `.bss`, dependendo se ela foi inicializada ou não. Como no exemplo:

```c
int data_var = 1;
int bss_var;
```

Se compilamos com `gcc main.c -S -o main.s -fno-asynchronous-unwind-tables` obtemos a seguinte saída:

{% code title="main.s" %}

```
	.globl	data_var
	.data
	.align 4
	.type	data_var, @object
	.size	data_var, 4
data_var:
	.long	1
	.comm	bss_var,4,4
```

{% endcode %}

A variável `data_var` foi alocada na seção `.data` e teve seu símbolo exportado com a diretiva `.globl data_var`, que é equivalente a diretiva `global` do NASM.

Já a variável `bss_var` foi declarada com a diretiva `.comm symbol, size, aligment` que serve para declarar *commom symbols* (símbolos comuns). Onde ela recebe como argumento o nome do símbolo seguido de seu tamanho (em bytes) e opcionalmente um valor de alinhamento. Em arquivos objetos ELF o argumento de alinhamento é um alinhamento em bytes, nesse exemplo a variável será alocada em um endereço alinhado por 4 bytes.

Já em arquivos objetos PE (do Windows) o alinhamento é um valor em potência de dois, logo para alinhar em 4 bytes deveríamos passar 2 como argumento ( $$2² = 4$$ ). Se a gente passar 4 como argumento então seria um alinhamento de $$2^4$$ que daria um alinhamento de 16 bytes.

Os símbolos declarados com a diretiva `.comm` que não foram inicializados em qualquer arquivo objeto são alocados na seção `.bss`. Logo nesse caso o uso da diretiva seria equivalente ao uso de `res*` do NASM, com a diferença que no NASM precisamos usar explicitamente na seção onde o espaço será alocado.

### Variável static global

As variáveis globais com *storage-class* `static` funcionam da mesma maneira que as variáveis globais comum, com a diferença que seu símbolo não é exportado para que possa ser acessado em outro arquivo objeto. Como no exemplo:

```c
static int data_var = 1;
static int bss_var;
```

Onde obtemos a saída:

```
	.data
	.align 4
	.type	data_var, @object
	.size	data_var, 4
data_var:
	.long	1
	.local	bss_var
	.comm	bss_var,4,4
```

Repare que dessa vez o símbolo `data_var` não foi exportado com a diretiva `.globl`, enquanto o `bss_var` foi explicitamente declarado como local com a diretiva `.local` (já que a diretiva `.comm` exporta como global por padrão).

### Variável extern

Variáveis `extern` em C são basicamente variáveis que são definidas em outro módulo. O GAS tem uma diretiva `.extern` que é equivalente a diretiva `extern` do NASM, isto é, indica que o símbolo será definido em outro arquivo objeto. Porém qualquer símbolo não declarado já é considerado externo por padrão pelo GAS. Experimente ver o código de saída do exemplo abaixo:

```c
extern int extern_var;

int main(void)
{
  int x = extern_var;
  return 0;
}
```

Você vai reparar que na função `main` o símbolo `extern_var` foi lido porém ele não foi declarado.

## Variáveis locais

Variáveis locais em C são comumente alocadas no *stack frame* da função, porém em alguns casos o compilador também pode reservar um registrador para armazenar o valor da variável.

Em C existe o *storage-class* `register` que serve como um "pedido" para o compilador alocar aquela variável de forma que o acesso a mesma seja o mais rápido possível, que geralmente é em um registrador (daí o nome da palavra-chave). Mas isso não garante que a variável será realmente alocada em um registrador. Na prática o único efeito colateral garantido é que você não poderá obter o endereço na memória daquela variável com o operador de endereço (`&`), e muitas vezes o compilador já vai alocar a variável em um registrador mesmo sem o uso da palavra-chave.

### Variável static local

Variáveis `static` local são armazenadas da mesma maneira que as variáveis `static` global, a única coisa que muda é no ponto de vista do código-fonte em C onde a visibilidade da variável é limitada para o escopo onde ela foi declarada. Isso faz com o que o compilador gere um símbolo de nome único para a variável, como no exemplo abaixo:

{% tabs %}
{% tab title="test.c" %}

```c
int test(void)
{
  static int data_var = 5;
  static int bss_var;

  return data_var + bss_var;
}
```

{% endtab %}

{% tab title="test.s" %}

```
	.data
	.align 4
	.type	data_var.1913, @object
	.size	data_var.1913, 4
data_var.1913:
	.long	5
	.local	bss_var.1914
	.comm	bss_var.1914,4,4
```

{% endtab %}
{% endtabs %}

Repare como `data_var.1913` não teve seu símbolo exportado e `bss_var.1914` foi declarado como local.

## Variáveis \_Thread\_local

O *storage-class* `_Thread_local` foi adicionado no C11. Assim como o nome sugere ele serve para alocar variáveis em uma região de memória que é local para cada [*thread*](https://pt.wikipedia.org/wiki/Thread_\(computa%C3%A7%C3%A3o\)) do processo. Vamos analisar o exemplo:

{% tabs %}
{% tab title="test.c" %}

```c
_Thread_local int global_thread_data = 5;
_Thread_local int global_thread_bss;

int test(void)
{
  _Thread_local static int local_thread_data = 5;
  _Thread_local static int local_thread_bss;

  return global_thread_data
    + global_thread_bss
    + local_thread_data
    + local_thread_bss;
}
```

{% endtab %}

{% tab title="test.s" %}

```
	.text
	.globl	global_thread_data
	.section	.tdata,"awT",@progbits
	.align 4
	.type	global_thread_data, @object
	.size	global_thread_data, 4
global_thread_data:
	.long	5
	.globl	global_thread_bss
	.section	.tbss,"awT",@nobits
	.align 4
	.type	global_thread_bss, @object
	.size	global_thread_bss, 4
global_thread_bss:
	.zero	4
	.section	.tdata
	.align 4
	.type	local_thread_data.1915, @object
	.size	local_thread_data.1915, 4
local_thread_data.1915:
	.long	5
	.section	.tbss
	.align 4
	.type	local_thread_bss.1916, @object
	.size	local_thread_bss.1916, 4
local_thread_bss.1916:
	.zero	4
	.text
	.globl	test
	.type	test, @function
test:
	endbr64
	pushq	%rbp
	movq	%rsp, %rbp
	movl	%fs:global_thread_data@tpoff, %edx
	movl	%fs:global_thread_bss@tpoff, %eax
	addl	%eax, %edx
	movl	%fs:local_thread_data.1915@tpoff, %eax
	addl	%eax, %edx
	movl	%fs:local_thread_bss.1916@tpoff, %eax
	addl	%edx, %eax
	popq	%rbp
	ret
```

{% endtab %}
{% endtabs %}

No Linux, em x86-64, a região de memória local para cada *thread* (*thread-local storage* - TLS) fica no segmento apontado pelo [registrador de segmento](https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/registradores-de-segmento) FS, por isso os valores das variáveis estão sendo lidos desse segmento.

Repare que as seções são diferentes, `.tdata` (equivalente a `.data` só que *thread-local*) e `.tbss` (equivalente a `.bss`) são utilizadas para armazenar as variáveis.

O sufixo `@tpoff` (*thread pointer offset*) usado nos símbolos indica que o *offset* do símbolo deve ser calculado levando em consideração a TLS como endereço de origem. Por padrão o *offset* é calculado com o segmento de dados "normal" como origem.

## Lidando com os tipos da linguagem C

Agora que já entendemos onde e como as variáveis são alocadas em C, só falta entender "o que" está sendo armazenado.

### Arrays e strings

O tipo *array* em C é meramente uma sequência de variáveis do mesmo tipo na memória. Por exemplo podemos inicializar um `int arr[4]` na sintaxe do GAS da seguinte forma:

```c
arr:
    .long 1, 2, 3, 4
```

Onde os valores `1`, `2`, `3` e `4` são despejados em sequência.

Em C não existe um tipo *string* porém por convenção as *strings* são uma *array* de `char`, onde o último `char` contém o valor zero (chamado de terminador nulo). Esse último caractere `'\0'` é usado para denotar o final da *string* e funções da libc que lidam com *strings* esperam por isso. Exemplos:

```c
string1:
    .ascii "Hello World", 0
string2:
    .ascii "Hello World\0"
string3:
    .asciz "Hello World"
```

As três *strings* acima são equivalentes na sintaxe do GAS.

Sobre a passagem de *arrays* (incluindo obviamente *strings*) como argumento para uma função, isso é feito passando um ponteiro para o primeiro elemento da *array*.

### Ponteiros

Ponteiros em C, na arquitetura x86/x86-64, são traduzidos meramente como o *offset* do objeto na memória. O segmento não é especificado como parte do valor do ponteiro.

Experimente ler o código de saída do seguinte programa:

```c
#include <stdio.h>

_Thread_local int my_var = 111;

int main(void)
{
  int *test = &my_var;
  *test = 777;

  printf("%d, %d\n", my_var, *test);
}
```

A leitura do endereço de `my_var` vai ser compilada para algo como:

```
movq	%fs:0, %rax
addq	$my_var@tpoff, %rax
movq	%rax, -8(%rbp)

# Com otimização ligada o GCC usa LEA:

movq	%fs:0, %rax
leaq	my_var@tpoff(%rax), %rdi
```

Onde primeiro é obtido o endereço do início do segmento FS que depois é somado ao *offset* de `my_var`. Assim obtendo o endereço efetivo da variável na memória.

### Estruturas

As estruturas em C são compiladas de forma que os valores dos campos da estrutura são dispostos em sequência na memória, seguindo a mesma ordem que foram declarados na estrutura. Existe a possibilidade do GCC adicionar alguns bytes extras no final da estrutura afim de manter o alinhamento dos dados, esses bytes extras são chamados de *padding*. Exemplo:

```c
#include <stdio.h>

typedef struct
{
  int x;
  char y;
} my_test_t;

my_test_t test = {
    .x = 5,
    .y = 'A',
};

int main(void)
{
  printf("%d, %c | sizeof: %zu\n", test.x, test.y, sizeof test);
}
```

Isso produziria o seguinte código para a inicialização da variável `test`:

```c
	.globl	test
	.data
	.align 8
	.type	test, @object
	.size	test, 8
test:
	.long	5
	.byte	65
	.zero	3
```

Repare a diretiva `.zero 3` que foi usada para despejar 3 bytes zero no final da estrutura, afim de alinhar a mesma em 4 bytes. No total a estrutura acaba tendo 8 bytes de tamanho: 4 bytes do `int`, 1 byte do `char` e 3 bytes de *padding*.

### Unions

As *unions* são bem simples, são alocadas com o tamanho do maior tipo declarado para a *union*. Por exemplo:

```c
typedef union
{
  int x;
  char y;
} my_test_t;
```

Essa *union* é alocada na memória da mesma forma que um `int`, que tem 4 bytes de tamanho.
