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.
- 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,MaineMAINsã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 receita | Em C | Exemplo |
|---|---|---|
| Um passo | Uma instrução | printf("Oi\n"); |
| O ponto final do passo | O ponto e vírgula | ; |
| Um grupo de passos ("para a cobertura:") | Um bloco, entre chaves | { … } |
| O título da receita | O nome da função | int 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
| Linha | Leva ;? | Por quê |
|---|---|---|
#include <stdio.h> | Não | Linhas 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ão | As chaves já marcam o começo e o fim do bloco. |
printf("…");, return 0; | Sim | Sã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
}damainnã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 ntfnão éprintf, ereturn0(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
#includee as definições de funções, como a própriamain. 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
printfe oreturn.
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
| Regra | Exemplo |
|---|---|
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 baixo | trê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 diferentes | main ≠ Main; printf ≠ Printf |
| Espaços e linhas entre os pedaços do código são livres | printf ( "Oi\n" ) ; funciona |
| Espaço dentro de um nome quebra o nome | pri 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ções | printf fora da main é erro |
| Recue um nível a cada bloco | 4 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:
2. O nome certo
O programa tem int MAIN(void) {. Como deve ser escrito o nome da função?
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):
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ítuloint 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,returne#includesã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ó
#includee definições. - Muitos erros de uma vez? Conserte o primeiro e compile de novo.
- Próximo passo: Comentários.