Programação

C

C

Miscelânea

Comentários

Comentários começam com /* e se extendem até o */.
Versões mais novas de C têm suporte para comentários estilo C++, que começam com // e vão até o fim da linha.

C

Variáveis e constantes

Declaração

Ao contrário de linguagens como Python, em C toda variável deve ser declarada antes de ser usada, e o tipo deve ser especificado na declaração. Exemplos:

int x;
double nota;
unsigned char c;
volatile unsigned long long int declaracaoLonga;

É possível atribuir um valor à variável já durante a declaração:

int x = 100;
double nota = -2.1;
unsigned char c = 'F';

Nota: nomes de variáveis não podem começar com dígitos, nem com underscore seguido de maiúscula. Não se pode usar dois underscores seguidos em nenhum lugar no nome da variável.

Tipos primitivos

Nota: char não é sinônimo de caractere.
Na época em que C foi criado, a codificação mais usada era ASCII, e todo caractere ASCII cabe em 1 byte. Hoje, a codificação mais usada é UTF-8, na qual um caractere pode ocupar vários bytes; portanto, podem ser necessários vários chars para guardar um único caractere.

Nota: em C, existe uma correspondência direta entre int e bool: 0 é false; qualquer outro valor é true. (Geralmente usa-se 1 para true.) Em função disso, nas versões de C que não têm bool, usa-se simplesmente int no lugar.

Modificadores de tipo

Exemplos:

const int BUFFER_SIZE = 4096;
const double PI =  3.1415926535;

Nota: quando se usa um desses modificiadores (à exceção do const) com int é possível omitir o int; por exemplo, long int x; pode ser escrito simplesmente como long x;.

Conversão de tipo (cast)

Em C, uma conversão de tipos é feita como:

(tipo_para_o_qual_converter) expressao

Por exemplo:

int x = 5;
float y = (float)x / 2;

Arrays

Uma array é uma sequência de valores de mesmo tipo, alocados em endereços contínuos na memória. Arrays são declarados com o número de elementos após o nome, entre colchetes []. Por exemplo:

int arr[20]; /* declara uma array de 20 ints */

Como qualquer outra variável, é possível atribuir um valor inicial para a array durante a declaração:

int arr[5] = {10, -1, 4, -7, 31}; /* todos os elementos são especificados */
int arr[20] = {10, -1, 4, -7, 31}; /* apenas até o 5o é especificado */
int arr[3] = {10, -1, 4, -7, 31}; /* ERRO! A array não é grande o suficiente */

Nesse caso, é possível também omitir o tamanho da array e deixar que o compilador determine o tamanho:

/* o compilador declara a array com o menor tamanho possivel que caiba todos os elementos, ou seja, 5 */
int arr[] = {10, -1, 4, -7, 31};

Usa-se o operador [] para acessar o n-ésimo elemento da array. Por exemplo:

int arr[20];
arr[0] = 1;
arr[1] = 2 + arr[0];
arr[2] = arr[1] - 2 * arr[0];
/* ... */
arr[19] = -10;

Se a array foi declarada com N elementos, é possível acessar os elementos de 0 a N - 1. Em C, ao contrário de lingaugens como Java, não há checagem se o indíce está dentro dos limites da array.
arr[i] é, por definição, equivalente a *(arr + i); vide relação com arrays.

Strings

Em C, strings são sequências de caracteres terminadas pelo caractere especial \0. Strings são implementadas como ponteiros ou arrays de char. (Ponteiros serão explicados mais adiante.)

Literais

Literais (literals) são representações de um valor fixo. Os exemplos mais óbvios são números como 21 ou 3.14, mas há também:

Todo string literal, assim como toda string em C, é terminado pelo caractere especial \0.
É possível atribuir um char literal a uma variável do tipo char, e um string literal a um ponteiro ou array de const char:

const char *string_ponteiro = "foo";
const char string_array[] = "bar";

(Nota: é possível omitir o const nos dois casos, mas tentar alterar um caractere de uma string literal é undefined behaviour.)

Caracteres escapados (backslash sequences)

Esses caracteres podem ser usados tanto em char literals quanto em string literals:

(Há outros, esses são os mais importantes.) Para obter uma barra invertida (backslash) de verdade, use \\.

Escopo

Em C, todo código que está entre chaves { e } é chamado de bloco. O escopo da variável é o bloco em que ela foi declarada; ou seja, a variável só existe dentro daquele bloco. Por exemplo:

int x;
if(1) {
  int y;
  y = 5; /* OK */
  x = 0; /* OK, declarada num escopo mais externo */
}
x = 1; /* OK */
y = 0; /* ERRO! Variável fora de escopo! */

É possível declarar variáveis com o mesmo nome em escopos diferentes. A variável usada é sempre a do escopo mais interno. Por exemplo:

int x = 0;
if(1) {
  int x;
  x = 1;
  printf("x = %d\n", x);
}
printf("x = %d\n", x);

Tem como output

x = 1
x = 0

Ou seja, a atribuição dentro do if não altera a variável fora do if.

Nota: nas versões mais antigas de C, as declarações de variáveis só podiam ser feitas no começo do bloco. Versões posteriores não têm essa restrição.

C

Operadores

De atribuição

O operador = denota uma atribuição. Sempre. Em qualquer contexto - mesmo dentro de um if ou while. Por exemplo, esse código é válido, mas não compara x com 3:

if(x = 3) {
  printf("a condicao eh sempre verdadeira!\n");
}

O código é válido porque a expressão x = 3 tem, na verdade, um valor: 3, que é o operando da direita. (O operador = devolve o operando da direita para permitir atribuições encadeadas, como a = b = c = 1; por exemplo.) Esse valor, como qualquer outro valor que não é zero, é considerado como true.
Para comparar x com 3, seria preciso usar o operador ==.

Nota: é comum, em C, ver construções do tipo

if(((var = function_call(args)) == NULL) {
  /* tratamento de erro */
}

Esse código é uma versão mais concisa de

var = function_call(args);
if(var == NULL) {
  /* tratamento de erro */
}

O uso de parênteses ao redor da atribuição (no primeiro exemplo) é devido ao fato de que o operador = tem precedência menor que o operador ==.

Aritméticos

Além das 4 operações básicas (+, -, *, /), há também o operador %, que calcula o resto de divisão entre dois inteiros.
Não há nenhum operador para exponenciação: no caso de inteiros pode-se fazer um loop, no caso de floats, há a função pow() (declarada em math.h).

Nota: em C, toda divisão entre inteiros também gera um resultado inteiro (a parte decimal é truncada). Por exemplo

float x = 17 / 5;
printf("%f\n", x);

Imprime 3.000000. Para forçar uma divisão não-inteira, é preciso fazer uma conversão de tipo (cast) de um dos operandos para float:

float x = ((float)17) / 5;
printf("%f\n", x);

Imprime 3.400000, como esperado.

De comparação

==: igual a
!=: diferente de
< e >: menor que e maior que
<= e >=: menor ou igual e maior ou igual

Nota: em C, esses operadores não podem ser usados com strings. Use a função strcmp() em vez disso.

Lógicos

Esses operadores atuam como portas lógicas e permitem fazer condições compostas (novamente, 0 equivale a false e não-zero equivale a true)

&& AND
|| OR
! NOT

Nota: os operadores && e || são chamados curto-circuitados, porque podem não avaliar todos os operandos. Por exemplo, em:

if(foo() && bar()) {
  /* alguma coisa */
}

Se a chamada foo() retornar falso, então a função bar() nunca é chamada, pois o resultado final será falso de qualquer forma. Da mesma forma, se tivermos

if(foo() || bar()) {
  /* alguma coisa */
}

E se a chamada foo() retornar verdadeiro, então o resultado será necessariamente verdadeiro e a função bar() nunca é chamada.

De incremento e decremento

Os operadores ++ e -- representam incremento e decremento, e aumentam/diminuem o valor da variável em 1, respectivamente.
Os dois operadores têm uma peculiaridade: ambos podem aparecer antes ou depois da variável (++var e var++, --var e var--). Por exemplo:

int x = 4;
int y = ++x; /* pré-incremento: realizado antes da atribuição */
printf("%d\n", y);

Imprime 5, enquanto

int x = 4;
int y = x++; /* pós-incremento: realizado depois da atribuição */
printf("%d\n", y);

Imprime 4. O pré-decremento e pós-decremento funcionam de forma análoga.

Esses operadores são usados ostensivamente em C - em índices de loops for, em ponteiros, em índices de arrays, etc.

De atribuição composta

Todos os operadores aritméticos e bitwise possuem uma versão de atribuição composta, que são da forma operador=. Por exemplo:

x += 5;

Que é, por definição, equivalente a

x = x + 5;

Os operadores de atribuição composta são:
+= -= *= /= %= &= |= ^= <<= >>=

sizeof

O operador sizeof devolve o tamanho do operando, em bytes. Exemplos:

/* size_t é o tipo do resultado de sizeof */
size_t int_size = sizeof(int); /* sizeof(tipo) */
int x;
size_t x_size = sizeof(x); /* sizeof(variável) */
size_t expr_size = sizeof(&x); /* sizeof(expressão) */

Por definição, sizeof(char) vale 1; portanto, char é equivalente a byte.

Esse operador pode ser usado para descobrir quantos elementos uma array tem:

int arr[20];
/* ... */

/* sizeof(arr) == 20 * sizeof(int) e sizeof(*arr) == sizeof(int) */
size_t arr_size = sizeof(arr) / sizeof(*arr);

(Note, porém, que essa técnica não funciona com memória alocada dinâmicamente, nem com arrays que foram passadas para uma função - funções não recebem arrays, mas sim ponteiros.)

Bitwise

Operadores bitwise atuam nos bits individuais de seus operandos.

O operador ~ (NOT) tem apenas um operando e inverte todos os seus bits.
Os operadores & (AND), | (OR) e ^ (XOR) têm dois operandos, e atuam como portas lógicas em seus bits: no caso do &, o primeiro bit do resultado é o AND do primeiro bit dos dois operandos, e assim sucessivamente para os demais bits. Por exemplo:

int x = 0xb; /* 1011 em binario */
int y = 0x5; /* 0101 em binario */
int z = x & y;
printf("%d\n", z); 

Imprime 1:

b3 b2 b1 b0
1 0 1 1 x
0 1 0 1 y
&
0 0 0 1 z

O operador | é frequentemente usado para obter combinações de uma ou mais flags, que podem estar ativas ou não, e por isso podem ser representadas por um único bit. O uso de flags (em vez de uma array de bools, por exemplo), permite fazer combinações de inúmeras de flags usando apenas uma variável.
Por exemplo, no caso da função open() (de UNIX):

int fd = open("log.txt", O_WRONLY|O_APPEND|O_CREAT);
/* ... */
close(fd);

Cada macro O_* usada como operando do | é uma flag, e corresponde a um valor que tem todos os bits zero, exceto por um bit em uma posição específica (e essa posição é diferente para cada macro). Então fazer | com essa macro ativa o bit naquela posição.
De maneira similar, podemos usar o operador & para checar se uma flag está ativa:

if(flags & O_CREAT) {
  /* flag O_CREAT está ativa, aja de acordo */
}

Note que a expressão usada como condição no if será não-zero (true) se e somente se o bit correspondente em flags está ativo.

Além desses operadores bitwise, há os de bitshift: << e >>, que denotam shift para a esquerda e para a direita, respectivamente. A operação de bitshift desloca os bits do primeiro operando, em um número inteiro de posições, denotado pelo segundo operando. Por exemplo:

int x = 7 << 2;
printf("%d\n", x);

Imprime 28:

b31 b30 ... b5 b4 b3 b2 b1 b0
0 0 0 0 0 0 1 1 1 7
0 0 0 0 1 1 1 7 << 2

As posições vazias que são criadas à esquerda (b1 e b0) são preenchidas com zeros, e os bits mais à esquerda (b31 e b30, em negrito) são descartados. (Supomos, nesse exemplo e nos seguintes, que um int tem 32 bits.)
No shift para a esquerda, os bits vazios são sempre preenchidos com zero, mesmo quando os bits mais significativos valem 1:

int x = -3 << 3;
printf("%d\n", x);

Imprime -24:

b31 b30 ... b5 b4 b3 b2 b1 b0
1 1 1 1 1 1 1 0 1 -3
1 1 1 1 0 1 0 0 0 -3 << 3

Portanto, um shift para a esquerda em N posições equivale a mulitiplicar um inteiro por 2^N, independentemente de seu sinal.

Consideremos agora um shift para a direita, por exemplo, 15 >> 2:

b31 b30 ... b5 b4 b3 b2 b1 b0
0 0 0 0 0 1 1 1 1 15
0 0 0 0 0 1 1 15 >> 2

Nesse caso, criam-se posições vagas nos bits b31 e b30, e os bits b1 e b0 são desprezados. Preenchendo os bits vazios com zero, temos como resultado o valor 3, que é igual a floor(15 / 2^2).

Note que, no caso do shift à direita, nem sempre podemos preencher os bits vazios com zero. Considere, por exemplo, -3 >> 1:

b31 b30 ... b5 b4 b3 b2 b1 b0
1 1 1 1 1 1 1 0 1 -3
1 1 1 1 1 1 1 0 -3 >> 1

Se preenchermos a posição vazia com zero, teremos alterado o sinal do resultado! Por outro lado, esses mesmos bits podem corresponder a um unsigned int: nesse caso o bit b31 não é um bit de sinal, e devemos preencher as posições vazias com bits zero. Daí, surge uma diferenciação entre shift à direita lógico e aritmético:

Em C, o operador >> denota shift à direita lógico quando o operando é de tipo unsigned. Caso contrário, o padrão não especifica se o tipo de shift usado deve ser lógico ou aritmético.

Portanto, x >> N equivale a floor(x / 2^N), contanto que o tipo adequado de shift seja usado.

Precedência e associatividade

A precedência dos operadores dita qual operação é realizada primeiro em uma expressão. Considere, por exemplo:

int x = 4 + 5 * 2;

Intuitivamente, a multiplicação deve acontecer antes da soma. Para que isso aconteça, o operador * tem precedência maior que o +, de modo que a expressão equivale a

int x = 4 + (5 * 2);

Se quisermos que a soma aconteça antes, é preciso colocá-la entre parênteses:

int x = (4 + 5) * 2;

A precedência dos operadores aritméticos é intuitiva, mas isso não necessariamente ocorre para outros tipos de operadores; por isso, é preciso conhecer sua precedência.

A associatividade também dita a ordem das operações - mas quando um mesmo operador aparece várias vezes. Considere, por exemplo:

int x = 1 + 2 + 3;

Essa expressão será avaliada como (1 + 2) + 3 ou como 1 + (2 + 3)? No primeiro caso, temos assocatividade da direita para a esquerda, pois avaliamos a expressão nesse sentido; no segundo, a associatividade é da esquerda para a direita. Nesse exmeplo, a associatividade não faz diferença, pois o resultado será o mesmo nos dois casos. Mas considere agora:

int a = 0, b = 0, c = 0;
a = b = c = 1;

Se tentarmos avaliar a expressão da esquerda para a direita, teremos um problema: a atribuição a = b dará à variável a o valor de b, que ainda é 0. Porém, se avaliarmos a expressão no sentido contrário, tudo funciona: a = (b = (c = 1)); -> a = (b = 1)); -> a = 1; -> 1. (Note que o valor final dessa expressão é descartado; o que importa para nós são as atribuições feitas ao longo do caminho.) Portanto, o operador = precisa ter precedência da direita para a esquerda.

Na tabela abaixo, operadores na mesma linha têm a mesma precedência, e operadores em linhas mais acima têm precedência maior.

c_oper_prec.png
Fonte: Brian W. Kernighan, Dennis M. Ritchie. The C Programming Language. 2ª edição.

(Nota: os operadores ? e : são mencionados na seção Condicionais; o operador [] é mencionado na subseção Arrays; os operadores & e * (unário) são mencionados na seção Ponteiros; os operadores . e -> serão mencionados na seção Tipos definidos pelo usuário.)

C

Ponteiros

Um ponteiro é uma variável que armazena o endereço de outra variável. Ponteiros devem ser declarados com o símbolo *:

int *x; /* ponteiro para int */
char *s; /* ponteiro para char */
int **y; /* ponteiro para ponteiro para int */

O operador & obtém o endereço de uma variável, enquanto o operador * acessa (dereferencia) o que está no endereço de memória cujo valor é aquela expressão. Por exemplo:

int x = 5;
int *y;
y = &x; /* o valor de y passa a ser o endereço de x */
*y = 3; /* escreve 3 no endereço cujo valor é y */
printf("%d\n", x);

Imprime 3. Mesmo sem alterar x diretamente, alteramos o que está na memória em que x está armazenado, o que tem o mesmo efeito.
Note que o * tem significados diferentes quando usado na declaração (int *y) e quando usado como operador (*y = 3;): no primeiro caso, o asterisco serve apenas para denotar que a variável y é um ponteiro. Note também que os operadores & e * são como funções inversas, de modo que &*x equivale a *&x que equivale a x.
Como outras variáveis, é possível atribuir um valor a um ponteiro durante sua declaração; por exemplo, as duas linhas do exemplo acima

int *y;
y = &x;

Podem ser escritas como uma só:

int *y = &x;

Erros ao manipular ponteiros são comuns, e são a causa mais comum do famoso Segmentation fault.

Nota: em uma declaração como

int *x, y;

x é um ponteiro para int, mas y é apenas um int, não um ponteiro. Isso talvez não seja surpreendente quando a declaração é escrita assim, mas essa declaração poderia também ser escrita como

int* x, y;

Ainda assim, y continua não sendo um ponteiro.

NULL

NULL é um valor especial que indica que o ponteiro não aponta para nenhum endereço válido. É uma boa prática atribuir um ponteiro a NULL durante sua declaração. Dereferenciar um ponteiro que aponta para NULL gera um Segmentation fault:

int *p = NULL;
int x = *p + 1; /* Segmentation fault! */

O valor NULL é quase sempre definido como 0, de modo que a condição

if(p == NULL) {
  /* ... */
}

É frequentemente escrita como

if(!p) {
  /* ... */
}

E da mesma forma, if(p != NULL) pode ser escrito simplesmente como if(p).

Nota: NULL é garantidamente 0 em sistemas que seguem a especificação POSIX, entre os quais Linux.

Relação com arrays

Arrays e ponteiros estão fortemente relacionados. Uma array, na verdade, guarda o endereço de seu primeiro elemento:

int arr[20];
int *p;
p = arr; /* válido: p agora aponta para o primeiro elemento de arr */

Além disso, é possível somar um valor inteiro a um ponteiro para obter o n-ésimo valor depois daquele endereço:

int arr[20];
int *p;

p = arr + 4; /* o endereço 4 ints depois do começo de arr; ou seja, o endereço do quinto elemento de arr */

Além disso, por definição, arr[i] é equivalente a *(arr + i). Desse modo, se declaramos uma array como

double arr[SIZE];

Podemos percorrer seus elementos como

int i;
for(i = 0; i < SIZE; i++) {
  /* fazer algo com arr[i] */
}

Ou como

double arr[SIZE];
/* ... */
double *p;
for(p = arr; p < arr + SIZE; p++) {
  /* fazer algo com *p */
}

Isso explica porque os índices das arrays em C começam em zero: arr[0] equivale a *(arr + 0), que equivale a *arr. Isso explica também porque índices negativos não funcionam em C da mesma forma que em Python: arr[-4] é o endereço do quarto elemento antes do começo da array. Por último, isso explica porque &arr[2] é equivalente a arr + 2, pois &arr[2] -> &*(arr + 2) -> arr + 2.

Diferenças com arrays

Arrays e ponterios são semelhantes, mas não idênticos. Primeiramente, não é possível mudar o endereço que a array guarda:

int arr[20];
int x;
int *p;

p = &x; /* OK */
arr = &x; /* ERRO! */

p++; /* OK */
arr++; /* ERRO! */

Mas a diferença mais importante aparece ao usar o operador sizeof:

int arr[20];
int *p = arr;

printf("%d %d\n", sizeof(arr), sizeof(p));

Imprime valores diferentes: sizeof(arr) é igual a 20 * sizeof(int), enquanto sizeof(p) corresponde a sizeof(int*).

Alocação dinâmica

Arrays possuem uma limitação séria: seu tamanho deve ser estático e conhecido em tempo de compilação. Por exemplo, o código a seguir não é válido:

size_t size;
/* leia size de algum lugar */
int p[size]; /* ERRO! */

Para contornar essa limitação, usa-se a alocação dinâmica de memória, por meio das funções malloc (ou calloc) e free, declaradas em stdlib.h.
malloc recebe um tamanho em bytes, aloca uma região de memória com esse tamanho, e retorna um ponteiro para o começo dessa região, ou NULL em caso de erro. Essa é uma forma válida de fazer o que tentamos no exemplo anterior:

size_t size;
/* leia size de algum lugar */
int *p = malloc(size * sizeof(int)); /* espaço para size ints */
if(p == NULL) {
  /* tratamento de erro */
}

Há também a função calloc, que recebe dois parâmetros: o número de elementos e o tamanho de cada elemento, e também retorna uma região de memória com o tamanho necessário - mas os bytes dessa região são garantidamente inicializados para 0, o que livra o programador da possibilidade de problemas relacionados a memória não-inicializada. O exemplo anterior, usando calloc:

size_t size;
/* leia size de algum lugar */
int *p = calloc(size, sizeof(int)); 
if(p == NULL) {
  /* tratamento de erro */
}

Após uma chamada a qualquer uma das duas funções, a memória alocada deve ser liberada pela função free:

int *p = calloc(size, sizeof(int));
/* ... */
free(p);
p = NULL; /* boas práticas; veja abaixo */

Alguns erros podem surgir pelo uso indevido de free:

Nota: o tamanho de uma região de memória alocada dinamicamente não pode ser obtido com sizeof:

int *p = malloc(num * sizeof(int));
printf("%d", sizeof(p));
free(p);

Imprime o valor de sizeof(int*), indepedentemente de quantos bytes foram alocados por malloc. Novamente, isso ocorre porque p é um ponteiro, não uma array.

Nota: é comum ver casts depois de uma chamada a malloc ou calloc:

int *p = (int*) malloc(num * sizeof(int));

Porém, esse cast não é necessário, pois malloc tem tipo de retorno void*, que pode ser implicitamente convertido para qualquer outro tipo de ponteiro (vide seção void*).

(Nomenclatura: há quem chame a região de memória alocada por malloc e similares de "array alocada dinamicamente", mas evitamos chamar isso de array, porque o que temos de fato é um ponteiro.)

C

Fluxo de execução

Condicionais

if

Executa um bloco de comandos se a condição vale true. Exemplo:

/* módulo do número */
if(x < 0) {
  x = -x;
}

Nota: caso haja apenas um comando a ser executado pelo if, os colchetes podem ser omitidos, logo o exemplo acima poderia ser escrito como

/* módulo do número */
if(x < 0)
  x = -x;

if - else

Caso a condição valha true, executa o bloco de comandos após o if; caso contrário, executa o bloco de comandos após o else. Exemplo:

if(x % 2 == 0) {
  printf("x eh par\n");
} else {
  printf("x eh impar\n");
}

if - else if - ... - else

Podemos encadear várias construções if..else de modo a obter:

if(condicao1) {
  comandos_se_condicao1_eh_verdadeira;
} else if(condicao2) {
  comandos_se_condicao1_eh_falsa_mas_condicao2_eh_verdadeira;
} else if(condicao3) {
  comandos_se_condicao1_e_2_sao_falsas_mas_condicao3_eh_verdadeira;
} /* mais else if se voce quiser */ else {
  comandos_se_nenhuma_das_condicoes_eh_verdadeira;
}

Exemplo (Fizzbuzz):

if(x % 15 == 0) {
	printf("FizzBuzz\n");
} else if(x % 5 == 0) {
  printf("Buzz\n");
} else if(x % 3 == 0) {
  printf("Fizz\n");
} else {
  printf("%d\n", x);
}

Nota: esse exemplo pode ser reescrito com um if a menos se usarmos uma váriavel extra:

bool fizz_or_buzz = false;
if(x % 3 == 0) {
  printf("Fizz");
  fizz_or_buzz = true;
}
if(x % 5 == 0) {
  printf("Buzz");
  fizz_or_buzz = true;
}
if(!fizz_or_buzz) {
  printf("%d", x);
}
printf("\n");

switch...case

A construção

switch(expr) {
  case VALOR1:
    comandos_se_expr_igual_a_VALOR1;
    break;
  case VALOR2:
    comandos_se_expr_igual_a_VALOR2;
    break;
  case VALOR3:
    comandos_se_expr_igual_a_VALOR3;
    break;
  ...
  default:
    comandos_se_variavel_diferente_de_todos_aqueles_valores;
}

É equivalente a

var = expr; /* expr é avaliada só uma vez */
if(var == VALOR1) {
    comandos_se_variavel_igual_a_VALOR1;
} else if(var == VALOR2) {
    comandos_se_variavel_igual_a_VALOR2;
} else if(var == VALOR3) {
    comandos_se_variavel_igual_a_VALOR3;
} ... else {
    comandos_se_variavel_diferente_de_todos_aqueles_valores;
}

Exemplo:

switch(x % 3) {
  case 0:
    printf("x eh divisivel por 3\n");
    break;
  case 1:
    printf("o resto de divisao de x por 3 eh 1\n");
    break;
  default:
    printf("o resto de divisao de x por 3 eh 2\n");
    break;
}

Operadores ? e :

O código

sign = x >= 0 ? 0 : 1;

É equivalente a

if(x >= 0) {
  sign = 0;
} else {
  sign = 1;
}

Laços (loops)

while

while(condicao) {
  comando_executado_enquanto_condicao_eh_verdadeira;
}

A condição é testada, e se for verdadeira, os comandos dentro do while são executados. A cada vez que se chega ao final do while, a condição é testada novamente, e se ainda for verdadeira, os comandos dentro do while são executados de novo, e assim por diante.
Exemplo:

/* calcula x! */
int y = 1;
while(x > 0) {
  y *= x;
  x--;
}

Podemos escrever um loop infinito como

while(1) {
  printf("loop infinito!\n");
}

for

O código

for(inicializacao; condicao; transicao) {
  comandos;
}

É, por definição, equivalente a

inicializacao;
while(condicao) {
  comandos;
  
  transicao;
}

Por exemplo:

/* imprime os números de 0 a 9 */
int i;
for(i = 0; i < 10; i++) {
  printf("%d\n", i);
}

Um ou mais dos campos do for podem estar vazios. Por exemplo, podemos reescrever o exemplo do fatorial acima como

/* calcula x! */
int y = 1;
for(; x > 0; x--) {
  y *= x;
}

Uma condição vazia é sempre avaliada como verdadeira, de modo que podemos também fazer loops infinitos como

for(;;) {
  printf("loop infinito!\n");
}

do...while

Semelhante ao while, mas a condição só é testada depois de executar o que está dentro do do...while uma vez. Por exemplo,

/* 
 * pede confirmação do usuário; queremos um loop que executa até o usuário
 * dar uma resposta válida.
 */
int c;
do {
  printf("deseja continuar? (s)im/(n)ao\n");
  c = getchar();
} while(c != 's' && c != 'n');

Temos que usar o do...while nesse caso porque c não tem um valor definido antes da primeira interação do loop.

C

Funções

Ao contrário de linguagens como Python, em C, quase todo código deve estar dentro de alguma função. Só declarações (de variáveis, tipos, funções, etc) e definições de função podem estar fora de uma função.
Uma função é definida como

tipo_de_retorno nome_da_funcao(tipo1 arg1, tipo2 arg2, ..., tipoN argN)
{
  /* comandos */
}

Por exemplo:

double soma(double a, double b)
{
  return a + b;
}

Para chamar a função:

double a, b;
/* leia a e b de algum lugar */
double x = soma(a, b);

Assim que a função atinge o return, passa-se imediatamete o controle da execução para a função que a chamou - ou seja, nada depois do return é executado. Por isso, a função

double soma(double a, double b)
{
  return a + b;
  printf("somei\n");
}

Não imprime "somei".

A função pode ter void na lista de argumentos, o que especifica que ela não recebe nenhum argumento:

int funcao_sem_parametros(void)
{
  /* ... */
  return 42;
}

A função também pode ter tipo de retorno void - nesse caso, a função não retorna nenhum valor:

void print_stuff(void)
{
  printf("stuff\n");
}

O return pode ser omitido em funções com tipo de retorno void, mas ainda é possível usá-lo para sair da função prematuramente em caso de erro:

void aloca_e_faz_algo(void)
{
  int *ptr = malloc(100 * sizeof(int));
  if(ptr == NULL) {
    return; /* note que o return é usado sem nenhum valor */
  }
  /* faça algo (se estamos aqui, ptr não é NULL) */
  free(ptr);
}

Nota: é comum ver implementações desse tipo feitas por programadores novatos:

int eh_positivo(int x)
{
  if(x > 0) {
    return 1; /* (A) */
  } else {
    return 0;
  }
}

Isso funciona, mas há duas coisas desnecssárias. A primeira é o else: se a função não retornar em (A), isso necessariamente significa que a condição x > 0 é falsa, logo podemos escrever

int eh_positivo(int x)
{
  if(x > 0) {
    return 1;
  }
  return 0;
}

A segunda é que tudo isso pode ser escrito simplesmente como

int eh_positivo(int x)
{
  return x > 0;
}

Porque, para entrar no if em (A), a condição x > 0 já deve ser verdadeira (ou seja, valer 1), e para não entrar no if, a condição deve valer 0, que são justamente os valores que retornamos. Logo podemos retornar o valor da expressão diretamente.

Protótipo (declaração) de função

Uma função deve ser declarada antes de ser usada.
Por exemplo, o código abaixo está errado:

int main(void)
{
  foo(); /* ERRO! */
  return 0;
}

void foo(void)
{
  printf("foo\n");
}

Porque a função foo não foi declarada antes de sua chamada. A maneira mais simples de resolver isso é invertendo a ordem das definições:

int main(void)
{
  foo();
  return 0;
}

void foo(void)
{
  printf("foo\n");
}

Mas poderíamos também declarar a função antes de definí-la, com um protótipo de função:

void foo(void); /* declaração (ou protótipo) */

int main(void)
{
  foo();
  return 0;
}

void foo(void)
{
  printf("foo\n");
}

O número e o tipo dos argumentos deve ser o mesmo no protótipo e na definição da função.
O uso mais comum de protótipos é em cabeçalhos (headers). Colocar um protótipo de uma função no header permite que qualquer arquivo que inclua aquele header possa usar a função.
Além disso, o uso de protótipos é necessário quando se tem funções mutuamente recursivas (que chamam uma à outra e vice-versa):


/* conta quantos bits 0 e 1 há em um número */
int zeroes = 0;
int ones = 0;

void count_ones(int x); /* protótipo necessário para a chamada abaixo */

void count_zeroes(int x)
{
  if(x == 0) {
    return;
  }
  if(!(x & 0x1)) {
    zeroes++;
    x >>= 1;
  }
  count_ones(x);
}

void count_ones(int x)
{
  if(x == 0) {
    return;
  }
  if(x & 0x1) {
    ones++;
    x >>= 1;
  }
  count_zeroes(x);
}

Ponteiros como argumentos

Os argumentos passados para uma função são, na verdade, cópias do valor original: uma alteração no argumento não se propaga para a função que chama. Por exemplo,

void incr(int x) 
{
  x++;
}

int main(void)
{
  int x = 5;
  incr(x);
  printf("%d\n", x);
  return 0;
}

Imprime 5, porque a variável x da função incr não é a mesma que a variável x da função main, elas apenas tem o mesmo valor no começo de incr. Se quisermos que essa alteração se propague, devemos fazer com que a função receba um ponteiro:

void incr(int *x) 
{
  (*x)++; /* (A) */
}

int main(void)
{
  int x = 5;
  incr(&x);
  printf("%d\n", x);
  return 0;
}

Imprime 6.
(Note o uso de parênteses em (A): o operador ++ tem precedencia maior que *, de modo que, se não usássemos parênteses, a expressão seria equivalente a *(x++);)

Funções não podem receber arrays. É possível escrever o argumento de uma função como

void funcao(int arg[])
{
  /* ... */
}

Mas arg não é uma array, e sim um ponteiro. Essa notação é usada simplesmente para mostrar que espera-se que a função seja chamada com uma array. Quando a array é passada para uma função, ela é automaticamente convertida em ponteiro:

void func(int arg[])
{
  printf("tamanho em func = %d\n", sizeof(arg));
}

int main(void)
{
  int arr[20];
  printf("tamanho em main = %d\n", sizeof(arr));
  func(arr);
  return 0;
}

Imprime tamanhos diferentes: o tamanho impresso em func é igual a sizeof(int*). Se a função tiver que lidar com o tamanho (ou o número de elementos) da array, é preciso passar esse valor como argumento.

Nota: é possível também declarar um parâmetro de função como

void func(int arg[20])
{
  /* ... */
}

Ainda assim, arg é um ponteiro (como podemos averiguar imprimindo sizeof(arg) como no exemplo anterior). O tamanho 20 é simplesmente ignorado pelo compilador. Isso pode confundir quem for ler o seu código - por isso, evite esse tipo de declaração.

Ponteiros como tipo de retorno

Uma função pode também retornar um ponteiro. Porém, não se pode retornar ponteiros para váriaveis locais da função. Por exemplo, supondo que temos a seguinte definição de struct:

struct ponto {
  int x;
  int y;
  int z;
};

Então essa função pode parecer, à primeira vista, uma boa forma de criar uma variável desse tipo:

struct ponto * cria_ponto(int x, int y, int z)
{
  struct ponto p;

  p.x = x;
  p.y = y;
  p.z = z;

  return &p; /* ERRO! */
}

O problema: como p foi declarada no escopo daquela função, assim que a função retornar, p vai sair de escopo imediatamente depois do return, e não há nenhuma garantia do que passará a ser guardado naquele endereço.
(Na prática, variáveis locais costumam ser armazenadas em uma pilha, de modo que, quando a função que chamou criaPonto chamar outras funções, as variáveis locais dessas funções passarão a ocupar o endereço que antes era de p.) Portanto, uma função que retorna um ponteiro para uma variável nova deve alocar essa variável dinamicamente:

/* esta funcao retorna um endereco obtido com malloc(): lembre-se de chamar free()! */
struct ponto * cria_ponto(int x, int y, int z)
{
  struct ponto *p = malloc(sizeof(struct ponto));

  p->x = x;
  p->y = y;
  p->z = z;

  return p; /* OK! */
}

Outra alternativa seria receber um ponteiro em vez de retornar um, deixando a criação da variável a cargo de quem for usar a função:

/* note como mudamos de "cria" para "inicializa" para refletir o que a função faz */
void inicializa_ponto(struct ponto *p, int x, int y, int z)
{
  if(p == NULL) {
    return;
  }
  p->x = x;
  p->y = y;
  p->z = z;
}

Nota: uma função que retorna um ponteiro alocado dinamicamente deve deixar esse fato documentado, para que a pessoa que for usar essa função saiba que deve chamar free() depois. (De maneira geral, a pessoa que for usar suas funções não deve ser forçada a ler a implementação delas.)

main

A primeira função chamada num código em C é main. Essa função pode ser definida de duas maneiras:

int main(void)
{
  /* (A) */
}

Ou

int main(int argc, char **argv)
{
  /* (B) */
}

Declarando maincomo em (B), essa função pode acessar os parâmetros passados ao programa pela linha de comando: argc (argument count) é o número de argumentos, e argv (argument values) contém as strings que foram passadas em argv[0], argv[1], ..., argv[argc - 1].
Por exemplo, se o progama chama-se prog e é invocado pelo bash como

$ ./prog foo bar baz

Então teremos argc == 4, (o nome do programa é contado), e:

Naturalmente, se main for declarada como em (A), não é possível acessar os parâmetros da linha de comando.

Tanto em (A) quanto em (B), main possui tipo de retorno int. A função main deve retornar 0 se a execução correu como esperado, ou algum código de erro que não valha zero, caso contrário. Esse valor é chamado de "exit status" do programa.

C

Tipos definidos pelo usuário

struct

Uma struct é um tipo composto por vários outros. Por exemplo:

struct cliente {
  char *nome;
  int idade;
  char *endereco;
  /* ... */
};

Define o tipo struct cliente como uma composição dos tipos entre { }, com os nomes acima. Cada sub-tipo da struct é chamado de campo da struct.

Podemos declarar uma variável desse tipo simplesmente como:

struct cliente c1;

Note que o nome do tipo é struct cliente e o da variável é c1.

Podemos também declarar variáveis de um tipo struct juntamente com a declaração do tipo:

struct ponto {
  double x;
  double y;
  double z;
} p1, p2, p3;

Declara as variáveis p1, p2 e p3 do tipo struct ponto. Depois disso, ainda podemos declarar mais variáveis desse tipo normalmente:

struct ponto p4, p5;

Se declararmos variáveis juntamente com a struct tipo de declaração, é possível omitir o nome da struct:

struct {
  int x;
  int y;
} vetor1, vetor2;

Nesse caso temos uma struct anônima. Como esse tipo não tem nome, não é possível declarar nenhuma outra variável desse tipo além de vetor1 e vetor2.

Podemos acessar cada um dos campos de um tipo struct com o operador .

p1.x = 1;
p1.y = 2;
p1.z = -3;
printf("x = %f y = %f z = %f\n", p1.x, p1.y, p1.z);

Como em outras variáveis, é possível atribuir um valor a structs durante sua declaração, de um modo similar à atribuição de arrays:

/* atribui o primeiro campo da struct ao valor 1, o segundo ao valor 2 e o terceiro, a -3 */
struct ponto p1 = {1, 2, -3};

Como com qualquer outro tipo, é possível declarar ponteiros para structs, e é possível alocar memória para esses ponteiros dinamicamente:

struct ponto *ptr = malloc(sizeof(struct ponto));
/* ... */
free(ptr);
ptr = NULL;

Nesse caso, poderíamos acessar o campo x desse ponto usando (*ptr).x (os parênteses são necessários por causa da precedência dos operadores). Porém, por conveniência, criou-se o operador ->, de modo que podemos escrever ptr->x, o que é equivalente, por definição, à construção anterior.
Note que podemos alocar memória para vários pontos de maneira semelhante:

struct ponto *ptr = malloc(10 * sizeof(struct ponto)); /* 10 pontos */
/* ... */
free(ptr);
ptr = NULL;

Nesse caso, ainda podemos acessar o campo x do primeiro ponto com ptr->x, mas para acessar esse mesmo campo de outro ponto (digamos, o quarto) teríamos que fazer (*(ptr+3)).x, o que equivale a ptr[3].x.

Funções podem receber e/ou retornar structs. Contudo, nos dois casos, gera-se uma cópia da struct, o que pode demandar muita memória se a struct tiver muitos campos. Por isso, costuma-se usar ponteiros para struct nesse caso. (Mas lembre-se que não podemos retornar ponteiros para variáveis locais!)

Uma struct pode conter um ponteiro para seu próprio tipo. Na verdade, essa é uma implementação comum de listas ligadas:

struct lista {
  int val; /* o valor desse elemento */
  struct lista *prox; /* ponteiro para o próximo elemento */
};

Mas como esse tipo pode declarar um ponteiro para si mesmo se a definição de struct lista ainda não terminou quando prox é declarado? A resposta tem a ver com tipos opacos.

Nota: declarações de structs são frequentemente acompanhadas de typedef, para que se possa omitir o nome struct ao usar o tipo. Por exemplo:

typedef struct ponto {
  int x;
  int y;
  int z;
} Ponto;

Nesse caso, Ponto se torna um outro nome para o tipo struct ponto. Porém, usar typedef com structs é considerado má prática por vários guias de estilo, entre eles o do Linux, pois com uma quantidade suficientemente grande de tipos, pode-se esquecer que aquele tipo é uma struct e tratá-lo como um tipo primitivo.

Nota: embora C seja uma linguagem imperativa, structs podem ser usadas juntamente com ponteiros de funções para emular a orientação a objetos, de maneira limitada.

union

Uma variável de um tipo union pode ter apenas um entre os tipos especficados em um dado momento. Esses tipos são definidos da mesma forma

union number {
  int i;
  float f;
};

Variáveis de um tipo union podem ser declaradas das mesmas maneiras que variáveis de tipo struct.
Ao contrário das structs, numa union apenas um dos campos é válido em um dado instante. Por exemplo:

union number num;

num.i = 5;
printf("%d\n", num.i); /* OK num.i é válido agora */
printf("%f\n", num.f); /* comportamento indefindo! */

num.f = 3.0;
printf("%f\n", num.f); /* OK, num.f é válido agora */
printf("%d\n", num.i); /* comportamento indefindo! */

Ou seja, se atribuirmos a um dos campos da union, qualquer tentativa de ler de qualquer outro campo gera um valor indefinido, mas a qualquer momento podemos atribuir um valor a outro campo.
Para saber qual campo da union é válido, costuma-se usar colocar a union dentro de uma struct, juntamente com uma enum que guarda o tipo:

struct numero {
  union {
    int i;
    float f;
  } valor; /* note que a union é anônima, valor é a variável, que é um campo da struct */
  enum {
    NUMERO_FLOAT,
    NUMERO_INT
  } tipo; /* enum também é anônima */
};

Por exemplo:

struct numero num;
num.valor.i = 2;
num.tipo = NUMERO_INT;
/* ... */

Nota: o tamanho de um tipo union é geralmente o tamanho do maior de seus campos.

enum

Um enum é um tipo que pode conter apenas alguns valores especificados. Por exmeplo:

enum cor_semaforo {
  VERMELHO,
  AMARELO,
  VERDE
};

Qualquer variável do tipo enum cor_semaforo pode conter apenas um desses três valores.
Cada valor da enum é internamente representado por um inteiro, e é possível atribuir valores explícitos a eles:

enum cor_semaforo {
  VERMELHO = 0,
  AMARELO = 1,
  VERDE = 2
};

Isso é um tanto parecido com

const int VERMELHO = 0;
const int AMARELO = 1;
const int VERDE = 2;
/* e então usamos int em vez de `enum cor_semaforo` */

Porém, usando-se a enum, há a garantia de que a variável só pode valer um desses três valores; o mesmo não ocorre quando se usa int.

C

Preprocessador

O preprocessamento é uma das primeiras etapas na compilação do código em C. Toda linha que começa com # é uma diretiva para o preprocessador, e se extende até o fim da linha (diferentemente da sintaxe do restante da linguagem C, que ignora quebras de linha).

O preprocessador funciona simplesmente usando substituição de texto.

#include

Inclui um arquivo. (Isso é feito com um copy-paste automático).
Se o nome do arquivo está entre < e >, ele será procurado num caminho global (/usr/include no Linux). Se o nome está entre aspas duplas, ele será procurado primeiro no diretório em que o arquivo que contém o #include está.

Nota: é uma boa prática manter as diretivas #include no começo do arquivo.

Nota: não se deve incluir um arquivo .c. Inclua apenas arquivos .h (headers). Além disso, os headers não devem conter definições de funções, mas apenas declarações; do contrário, teríamos múltiplas definições de uma função se mais de um arquivo incluir aquele header, e isso gera um erro de ligação.

Macros

#define

Define uma macro. Por exemplo:

#define MAX_SIZE 256

Dessa linha em diante, todas as ocorrências de MAX_SIZE serão substiuídas por 256. É possível também criar macros que recebem "parâmetros", como se fossem funções:

#define MAX(x, y) ((x) > (y) ? (x) : (y))

Note o uso de parênteses ao redor de todos os "parâmetros", e da expressão inteira: isso é feito para evitar que ocorram erros de precedência de operador, independentemente das expressões usadas como "parâmetro".
Com essa definição,

z = MAX(5,10);

se transforma em

z = ((5) < (10) ? (5) : (10));

O uso de macros desse tipo pode ter efeitos imprevistos, por avaliarem seu argumento mais de uma vez. Por exemplo, à primeira vista, essa parece ser uma maneira razoável de incrementar x e y ao mesmo tempo em que obtemos o máximo entre eles:

int x = 5;
int y = 3;
int z = MAX(x++, y++);
printf("x = %d, y = %d\n", x, y);

Imprime x = 7, y = 4 -- ou seja, x foi incrementado duas vezes! Isso ocorre porque a expansão de MAX, nesse caso, é:

int z = ((x++) > (y++) ? (x++) : (y++))

Ao avaliar a condição (x++) > (y++) ambas as variáveis são incrementadas. Como a condição é verdadeira, o valor da expressão se torna (x++) e x é incrementado novamente!

Nota: nomes de macros são sempre em maiúsculas, por convenção (para diferenciá-las de variáveis e funções). Porém, a própria biblioteca padrão foge dessa convenção às vezes (por exemplo, a "função" putc de stdio.h é na verdade uma macro).

do {...} while(0)

Outro efeito imprevisto surge com o uso de if em macros com diversos comandos (cada comando termina com um ;):

#define INCR_THREE(x,y,z) (x)++; (y)++; (z)++

Ou equivalentemente, colocando cada comando em uma linha:

#define INCR_THREE(x,y,z) \
  (x)++; \
  (y)++; \
  (z)++

(Note o uso de \ ao final de cada linha: isso faz com que o compilador veja todas a linha seguinte como uma continuação da atual. Isso é necessário porque o pré-processador entende que suas diretivas terminam com o fim da linha.)

Quando o if tem apenas um comando, é possível omitir as chaves; porém, visualmente, pode parecer que

if(condicao)
  INCR_THREE(x, y, z);

Está correto, porque só há um ; no final; porém, esse código expande para

if(condicao)
  x++;
  y++;
  z++;

Então apenas o primeiro comando (x++) está dentro do if. Há duas maneiras de corrigir isso: a primeira é usar sempre as chaves no if, mesmo quando poderiam ser omitidas; a segunda é colocar macros que tenham vários comandos dentro de um do...while(0):

#define INCR_THREE(x,y,z) \
do { \
  (x)++; \
  (y)++; \
  (z)++ \
} while(0)

O corpo de um do...while(0) será executado apenas uma vez; logo, esse loop não é realmente um loop: seu único efeito é fazer com que todo o corpo da macro conte como um único comando. (Além disso, o compilador provavlemente eliminará o loop ao otimizar o código.)

# e ## em macros

Caso o argumento de uma macro seja precedido por #, coloca-se aspas duplas ao redor do argumento passado, de modo que ele se torna uma string literal. Por exemplo:

/* note o uso de # antes do x */
#define PRINT_VARNAME(x) printf("minha variavel se chama %s", #x)

Com essa definição,

PRINT_VARNAME(my_var);

Expande para

printf("minha variavel se chama %s", "my_var");

Já o ## permite concatenar o argumento da macro com outro nome:

#define ADD_GENERIC(X) add_##X

Com essa definição,

ADD_GENERIC(int)(x, y);

Expande para

add_int(x, y);

#undef

Desfaz um #define anterior.

Compilação condicional

Estas diretivas são usadas para se obter compilação condicional - ou seja, certos trechos de código podem ser usados ou omitidos em um arquivo se certas condições forem atendidas.

#ifdef, #ifndef, #endif

O trecho de código entre o #ifdef e o #endif será usado se, e só se, a macro nomeada após o #ifdef estiver definida. A macro #ifndef é similar, mas o código será usado se a macro não está definida (IF Not DEFined).

Por exemplo:

int main(void)
{
#ifdef FOO
  printf("foo!\n");
#endif
  printf("bar!\n");
  return 0;
}

Se a macro FOO estiver definida (com um uso anterior de #define), a função main se tornará, depois do pré-processamento,

int main(void)
{
  printf("foo!\n");
  printf("bar!\n");
  return 0;
}

Do contrário, o código entre as linhas do #ifdef e do #endif será omitido, e teremos

int main(void)
{
  printf("bar!\n");
  return 0;
}

A compilação condicional tem diversos usos. Por exemplo, é comum ver este trecho de código no começo dos headers em C:

#ifdef __cplusplus
extern "C" {
#endif

E este trecho no final:

#ifdef __cplusplus
}       /* C++ */
#endif

Se a macro __cplusplus estiver definida, entende-se que estamos compilando em C++, logo todo o código do header é colocado dentro de um bloco extern C { ... } (isso é necessário para evitar erros de ligação, porque C++ tem suporte para overload de funções - funções com mesmo nome mas com protótipos diferentes).

Header guards

Considere um header foo.h com o seguinte código:

/* foo.h */
struct foo {
  int a;
  int b;
};

E um arquivo foo.c como segue:

/* foo.c */
#include "foo.h"
#include "foo.h"

/* use struct foo de algum modo */

foo.c não compila, porque a inclusão do header duas vezes faz com que a definição de struct foo apareça duas vezes (redefinição de struct não é permitido, ainda que as definições sejam idênticas).

Nesse caso, podemos detectar o erro facilmente e remover o segundo #include. Mas considere que temos também um segundo header, bar.h, como segue:

/* bar.h */

#include "foo.h"

struct bar {
  struct foo f;
  char *nome;
};

Esse header define um tipo que tem uma instância de struct foo; por isso, o header deve ter a definição desse tipo, o que se obtem incluindo foo.h. (É comum ter um header que inclui outro header, não só para usar tipos definidos no outro, mas também macros, protótipos de função etc.)
Suponha agora que queremos criar usar ambas structs em foo.c. Como cada uma foi declarada em um header, é natural fazer:

#include "foo.h"
#include "bar.h"

/* use struct foo e struct bar */

...mas esse código não compila, pelo mesmo motivo do anterior: redefinição de struct foo. Poderíamos resolver isso incluir apenas bar.h, mas esse header pode ser mudado no futuro e não mais incluir foo.h; além disso, manter nota de quais headers incluem quais se torna rapidamente impraticável à medida que o número de headers aumenta.
A solução mais prática é usar um header guard em foo.h:

/* foo.h */
#ifndef FOO_H
#define FOO_H

struct foo {
  int a;
  int b;
};

#endif

Um header guard protege contra múltiplos #includes: se o header não foi incluído antes, então supõe-se que a macro do header guard (no caso, FOO_H) nunca foi declarada, logo todo o código do header (entre o #ifndef na primeira linha e o #endif na última) é usado. Já na primeira linha dentro desse código, essa macro é definida; assim, da próxima vez que o header for incluído, o #ifndef omitirá todo o código do header.

Nota: por convenção, a macro do header guard tem o mesmo nome do header, mas em maiúsculas e com a extensão .h substituída por _H.

Nota: o header guard só funciona se a macro usada não é definida em nenhum outro lugar; por isso, tome cuidado ao declarar uma macro cujo nome termine em _H.

#if

Para condições mais complexas, podemos usar a diretiva #if. Por exemplo:

#if CHAR_BIT != 8
#error "non 8-bit chars are not supported!"
#endif

Isso gera um erro de compilação (com a diretiva #error) se a macro CHAR_BIT não está definida ou tem qualquer outro valor que não seja 8.

#ifdef FOO e #ifndef FOO são equivalentes a #if defined(FOO) e if !defined(FOO) respectivamente. Podemos usar isso juntamente com && e ||, por exemplo:

#if defined(FOO) || !defined(BAR)
/* ou FOO está definido, ou BAR não está (ou os 2) */

#else

É possível também usar a diretiva #else para que um trecho alternativo de código seja usado se a condição de um #if ou #ifdef for falsa. Por exemplo, isso permite usar funções típicas de um sistema operacional em outros sistemas:

/* queremos usar strdup(), mas essa função só está implementada em sistemas UNIX.
 * Nesses sistemas, queremos usar a implementação que já está pronta; nos demais, 
 * temos que implementar "na mão".
 */

#ifdef __UNIX__
/* em UNIX, a função strdup() está implementada: use-a */
#	define my_strdup strdup
#else
/* em outros sistemas, implemente a função */
char *my_strdup(const char *s)
{
  /* ... */
}
#endif

Se a macro __UNIX__ estiver definida, o código que está entre o #ifdef e o #else será usado - ou seja, my_strdup se torna um outro nome para strdup, e o código entre o #else e o #endif será descartado. Se a macro não estiver definida, ocorre o oposto, e usa-se a implementação fornecida para my_strdup.

Misc

#error

Essa diretiva gera um erro de compilação, com a mensagem fornecida. Por exemplo:

#ifndef MY_AWESOME_MACRO
#	error "compilacao falhou: MY_AWESOME_MACRO nao esta definida!"
#endif

É quase sempre usada juntamente com as diretivas de compilação condicional (do contrário, a compilação do código sempre falharia).

#pragma

O efeito dessa diretiva depende da implementação. Por exemplo, alguns compiladores suportam header guards da forma

#pragma once

O problema dessa diretiva é justamente o fato de seu efeito ser dependente da implementação, então não se pode contar que um #pragma vá ter o mesmo efeito em qualquer compilador.

#

Essa diretiva não faz nada. (Ela é normalmente usada entre duas diretivas "de verdade", para dar um efeito visual de quebra de linha.)

C

Biblioteca padrão

I/O (stdio.h)

printf

Imprime uma string com formatação na saída padrão. A formatação começa com um %, e deve terminar com um dos caracteres abaixo (só os mais importantes foram listados).

É preciso passar para printf argumentos correspondentes à formatação usada. Por exemplo:

int x, y;
/* leia os valores de x e y de algum lugar */
printf("x = %d, y = %d\n", x, y);
C

Tópicos avançados

O ponteiro genérico void*

Em C, o tipo void* é especial: uma variável desse tipo é um ponteiro genérico, ou seja, um ponteiro para qualquer tipo.

Qualquer ponteiro pode ser convertido implicitamente para void*, e vice-versa:

int *p;
void *vp;
int x;

p = &x;
vp = p; /* OK: conversão implícita de int* para void* */
vp = &x; /* OK, pelo mesmo motivo (a expressão &x tem tipo int*) */
p = vp; /* OK: conversão implícita de void* para int* */

É importante notar que perdemos informação ao converter um ponteiro T* (em que T é um tipo qualquer) para void*: quando temos T*, sabemos o endereço em que a variável começa e sabemos o tamanho do tipo T (ou seja, sizeof(T)). Ao converter esse ponteiro para void*, perdemos a informação sobre o tipo original T, logo não sabemos mais qual o tamanho. Por essa razão, não podemos dereferenciar, nem incrementar ou decrementar um ponteiro void*:

void *vp;
int *p;
int x;

vp = &x;
p = &x;

*p = 5; /* OK: copia sizeof(int) bytes a partir do endereço em p */
*vp = 5; /* ERRO! Não sabemos quantos bytes temos que copiar */

p += 2; /* OK: aumenta o valor do endereço de p em 2 * sizeof(int) */
vp += 2; /* ERRO! Não sabemos em qual valor devemos aumentar o endereço */

(Nota: alguns compiladores, como o gcc, permitem fazer aritmética com ponteiros void*, mas isso é uma extensão não-portável.)

Ponteiros void* são usados sempre que se quer uma variável de tipo genérico. Por exemplo, uma implementação genérica de lista duplamente ligada em C poderia ser simplesmente:

struct lista {
  struct lista *next;
  struct lista *prev;
  void *data;
};

As funções malloc e calloc têm, ambas, tipo de retorno void*. Normalmente, usamos essas funções para alocar espaço suficiente para N variáveis de algum tipo, usando sizeof:

struct my_struct *foo = malloc(100 * sizeof(struct my_struct));

Porém, nada nos impede de fazer:

char *mem = malloc(40); /* aloca 40 bytes */

Ou seja, a função malloc não tem - e não precisa ter - nenhuma informação sobre o tipo que pretendemos alocar: só importa que a função receba o número total de bytes a ser alocado, e devolva o endereço do começo da região com esse tamanho. Então, como nenhuma informação sobre o tipo é usada, é natural que o tipo de retorno seja void*.
Da mesma forma, a função free recebe um void*: novamente, não importa o tipo do ponteiro que foi alocado, apenas seu endereço.

Nota: dissemos que void* é um tipo especial porque, em C,

T *p;

Declara p como um ponteiro para T. Seguindo esse padrão,

void *p;

Deveria declarar um ponteiro para o tipo void, o qual não tem nenhum valor - ou seja, um ponteiro para nada. Mas um ponteiro como esse não teria nenhuma utilidade! Logo, decidiu-se usar void* como ponteiro genérico em vez disso.

Ponteiro para função

Funções também ocupam um lugar na memória, e por isso é possível ter ponteiros para funções. Por exemplo:


void foo1(int x, int y)
{
  printf("foo1: x = %d, y = %d\n", x, y);
}

void foo2(int x, int y)
{
  printf("foo2: x = %d, y = %d\n", x, y);
}

int main(void)
{
  /* declara um ponteiro para função que recebe dois ints e não retorna nada */
  void (*fptr)(int, int);
  
  fptr = foo1;
  fptr(1, 2);
  fptr = foo2;
  fptr(3, 4);

  return 0;
}

Imprime:

foo1: x = 1, y = 2
foo2: x = 3, y = 4

Podemos notar, no exemplo acima, que

  1. o protótipo da função faz parte do tipo do ponteiro para função; e
  2. a chamada a um ponteiro de função tem a mesma sintaxe que uma chamada de função normal.

Na verdade, a mesma relação entre arrays e ponteiros (ambos guardam o endereço de seu primeiro elemento) existe entre uma função "normal" e um ponteiro de função:

void foo(int x, int y)
{
  printf("foo: x = %d y = %d\n", x, y);
}

int main(void)
{
  void (*fptr)(int, int);
  
  fptr = foo;
  printf("foo = %p, fptr = %p\n", foo, fptr);
}

Imprime o mesmo endereço para foo e para fptr.

Callbacks

Um callback é um ponteiro para função que é passado para outra função, a fim de ser chamado numa ocasião específica. Callbacks são muito usados em programação orientada a eventos.
Frequentemente, a função que recebe um callback pertence a uma biblioteca, mas o callback em si é escrito pelo usuário. Para se possa ter um callback que lide com tipos definidos pelo usuário - e que não são conhecidos pela biblioteca -, os parâmetros do ponteiro de função devem ter tipo void*.
Um exemplo disso é a função qsort da biblioteca padrão, que implementa o quicksort. Seu protótipo é:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

Note que a função recebe um ponteiro de função compar, que por sua vez recebe dois ponteiros const void*. A página de manual de qsort explica que

O conteúdo da array é ordenado em ordem ascendente, de acordo com a função de comparação apontada por compar, que é chamada com dois argumentos que apontam para os objetos sendo comparados. [...]
A função de comparação deve retornar um inteiro menor que, igual a, ou maior que zero se o primeiro argumento é considerado, respectivamente, menor que, igual a ou maior que o segundo.

Então compar é o nosso callback, e como essa função recebe parâmetros de tipo const void*, podemos usá-la com tipos que nós definimos. Graças a isso, a função qsort pode ser usada com qualquer tipo definido pelo usuário, sem ter que se preocupar com sua lógica de comparação.

Um exemplo de uso dessa função seria:


/* guarda uma array de inteiros e o número de elementos que ela tem */
struct vec {
        int *a;
        size_t elem_num;
};

/* Queremos comparar dois vetores da seguinte forma:
 * v1 é considerado maior que v2 se a soma das entradas de v1
 * for maior que a soma das entradas de v2.
 */

/* NOTA: só chame esta função com ponteiros para struct vec! */
int vec_cmp(const void *p1, const void *p2)
{
  const struct vec *v1 = p1;
  const struct vec *v2 = p2;

  int i, sum1 = 0, sum2 = 0;

  for(i = 0; i < v1->elem_num; i++) {
    sum1 += v1->a[i];
  }
  for(i = 0; i < v2->elem_num; i++) {
    sum2 += v2->a[i];
  }
  /* este valor é maior, igual ou menor que zero
   * se sum1 é menor, igual ou menor que sum2, respetivamente
   */
  return sum1 - sum2;
}

int main(void)
{
  struct vec *v;
  size_t num_elem;
  /* leia v e num_vec de algum lugar (e.g. de um arquivo) 
   * de tal modo que v tenha num_elem elementos 
   */

  qsort(v, num_elem, sizeof(struct vec), vec_cmp);

  /* faca algo com os vetores em ordem */

  /* libere a memória alocada */
  return 0;
}

Orientação a objetos

É possível emular em C - com certas limitações - features presentes em linguagens de programação orientadas a objetos.
Usaremos structs para emular o funcionamento de uma classe.

Fábrica (Factory) / Construtor

Podemos emular uma fábrica como uma função que retorna um ponteiro alocado dinamicamente para a nossa struct.

Por exemplo, poderíamos ter, em um header vec3.h:

/* um vetor tridimensional (R3). */
struct vec3 {
  double x;
  double y;
  double z;
};

/* Nota ao usuário: este método deve ter seu valor de retorno dealocado
 * manualmente com free().
 */
struct vec3 *cria_vec3(double x, double y, double z); /* fábrica */

E, no arquivo de implementação vec3.c:

struct vec3 *cria_vec3(double x, double y, double z)
{
  struct vec3 *v = malloc(sizeof(struct vec3));
  if(v == NULL) {
    return NULL;
  }
  
  v->x = x;
  v->y = y;
  v->z = z;

  return v;
}

Podemos também criar algo semelhante a um construtor - uma função que inicializa os campos de uma struct recém-criada, e não retorna nada:

void init_vec3(double x, double y, double z)
{
  v->x = x;
  v->y = y;
  v->z = z;
}

Nesse caso, a responsabilidade de alocar a memória fica com o código do usuário. Além disso, o usuário deve se lembrar de chamar essa função para cada variável desse tipo, após ser criada.

Método estático

Um método estático é um método que pertence à classe, não a um objeto específico, e por isso não precisa acessar seu ponteiro this.

No exemplo anterior, poderíamos adicionar a vec3.h o protótipo:

/*
 * este método chama cria_vec3() e por isso seu valor de retorno também deve ser
 * manualmente dealocado.
 */
struct vec3 *soma_vec3(struct vec3 *v1, struct vec3 *v2); /* método estático! */

E na implementação, em vec3.c, teríamos

struct vec3 *soma_vec3(struct vec3 *v1, struct vec3 *v2); 
{
  return cria_vec3(v1->x + v2->x, v1->y + v2->y, v1->z + v2->z);
}

Declarações estranhas: a Regra da Espiral

Declarações em C podem ser crípticas, especialmente quando envolvem ponteiros de função. Considere:

const int (*const foo)[64](int, int);

Isso declara a variável foo - mas qual o seu tipo? A resposta curta é recorrer a este site. A resposta longa é usar a Regra da Espiral, que imita como o compilador analisará a expressão.

Partindo da variável cujo tipo queremos descobrir, percorremos a expressão em espirais de dentro para fora, em sentido horário:

const int (*const foo)[64](int, int);
                  +++^

(Os símbolos + indicam a parte da expressão que já analisamos.)

Encontramos um ), logo estamos dentro de uma sub-expressão com parênteses, e devemos voltar até o respectivo (:

((ter algum texto aqui é necessário para preservar o whitespace no começo da linha abaixo))

          v-------+++
const int (*const foo)[64](int, int);
                  +++^

Então foo é um ponteiro constante, mas para o que? Percorremos a expressão com uma nova espiral, partindo de toda a sub-expressão que já analisamos:

const int (*const foo)[64](int, int);
          ++++++++++++^

Encontramos um [, então devemos ir adiante até o respectivo ]:

const int (*const foo)[64](int, int);
          ++++++++++++^--^

Então foo é um ponteiro constante para uma array de 64 elementos, mas de que tipo? Outra vez, uma nova espiral:

const int (*const foo)[64](int, int);
          ++++++++++++++++^

Encontramos um (, então devemos ir adiante até o respectivo ):

const int (*const foo)[64](int, int);
          ++++++++++++++++^--------^

Então foo é um ponteiro constante para uma array de 64 ponteiros de função que recebem dois ints, mas qual o tipo de retorno?

const int (*const foo)[64](int, int);
          ++++++++++++++++++++++++++^

Achamos o fim da expressão (;), então voltamos para o começo:

vvvvvvvvv-++++++++++++++++++++++++++
const int (*const foo)[64](int, int);
          ++++++++++++++++++++++++++^

A expressão inteira foi analisada.
Portanto, foo é um ponteiro constante para uma array de 64 ponteiros de função que recebem dois ints e retornam um const int.

Tipos opacos

structs e unions podem ser declarados sem ser definidos:

struct foo;

Um tipo como esse - declarado mas não definido - é chamado de tipo opaco. A príncipio, o compilador não sabe o tamanho nem os campos desse tipo, o que gera algumas restrições em seu uso:

struct foo;

/* OK: podemos definir ponteiro para tipo opaco,
 * porque todo ponteiro tem o mesmo tamanho
 */
struct foo *ptr;
struct foo var; /* ERRO! Não podemos instanciar tipo opaco */

int main(void)
{
  ptr->stuff = 1; /* ERRO! Não podemos acessar nenhum membro de um tipo opaco */
  *ptr; /* ERRO! Não podemos de-referenciar ponteiro para tipo opaco */
  size_t foo_size = sizeof(struct foo); /* ERRO! Tamanho desconhecido! */ 
  return 0;
}

Com tantas restrições, por que usar um tipo opaco? A resposta: esses tipos permitem obter um certo nível de encapsulamento de dados. Por exemplo, ao criar uma biblioteca, é possível colocar, em um header (.h) que será incluído pelo usuário, apenas a declaração do tipo opaco e protótipos de funções que lidam com ponteiros para esse tipo, enquanto a definição do tipo e das funções iria nos arquivos-fonte (.c). Com isso, os usuários dessa biblioteca podem manipular os dados do tipo opaco por meio das funções fornecidas, e não precisam se preocupar com os detalhes de implementação do tipo.

Nota: um tipo opaco só pode se usado quando tem uma definição completa em outro arquivo, que deve ser unido (pelo linker) com o primeiro.

Funções variádicas

Considere as seguintes chamadas a printf:

printf("foo\n"); /* OK */
printf("%d\n", 3); /* OK */
printf("%f %f %f\n", 1.2, 3.4, 5.6); /* OK */

Como é possível que uma mesma função seja chamada com número e tipos de argumentos diferentes?
Isso ocorre porque printf é uma função variádica. Uma função variádica é indicada pelo uso de reticências (...) em seu protótipo; no caso de printf,

int printf(const char *format, ...);

Todos os argumentos antes das reticências têm tipo determinado e são obrigatórios a toda chamada a essa função; a partir das reticências, entende-se que haverá um número arbitrário de argumentos (que pode ser zero), de qualquer tipo.
Logo, as seguintes chamadas estão incorretas:

printf(); /* ERRO! */
printf(3.1); /* ERRO! */
printf(1, 2, 3); /* ERRO! */

Porque em nenhuma delas o primeiro argumento de printf é do tipo const char*.

Funções variádicas devem, obrigatoriamente, ter ao menos o primeiro argumento com tipo especificado. Logo, a declaração a seguir não é válida:

void variadic(...); /* ERRO! Primeiro argumento não especificado */

O compilador não tem nenhuma informação sobre os argumentos à função depois das reticências, mas a função em si precisa saber quais argumentos foram passados a ela para que possa usá-los devidamente. No caso de printf, essa informação está embutida na string de formatação - por exemplo, se há um %d, entende-se que foi passado um inteiro; se há um %f, um float, e assim por diante. Porém, o compilador não checa se os argumentos passados a uma função variádica correspondem aos que a função espera, de modo que todas essas chamadas compilam (talvez com warnings):

printf("abc\n", 5); /* OK, o segundo argumento não será usado */
printf("%d\n"); /* undefined behaviour, possível falha de segurança */
printf("%f\n", "abcd"); /* tenta imprimir o endereço da string literal como um float */

Importante: chamadas do tipo printf("%d"); em que printf espera mais argumentos do que foram passados, constituem uma falha de segurança; por isso, não permita que a string de formatação passada para printf dependa do input do usuário, a menos que esse input seja sanitizado antes (por exemplo, susbtiuindo todos os % por %%).

Funções variádicas são implementadas usando-se uma variável do tipo va_list, definido no header stdarg.h. Essa variável deve ser inicializada pela macro va_start e liberada por va_end; entre as duas chamadas, podemos usar a macro va_arg para extrair um argumento com um dado tipo. Como exemplo, temos uma implementação simplificada de printf:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

/* versão simplificada de printf() que não retorna nada, e entende apenas
 * as formatações %s, %c, %d, %f e %%
 */
void my_printf(const char *fmt, ...)
{
  int in_format = 0;
  va_list args; /* nossa lista de argumentos */ 

  /* fmt é a última variável conhecida pela função, e deve ser passada a va_start
   * para que a macro saiba onde a lista de argumentos variádicos deve começar
   */
  va_start(args, fmt);

  for(; *fmt; fmt++) {
    if(in_format) {
      in_format = 0; /* todas as nossas formatações têm apenas 1 letra */

      switch(*fmt) {
        case '%':
          fputc('%', stdout);
        break;
        case 's':
        {
          /* va_arg extrai um argumento do tipo dado (no caso, char*) e o retorna,
           * de tal modo que a próxima chamada a va_arg retornará o próximo argumento
           * da lista, e assim por diante.
           * Note que o compilador não pode checar se o tipo do argumento passado
           * é compatível com o que a função espera.
           */
          char *s = va_arg(args, char*);
          fputs(s, stdout);
        }
        break;
        case 'c':
        {
          char c = (char) va_arg(args, int); /* TODO explicar por que extraimos o argumento como int */
          fputc(c, stdout);
        }
        break;
        case 'd':
        {
          int i = va_arg(args, int);
          /* converta i para string e imprima com fputs() */
        }
        break;
        case 'f':
        {
          double d = va_arg(args, double);
          /* converta d para string e imprima com fputs() */
        }
        break;
        default:
          /* formatação inválida, ignore. */
      }
    } else if(*fmt == '%') {
      in_format = 1;
    } else {
      in_format = 0;
      fputc(*fmt, stdout);
    }
  }
  va_end(args); /* terminamos de usar a varáivel */
}

O operador ,

O operador , avalia seus dois operandos, e tem como resultado o valor do último operando (da direita), descartando o valor do outro. Por exemplo:

int main(void)
{
  int w, x, y, z;
  z = (w = 1, x = 2, y = 3);
  printf("w = %d, x = %d, y = %d, z = %d\n", w, x, y, z);
  return 0;
}

Imprime

w = 1, x = 2, y = 3, z = 3

A expressão é avaliada como: z = ((w = 1, x = 2), y = 3) -> z = ((1, 2), 3) -> z = (2, 3) -> z = 3 -> 3.

Um uso comum desse operador é em laços for que têm mais de um índice:

/* suponha que temos uma array quadrada arr de tamanho arrsize */
double sum_diag = 0;
int i, j;

/* note o uso do operador ',' para manipular os dois indices ao mesmo tempo */
for(i = 0, j = 0; i < arrsize && j < arrsize; i++, j++) {
  sum_diag += arr[i][j];
}
printf("soma das entradas da diagonal principal: %g\n", sum_diag);

Por ser conta-intuitivo, esse operador pode ser usado para obfuscação de código.

Inline assembly

TODO

C

Gambiarras (18+)

Orientação a objetos: Parte 2

Herança / Composição

Essencialmente, é possível forjar uma herança entre duas classes das seguintes maneiras:

  1. Copiando-se todos os campos da classe mãe para a classe filha;
  2. Usando-se composição em vez disso, colocando-se uma instância da classe mãe na classe filha
  3. Também com composição, mas usando-se um ponteiro para a classe mãe, que deve ser alocado quando a filha for instanciada.

Todos os métodos têm suas desvantagens: em (1), uma mudança feita na classe mãe não se propaga automaticamente para a filha; em (2), qualquer tipo de Fábrica que pretendemos usar deve saber quais os campos da classe mãe para inicializá-la, e novamente isso pode mudar; em (3), podemos reutilizar a fábrica da classe mãe, mas temos que garantir que o ponteiro para a classe mãe será alocado quando o objeto da classe filha for criada (a maneira mais fácil seria criar uma fábrica para a classe filha).

Encapsulamento

Podemos obter uma struct em que alguns elementos são conhecidos pelo cliente e outros não, semelhante a campos public e private - usando-se uma struct cujos campos são públicos dentro de outra cujos campos são privados. Considere, por exemplo, este header foo.h:

struct foo {
  int pub; /* campo público */
};

/* funções públicas para manipular struct foo */
struct foo *create_foo(int pub); /* NÃO destrua com free(), use destroy_foo() abaixo! */
int increment_private(struct foo*);
void destroy_foo(struct foo *fpub);

E este arquivo de implementação foo.c:

#include <stdlib.h>
#include <stddef.h> /* offsetof() */
#include "foo.h"

/* tipo privado: não é acessível ao usuário (porque não está no header) */
struct foo_private {
  int priv;
  struct foo foo_public;
};

struct foo *create_foo(int pub)
{
  /* note que alocamos espaço para foo_private, não apenas foo */
  struct foo_private *fpriv = malloc(sizeof(struct foo_private));
  fpriv->priv = 0;
  fpriv->foo_public.pub = pub;
  
  /* pode parecer que perderemos o ponteiro que alocamos quando a função acabar, 
   * mas ainda é possível acessá-lo usando-se offsetof()
   */
  return &fpriv->foo_public;
}

int increment_private(struct foo *fpub)
{
  /* primeiro, temos que fazer cast para o tipo privado para poder acessar seus campos.
   * A macro offsetof calcula o tamanho em bytes entre o começo de uma struct e
   * a posição de algum de seus campos. Com isso e com fpub, podemos recuperar
   * o endereço da struct foo_private.
   * Note o cast para void*: isso impede que seja gerado um endereço errado em função do
   * tipo do ponteiro.
   */
  struct foo_private *fpriv = ((void*)fpub - offsetof(struct foo_private, foo_public));
  (fpriv->priv)++;
}

void destroy_foo(struct foo *fpub)
{
  /* como alocamos um foo_private, é isso que devemos desalocar. */
  struct foo_private *fpriv = ((void*)fpub - offsetof(struct foo_private, foo_public));
  free(fpriv);
}

Métodos não-estáticos

TODO com encapsulamento (o ponteiro this é privado) e ponteiros para funções que recebem this como parâmetro

Templates

(Obrigado ao Vitor Silva por me ensinar esse truque!)

Templates são tipos genéricos em C++. É possível imitar o comportamento dos templates - mas sem type safety - usando o preprocessador. Por exemplo, podemos criar uma lista genérica em um header list.h como:

#ifndef LIST_H
#define LIST_H

#include <stdlib.h>

#define LIST(X) struct list_##X

#define DEF_LIST(X) \
    LIST(X) { \
        X elem; \
        LIST(X) *next; \
    }; \
    LIST(X) *create_list_##X(X first) \
    { \
        LIST(X) *ptr = malloc(sizeof(LIST(X))); \
        if (ptr == NULL) { return NULL; } \
        ptr->elem = first; \
        ptr->next = NULL; \
        return ptr; \
    } \
    void destroy_list_##X(LIST(X) *ptr) \
    { \
        while (ptr != NULL) { \
            LIST(X) *next = ptr->next; \
            free(ptr); \
            ptr = next; \
        } \
    } \
    int insert_list_##X(LIST(X) *list, X elem) \
    { \
        LIST(X) *cell = malloc(sizeof(LIST(X))); \
        if (cell == NULL) { return 0; } \
        cell->elem = elem; \
        cell->next = list->next; \
        list->next = cell; \
        return 1; \
    }

#endif

Note o uso extenso de ## nas macros.

Podemos testar nosso "template" com o seguinte arquivo:

#include <stdio.h>
#include "list.h"

/* instancia o "template" de lista de int */
DEF_LIST(int)

int main(int argc, char **argv)
{
    LIST(int) *list = create_list_int(42);

    insert_list_int(list, -12);
    insert_list_int(list, 35);

    LIST(int) *cur;
    for(cur = list; cur != NULL; cur = cur->next) {
        printf("%d\n", cur->elem);
    }

    destroy_list_int(list);
    return 0;
}

Funcional: o que dá para fazer

Funcional: o que não dá para fazer

Shellcode em uma variável

É possível executar os bytes de uma variável como instruções em linguagem de máquina, fazendo um cast do tipo da variável para um ponteiro de função, e chamando-o em seguida:

#include <stdio.h>

int main(void) {
  /*
   * Shellcode para Linux x86-64:
   *      mov rax, 60 ; sys_exit
   *      xor rdi, rdi
   *      syscall
   */
  unsigned char shellcode[] = {0xb8, 0x3c, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x0f, 0x05};
  
  printf("antes do shellcode\n");
  ((void(*)(void)) shellcode)(); /* executa os bytes em shellcode como instruções */
  printf("depois do shellcode\n"); /* nunca é impresso */
  return 0;
}

Por padrão, os executáveis em Linux não permitem que se executem valores na pilha como instruções; para fazer isso, é preciso passar a flag -zexecstack para o gcc. Do contrário, o código acima gera um Segmentation fault.

Assembly x86-64

Introdução ao assembly, escrita por mim, em inglês: https://gitlab.com/luamfb/intro_x86-64

Assembly x86-64

Sobre

O que é assembly? O que é x86-64? Por que aprender isso?

Todas essas perguntas estão respondidas no README.

Assembly x86-64

0 - Básico

Este arquivo explica o básico de assembly.

Assembly x86-64

1 - I/O

Este arquivo explica como funciona I/O (input/output) em assembly.

Assembly x86-64

2 - Endereçamento de memória

Este arquivo explica como funcionam acessos a memória em assembly.

Assembly x86-64

3 - Jumps

Este arquivo explica como funcinam jumps (desvios) condicionais e incondicionais, e como eles implementam loops (como while) e branches (if-else).

Assembly x86-64

4 - Funções folha

Este arquivo explica as funções folha: funções que não chamam nenhuma outra função.

Assembly x86-64

5 - Funções não-folha

Este arquivo explica as funções não-folha (que chamam outras funções) e por que elas são diferentes das funções folha.

Assembly x86-64

6 - libc

Este arquivo explica como chamar funções da biblioteca padrão de C (e por extensão, de qualquer biblioteca escrita em C).

Assembly x86-64

7 - Ponto Flutuante

Este arquivo explica como lidar com ponto flutuante em assembly usando diferentes precisões (float, double e long double).