Um bloco é um conjunto de instruções e declarações agrupadas numa única unidade sintática. Os blocos são frequentemente definidos dentro de chavetas, mas nem sempre. Os blocos podem estar aninhados dentro de funções e dentro de outros blocos.
No exemplo seguinte, number é definido dentro da função main.
flag é definido dentro do bloco if, que está aninhado dentro da função main.
As variáveis definidas dentro de um bloco só são válidas dentro desse bloco.
Por isso, tentar compilar o código origina um erro de compilação.
#include <stdio.h>
int main() {
int number = 1;
if (number == 1) {
int flag = 1;
printf("%d", flag);
}
// this line generates a compilation error
// error: 'flag' undeclared (first use in this function)
printf("%d", flag);
}
A mensagem de erro pode ser confusa, porque flag é usada mais acima na função.
No entanto, flag está definida dentro de um bloco que existe dentro da função.
O bloco interior consegue «ver» a variável number da função exterior, mas a função exterior não consegue «ver» a variável flag definida dentro do seu bloco interior.
Assim que a execução sai do bloco, a variável flag é automaticamente destruída e deixa de estar disponível para o programa.
A própria função main vê o identificador flag pela primeira vez na última instrução printf.
Se a última instrução printf for removida, o programa compila e imprime 1 quando executado.
Isto acontece porque a variável flag é válida no bloco que contém a primeira instrução printf.
A partir do C99, as instruções if, for, while e do de uma só linha também são consideradas blocos.
No exemplo seguinte, embora i não esteja definido dentro de chavetas, só existe no bloco definido pela instrução for e não é visível para a função main exterior.
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i+=2)
printf("%d", i);
// this line generates a compilation error
// error: 'i' undeclared (first use in this function)
printf("%d", i);
}
Se a última instrução printf for removida, o programa compila e imprime 0 2 4 6 8 10 quando executado.
Algumas linguagens, como Go e Rust, são explícitas ao dizer que as instruções condicionais e de ciclo de uma só linha constituem um bloco.
Exigem que essas instruções de uma só linha continuem a ser envolvidas em chavetas.
Uma função comporta-se um pouco como um bloco. O identificador de uma variável definida numa função não pode ser visto fora da função, tal como o identificador de uma variável definida num bloco não pode ser visto fora do bloco. Duas variáveis em duas funções podem ter o mesmo nome, uma vez que as duas funções não conseguem ver uma para dentro da outra, tal como duas variáveis em dois blocos podem ter o mesmo nome, uma vez que os dois blocos não conseguem ver um para dentro do outro. Mas as funções e os blocos não são idênticos. Uma função não pode ser definida dentro de outra função, mas um bloco pode estar aninhado dentro de outro bloco. E alguns identificadores de um bloco podem ser vistos fora do bloco. No exemplo seguinte, o código não compila
#include <stdio.h>
int main() {
{
int i = 0;
begin:
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
begin:
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto begin;
}
}
}
O código acima não compila, dando o seguinte erro
/tmp/gH2TVYnTSJ.c: In function 'main':
/tmp/gH2TVYnTSJ.c:17:5: error: duplicate label 'begin'
17 | begin:
| ^~~~~
/tmp/gH2TVYnTSJ.c:7:5: note: previous definition of 'begin' was here
7 | begin:
| ^~~~~
Embora i possa ser definido em cada bloco, os nomes de label têm de ser únicos numa função.
Isto acontece porque labels têm âmbito de função, ou seja, cada rótulo é visto pela função independentemente do local onde está definido dentro dela.
Por isso, ao contrário das variáveis, onde quer que um label esteja definido numa função tem de ter um nome único dentro dessa mesma função.
Para o código funcionar, é preciso mudar o nome de um dos labels, para que ambos sejam únicos, como no exemplo seguinte
#include <stdio.h>
int main() {
{
int i = 0;
begin:
// prints
// i == 0
// i == 1
printf("i == %d\n", i);
if (i == 0) {
i = 1;
goto begin;
}
}
{
int i = 1;
start:
// prints
// i == 1
// i == 0
printf("i == %d\n", i);
if (i == 1) {
i = 0;
goto start;
}
}
}
As instruções de uma só linha podem ser «empilhadas» e continuar dentro de um único bloco sem usar chavetas.
No exemplo abaixo, a instrução for contém uma instrução if, que por sua vez contém uma instrução printf.
Embora haja duas instruções no total dentro do bloco for, só a sua instrução if de uma só linha é avaliada como corpo do ciclo for.
Por sua vez, a instrução printf de uma só linha da instrução if é avaliada como o seu corpo.
Se cada bloco aninhado tiver apenas uma linha, nenhum dos blocos precisa de chavetas.
Repara que o comentário não precisa de chavetas para o envolver, a ele e à instrução printf dentro do bloco if, uma vez que o comentário não é uma instrução.
A indentação deixa claro o que se passa, mas uma equipa de desenvolvimento pode ter as suas próprias normas de codificação sobre a colocação de chavetas nestas situações, para eliminar qualquer tipo de ambiguidade.
#include <stdio.h>
int main() {
for (int i = 0, j = 11; i < j; i++)
if (i & 1 == 1)
// prints 1 3 5 7 9
printf("%d ", i);
}
Vimos que os valores definidos dentro de um bloco não podem ser acedidos a partir de fora do bloco.
E vimos que os valores definidos fora de um bloco podem normalmente ser acedidos a partir de dentro do bloco.
É possível que uma variável de fora de um bloco fique oculta dentro do bloco, se o identificador dessa variável for usado para definir uma variável do bloco.
No exemplo seguinte, o identificador number é usado tanto para uma variável ao nível da função como para uma variável ao nível do bloco.
Dizemos que a variável ao nível do bloco esconde a variável ao nível da função, ou lhe faz sombra.
#include <stdio.h>
int main() {
int number = 1;
// prints number in function is 1
printf("number in function is %d\n", number);
if (number == 1) {
// the definition of a block-level variable using the same name
// shadows the function-level variable
int number = 2;
// prints number in function is 2
printf("number in if block is %d\n", number);
}
// prints number in function is 1
printf("number in function is %d\n", number);
}
Os compiladores podem gerar um erro se se tentar fazer sombra a um parâmetro de uma função. O seguinte exemplo
#include <stdio.h>
void print_number(int number) {
// will fail to compile
int number = 2;
printf("number in function is %d\n", number);
}
int main() {
int number = 1;
printf("number in main is %d\n", number);
print_number(number);
printf("number in main is %d\n", number);
}
origina os seguintes erros em duas plataformas
/tmp/jIvhD5UOop.c: In function 'print_number':
/tmp/jIvhD5UOop.c:5:9: error: 'number' redeclared as different kind of symbol
5 | int number = 2;
| ^~~~~~
/tmp/jIvhD5UOop.c:3:23: note: previous definition of 'number' was here
3 | void print_number(int number) {
| ~~~~^~~~~~
e
redefinition of formal parameter 'number'