C Sintaxe

Toda língua tem gramática: em português, a frase termina com ponto, os nomes próprios começam com maiúscula, e assim por diante. Uma linguagem de programação também tem a sua, e ela se chama sintaxe. A diferença é que o compilador não "entende o que você quis dizer": se a regra não for seguida, ele se recusa a criar o programa. A boa notícia é que as regras do C que você precisa agora são poucas. Esta aula mostra cada uma, com o erro real que aparece quando ela é quebrada.

Você vai aprender
  • O que é uma instrução e por que ela termina com ;, e onde o ; não vai.
  • Os blocos: o que as chaves { } agrupam.
  • Por que main, Main e MAIN são coisas diferentes.
  • Onde os espaços e as quebras de linha importam, e onde o compilador nem liga.
  • O recuo (indentação) e os dois jeitos de posicionar a {.
  • O que pode e o que não pode ficar fora da main.

Os exemplos foram compilados na máquina fictícia da Ana com o comando padrão do guia, gcc -std=c23 -Wall -Wextra (aula Compilar com gcc), na pasta ~/c.


A ideia: a gramática de uma receita

Uma receita bem escrita tem passos curtos, um depois do outro, cada um terminado com ponto. Às vezes os passos se agrupam: "Para a cobertura: derreta o chocolate; acrescente o creme". O programa em C é igual:

Na receitaEm CExemplo
Um passoUma instruçãoprintf("Oi\n");
O ponto final do passoO ponto e vírgula;
Um grupo de passos ("para a cobertura:")Um bloco, entre chaves{ … }
O título da receitaO nome da funçãoint main(void)

Aqui está o programa que vai servir de exemplo, a lista de compras da Ana:

#include <stdio.h>

int main(void) {
    printf("Lista de compras:\n");
    printf("- arroz\n");
    printf("- feijão\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra compras.c -o compras
$ ./compras
Lista de compras:
- arroz
- feijão

1. Instruções e o ponto e vírgula

Uma instrução (em inglês, statement) é uma ordem completa para o computador: "mostre este texto", "termine e devolva 0". No programa acima, há quatro: três printf e o return. Elas rodam uma depois da outra, de cima para baixo, na ordem em que aparecem.

Em C, toda instrução termina com ;. O compilador não usa a quebra de linha para saber onde a instrução acaba: ele procura o ponto e vírgula. Por isso, esquecer um ; faz o compilador "emendar" duas instruções:

#include <stdio.h>

int main(void) {
    printf("Olá, mundo!\n")
    return 0;
}
$ gcc -std=c23 -Wall -Wextra semponto.c -o semponto
semponto.c: In function ‘main’:
semponto.c:4:28: error: expected ‘;’ before ‘return’
    4 |     printf("Olá, mundo!\n")
      |                            ^
      |                            ;
    5 |     return 0;
      |     ~~~~~~                  

"Esperava ; antes do return". O compilador leu o printf(…), não achou o ;, continuou lendo, deu de cara com o return da linha de baixo e só aí reclamou. Repare que ele aponta o lugar exato (linha 4, coluna 28) e desenha o ; que falta.

Como quem manda é o ;, e não a linha, você pode até escrever várias instruções na mesma linha (não é bonito, mas funciona). E um ; sozinho é uma "instrução vazia", que não faz nada:

#include <stdio.h>

int main(void) {
    printf("Oi\n");;
    ;
    return 0;
}
$ gcc -std=c23 -Wall -Wextra vazio.c -o vazio
$ ./vazio
Oi

Compilou sem reclamar: os ; a mais foram ignorados. Mas cuidado: no lugar errado, um ; a mais muda o sentido do programa (você viu o if (nota >= 7); na aula Compilar com gcc).

Onde não vai ponto e vírgula

LinhaLeva ;?Por quê
#include <stdio.h>NãoLinhas com # são ordens para o pré-processador, não instruções do C. Terminam na quebra de linha.
int main(void)NãoÉ o "título" da função; logo depois vem o corpo, entre chaves.
{ e }NãoAs chaves já marcam o começo e o fim do bloco.
printf("…");, return 0;SimSão instruções.

O que acontece se puser ; onde não vai? Depois do #include, o gcc só avisa (extra tokens = "símbolos a mais"):

$ cat inc.c
#include <stdio.h>;

int main(void) {
    printf("Oi\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra inc.c -o inc
inc.c:1:19: warning: extra tokens at end of ‘#include’ directive
    1 | #include <stdio.h>;
      |                   ^

Depois do int main(void), é erro: o ; encerra a linha ali, e a { da linha seguinte fica "solta", sem dono:

$ cat pontomain.c
#include <stdio.h>

int main(void);
{
    printf("Oi\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pontomain.c -o pontomain
pontomain.c:4:1: error: expected identifier or ‘(’ before ‘{’ token
    4 | {
      | ^

2. Blocos: as chaves { }

As chaves agrupam instruções num bloco. O bloco que você já conhece é o corpo da main: tudo entre a { depois de int main(void) e a } correspondente pertence a ela.

Blocos podem ficar dentro de outros blocos. Um bloco solto, sem nada antes, é permitido e só agrupa as instruções; mais para a frente, os blocos vão aparecer depois do if (aula if e else) e dos laços (aula while), e aí sim fazem diferença:

#include <stdio.h>

int main(void) {
    printf("Antes do bloco\n");
    {
        printf("Dentro do bloco\n");
    }
    printf("Depois do bloco\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra blocos.c -o blocos
$ ./blocos
Antes do bloco
Dentro do bloco
Depois do bloco

As regras das chaves:

  • Toda { tem a sua }, como parênteses numa conta.
  • A } fecha a { aberta mais recente: os blocos se encaixam, nunca se cruzam.
  • Depois da } da main não vai ;.

Se faltar a } do fim, o compilador chega ao final do arquivo esperando mais:

$ cat semchave.c
#include <stdio.h>

int main(void) {
    printf("Lista de compras:\n");
    return 0;
$ gcc -std=c23 -Wall -Wextra semchave.c -o semchave
semchave.c: In function ‘main’:
semchave.c:5:5: error: expected declaration or statement at end of input
    5 |     return 0;
      |     ^~~~~~

At end of input = "no fim da entrada", ou seja, no fim do arquivo. O compilador esperava mais alguma coisa (no caso, a }) e o arquivo acabou.


3. Maiúsculas e minúsculas fazem diferença

Para o C, main, Main e MAIN são três nomes completamente diferentes, tanto quanto casa e cama. Dizemos que o C é sensível a maiúsculas e minúsculas (em inglês, case-sensitive). Quase tudo do C se escreve em minúsculas: int, main, void, return, printf, #include.

Main em vez de main

#include <stdio.h>

int Main(void) {
    printf("Olá!\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra maiuscula.c -o maiuscula
/usr/bin/x86_64-linux-gnu-ld.bfd: /usr/lib/gcc/x86_64-linux-gnu/15/../../../x86_64-linux-gnu/Scrt1.o: na função "_start":
(.text+0x1b): referência não definida para "main"
collect2: error: ld returned 1 exit status

Para o compilador, Main é uma função comum, com um nome qualquer, e está tudo certo com ela. Quem reclama é o ligador (ld), a última etapa: ele procura a main, onde o programa começa, e não acha. A mensagem saiu em português porque o ligador do Ubuntu é traduzido; em inglês, ela diz undefined reference to `main'.

Printf em vez de printf

$ cat printf2.c
#include <stdio.h>

int main(void) {
    Printf("Olá!\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra printf2.c -o printf2
printf2.c: In function ‘main’:
printf2.c:4:5: error: implicit declaration of function ‘Printf’; did you mean ‘printf’? [-Wimplicit-function-declaration]
    4 |     Printf("Olá!\n");
      |     ^~~~~~
      |     printf

O stdio.h trouxe uma função chamada printf, e ninguém apresentou ao compilador uma tal de Printf. A mensagem diz "declaração implícita da função Printf", ou seja, "você usou uma função que eu não conheço", e sugere: did you mean ‘printf’?, "você quis dizer printf?".

Int, #Include e RETURN

$ gcc -std=c23 -Wall -Wextra int2.c -o int2          « Int main(void) {
int2.c:3:1: error: unknown type name ‘Int’; did you mean ‘int’?
    3 | Int main(void) {
      | ^~~
      | int
$ gcc -std=c23 -Wall -Wextra ret.c -o ret            « RETURN 0;
ret.c: In function ‘main’:
ret.c:5:5: error: ‘RETURN’ undeclared (first use in this function)
    5 |     RETURN 0;
      |     ^~~~~~
ret.c:5:5: note: each undeclared identifier is reported only once for each function it appears in
ret.c:5:11: error: expected ‘;’ before numeric constant
    5 |     RETURN 0;
      |           ^~
      |           ;

Int virou um "nome de tipo desconhecido" e RETURN virou um nome "não declarado" (undeclared), como se fosse uma variável que ninguém criou. E #Include?

$ gcc -std=c23 -Wall -Wextra inc2.c -o inc2          « #Include <stdio.h>
inc2.c:1:2: error: invalid preprocessing directive #Include; did you mean #include?
    1 | #Include <stdio.h>
      |  ^~~~~~~
      |  include
inc2.c: In function ‘main’:
inc2.c:4:5: error: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
    4 |     printf("Oi\n");
      |     ^~~~~~
inc2.c:1:1: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’
  +++ |+#include <stdio.h>
    1 | #Include <stdio.h>
inc2.c:4:5: warning: incompatible implicit declaration of built-in function ‘printf’ [-Wbuiltin-declaration-mismatch]
    4 |     printf("Oi\n");
      |     ^~~~~~
inc2.c:4:5: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’

Um erro só gerou vários: como o #Include não valeu, o stdio.h não entrou, e aí o printf também ficou desconhecido. Isso é muito comum: conserte o primeiro erro e compile de novo, porque os outros costumam sumir junto (aula Lendo Erros do Compilador).

Missão: este programa tem dois problemas de maiúsculas. Rode, leia as mensagens e corrija até ele mostrar Bom dia, Ana!.

#include <stdio.h>

int main(void) {
    Printf("Bom dia, Ana!\n");
    Return 0;
}
#include <stdio.h>

int main(void) {
    printf("Bom dia, Ana!\n");
    return 0;
}

Repare que as maiúsculas importam no código, não no texto entre aspas: dentro das aspas você escreve o que quiser ("Bom dia, Ana!" ou "BOM DIA"), e é exatamente isso que aparece na tela.


4. Espaços, linhas e recuo: o compilador não liga, mas você sim

Entre os "pedaços" do código (nomes, símbolos, números), o compilador ignora espaços, tabulações e quebras de linha. Este programa horrível compila e faz exatamente o mesmo que o compras.c:

#include <stdio.h>

int
main
(
void
)
{
        printf   (    "Lista de compras:\n"   )   ;
printf("- arroz\n");
                    printf("- feijão\n");
    return
    0
    ;
}
$ gcc -std=c23 -Wall -Wextra espacos.c -o espacos
$ ./espacos
Lista de compras:
- arroz
- feijão

E este também, tudo numa linha só depois do #include:

#include <stdio.h>
int main(void){printf("Lista de compras:\n");printf("- arroz\n");printf("- feijão\n");return 0;}
$ gcc -std=c23 -Wall -Wextra umalinha.c -o umalinha
$ ./umalinha
Lista de compras:
- arroz
- feijão

Mas há lugares em que o espaço importa:

  • Dentro de um nome, espaço quebra o nome em dois: pri ntf não é printf, e return0 (sem espaço) não é return 0.
  • Dentro das aspas, cada espaço aparece na tela, exatamente como você escreveu.
  • As linhas com # ocupam uma linha inteira só para elas.
$ gcc -std=c23 -Wall -Wextra junto.c -o junto        « return0;
junto.c: In function ‘main’:
junto.c:5:5: error: ‘return0’ undeclared (first use in this function)
    5 |     return0;
      |     ^~~~~~~
junto.c:5:5: note: each undeclared identifier is reported only once for each function it appears in

O texto entre aspas não pode quebrar de linha

Um texto entre aspas tem de abrir e fechar na mesma linha. Se você apertar Enter no meio dele, o compilador se perde feio:

$ cat quebra.c
#include <stdio.h>

int main(void) {
    printf("Lista
de compras\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra quebra.c -o quebra
quebra.c: In function ‘main’:
quebra.c:4:12: warning: missing terminating " character
    4 |     printf("Lista
      |            ^
quebra.c:4:12: error: missing terminating " character
    4 |     printf("Lista
      |            ^~~~~~
quebra.c:5:1: error: ‘de’ undeclared (first use in this function)
    5 | de compras\n");
      | ^~
quebra.c:5:1: note: each undeclared identifier is reported only once for each function it appears in
quebra.c:5:3: error: expected ‘)’ before ‘compras’
    5 | de compras\n");
      |   ^~~~~~~~
      |   )
quebra.c:4:11: note: to match this ‘(’
    4 |     printf("Lista
      |           ^
quebra.c:5:11: error: stray ‘\’ in program
    5 | de compras\n");
      |           ^
quebra.c:5:13: warning: missing terminating " character
    5 | de compras\n");
      |             ^
quebra.c:5:13: error: missing terminating " character
    5 | de compras\n");
      |             ^~~
quebra.c:6:14: error: expected ‘;’ before ‘}’ token
    6 |     return 0;
      |              ^
      |              ;
    7 | }
      | ~             

Seis erros por causa de um Enter. O primeiro é o que importa: missing terminating " character, "falta o caractere " de fechamento" na linha 4. Depois disso, o compilador tentou ler de compras como código, e tudo desandou.

Se o texto é comprido e você quer dividi-lo em várias linhas do código, feche as aspas e abra outras na linha de baixo. O C junta textos entre aspas que aparecem um ao lado do outro:

#include <stdio.h>

int main(void) {
    printf("Lista de compras da Ana: "
           "arroz, feijão, café\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra concat.c -o concat
$ ./concat
Lista de compras da Ana: arroz, feijão, café

O recuo (indentação) é para pessoas

O compilador não liga para o recuo, mas você liga, e quem for ler o seu código também. A regra é simples: tudo o que está dentro de um bloco fica recuado um nível a mais que as chaves. O guia usa 4 espaços por nível; 2 espaços, 8 espaços ou a tecla Tab também são comuns. O importante é ser consistente.

#include <stdio.h>

int main(void) {                    // nível 0: a main
    printf("Antes do bloco\n");     // nível 1: dentro da main
    {
        printf("Dentro do bloco\n");  // nível 2: dentro do bloco
    }
    return 0;
}

(O texto depois de // é um comentário, uma anotação que o compilador ignora: é o assunto da próxima aula, Comentários.)

Sem recuo, o programa funciona igual, mas fica difícil ver onde cada bloco começa e termina, e erros como uma chave esquecida passam despercebidos. A maioria dos editores de código recua sozinha quando você aperta Enter depois de uma {.

Onde fica a {?

Existem dois estilos populares, e os dois compilam igual:

Chave na mesma linha (o do guia)Chave na linha de baixo (estilo Allman)
int main(void) {
    printf("Oi\n");
    return 0;
}
int main(void)
{
    printf("Oi\n");
    return 0;
}
A { fica no fim da linha do título. Ocupa menos linhas e é muito comum em C.A { fica sozinha na linha de baixo. Ocupa mais linhas, mas as chaves ficam alinhadas.

Há também uma mistura dos dois, a do livro de Kernighan e Ritchie (os criadores do C) e do código do Linux: a chave das funções fica na linha de baixo, e a dos if e dos laços fica na mesma linha.

Qual usar? O guia usa a chave na mesma linha em tudo. Num projeto que já existe, siga o estilo que ele já usa: misturar os dois no mesmo arquivo é que atrapalha.


5. O que fica dentro e o que fica fora da main

Um programa em C tem duas "regiões":

  • Fora de qualquer função (no "nível de cima" do arquivo): os #include e as definições de funções, como a própria main. Mais tarde, também outras funções (aula Funções) e algumas variáveis (aula Escopo).
  • Dentro de uma função: as instruções, como os printf e o return.

Uma instrução solta, fora de qualquer função, é erro: o compilador não saberia quando rodá-la.

$ cat fora.c
#include <stdio.h>

printf("Olá!\n");

int main(void) {
    return 0;
}
$ gcc -std=c23 -Wall -Wextra fora.c -o fora
fora.c:3:8: error: expected declaration specifiers or ‘...’ before string constant
    3 | printf("Olá!\n");
      |        ^~~~~~~~

A mensagem é confusa (ela tenta interpretar a linha como a declaração de uma função), mas a linha 3 indica o lugar. O conserto é levar o printf para dentro das chaves da main.

Resumindo, todo programa do Nível 1 tem este esqueleto:

#include <stdio.h>          // 1. os cabeçalhos, no topo

int main(void) {            // 2. a função principal
    // 3. as instruções, uma por linha, cada uma com ;
    return 0;               // 4. o fim da main
}                           // 5. a chave que fecha a main

Quando dá errado

expected ‘;’ before …

O que significa: faltou um ;. O erro costuma apontar para o fim da linha anterior à que o compilador cita depois do before ("antes de").

Como consertar: olhe o fim da linha indicada (e a de cima) e acrescente o ;.

expected declaration or statement at end of input

O que significa: o arquivo acabou com alguma { ainda aberta.

Como consertar: confira se cada { tem a sua }. O recuo ajuda: a } fica alinhada com a linha que abriu o bloco.

referência não definida para "main" (undefined reference to `main')

O que significa: não existe nenhuma função chamada exatamente main: talvez esteja Main, MAIN ou mian.

Como consertar: int main(void), tudo em minúsculas.

implicit declaration of function ‘Printf’; did you mean ‘printf’?

O que significa: o nome da função está escrito errado (maiúscula, letra trocada) ou falta o #include do cabeçalho dela. No gcc 15, com o C23, isso é erro; em compiladores antigos era só um aviso. No editor do guia, o Clang diz use of undeclared identifier 'Printf'; did you mean 'printf'? ("uso de um nome não declarado; você quis dizer printf?").

Como consertar: escreva o nome exato (printf) e confira o #include <stdio.h>.

missing terminating " character

O que significa: um texto entre aspas não fechou na mesma linha.

Como consertar: feche as aspas antes do fim da linha. Para quebrar a linha na tela, use \n dentro das aspas; para dividir um texto comprido no código, feche e abra aspas de novo ("Lista " "de compras").

expected declaration specifiers or ‘...’ before string constant

O que significa: há uma instrução fora de qualquer função.

Como consertar: mova a linha para dentro das chaves da main.


Referência rápida

RegraExemplo
Toda instrução termina com ;printf("Oi\n");
Linhas com #, títulos de funções e chaves não levam ;#include <stdio.h>, int main(void), {, }
As instruções rodam de cima para baixotrês printf = três linhas, na ordem
Chaves agrupam instruções num bloco; toda { tem a sua }{ printf("a\n"); printf("b\n"); }
Maiúsculas e minúsculas são diferentesmain ≠ Main; printf ≠ Printf
Espaços e linhas entre os pedaços do código são livresprintf ( "Oi\n" ) ; funciona
Espaço dentro de um nome quebra o nomepri ntf, return0 são erros
Texto entre aspas abre e fecha na mesma linha"Lista " "de compras" para dividir
Instruções só dentro de funçõesprintf fora da main é erro
Recue um nível a cada bloco4 espaços por nível, no guia

Exercícios

1. O que falta?

Complete a instrução com o símbolo que falta no fim:

printf("Feijão\n")

2. O nome certo

O programa tem int MAIN(void) {. Como deve ser escrito o nome da função?

int (void) {

3. Dividir o texto

Complete para o printf mostrar Bom dia, Ana! com o texto dividido em duas linhas do código (sem Enter dentro das aspas):

printf("Bom dia, "
       

4. Qual destas linhas está errada?

5. O compilador mostrou 6 erros de uma vez. O que fazer primeiro?

6. Sobre o recuo (os espaços no começo das linhas), é verdade que:

Missão final: arrume a bagunça

Missão: a Ana escreveu a lista de compras com pressa. Há quatro problemas: uma maiúscula errada, um ; esquecido, um texto que quebrou de linha e uma chave que falta. Conserte até a saída ficar assim, sem avisos, e aproveite para deixar o código bem recuado:

Lista de compras:
- arroz
- feijão
- café
#include <stdio.h>
int main(void) {
printf("Lista de compras:\n");
Printf("- arroz\n");
printf("- feijão\n")
printf("-
café\n");
return 0;
#include <stdio.h>

int main(void) {
    printf("Lista de compras:\n");
    printf("- arroz\n");
    printf("- feijão\n");
    printf("- café\n");
    return 0;
}

Resumo

  • A sintaxe é a gramática do C. Se ela não for seguida, o compilador não cria o programa.
  • Toda instrução termina com ;. Linhas com #, o título int main(void) e as chaves não levam ;.
  • As instruções rodam de cima para baixo. As chaves agrupam instruções em blocos, e toda { tem a sua }.
  • Maiúsculas importam: main, printf, int, return e #include são sempre em minúsculas.
  • Espaços e quebras de linha entre os pedaços são livres; dentro de um nome ou de um texto entre aspas, não. Um texto entre aspas abre e fecha na mesma linha.
  • Recue um nível a cada bloco: o compilador não precisa, mas as pessoas sim.
  • Instruções ficam dentro de funções; fora delas, só #include e definições.
  • Muitos erros de uma vez? Conserte o primeiro e compile de novo.
  • Próximo passo: Comentários.