Bl

Blocos em C

{one: "1 exercício", many: "%{count} de exercícios", other: "%{count} exercícios"}

Sobre Blocos

Um bloco é um conjunto de instruções e declarações agrupadas em uma única unidade sintática. Blocos são frequentemente, mas nem sempre, definidos entre chaves. Blocos podem ser aninhados dentro de funções e dentro de outros blocos.

No exemplo a seguir, number é definido dentro da função main. flag é definido dentro do bloco if, que está aninhado dentro da função main. Variáveis definidas dentro de um bloco só são válidas dentro desse bloco. Por isso, tentar compilar o código resulta em 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, já que flag é usada antes na função. No entanto, flag está definida dentro de um bloco dentro da função. O bloco interno consegue "ver" a variável number na função externa, mas a função externa não consegue "ver" a variável flag definida dentro do bloco interno. Assim que a execução sai do bloco, a variável flag é destruída automaticamente 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 vai compilar e imprimir 1 quando executado. Isso acontece porque a variável flag é válida no bloco que contém a primeira instrução printf.

A partir do C99, instruções if, for, while e do de uma única linha também são consideradas blocos. No exemplo a seguir, embora i não seja definida entre chaves, ela existe apenas no bloco definido pela instrução for e não é visível para a função main externa.

#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 vai compilar e imprimir 0 2 4 6 8 10 quando executado. Algumas linguagens, como Go e Rust, deixam explícito que instruções condicionais e de laço de uma única linha constituem um bloco. Elas exigem que essas instruções de uma única linha continuem entre chaves.

Uma função funciona um pouco como um bloco. Um identificador de uma variável definida em uma função não pode ser visto fora da função, assim como um identificador de uma variável definida em um bloco não pode ser visto fora do bloco. Duas variáveis em duas funções podem ter o mesmo nome, já que as duas funções não se enxergam por dentro, assim como duas variáveis em dois blocos podem ter o mesmo nome, já que os dois blocos não se enxergam por dentro. Mas funções e blocos não são idênticos. Uma função não pode ser definida dentro de outra função, mas um bloco pode ser aninhado dentro de outro bloco. E alguns identificadores em um bloco podem ser vistos fora do bloco. No exemplo a seguir, o código não vai compilar

#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 e gera 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 definida em cada bloco, os nomes de label precisam ser únicos em uma função. Isso acontece porque labels têm escopo de função, ou seja, cada rótulo é visto pela função, não importa onde ele esteja definido dentro dela. Então, diferentemente das variáveis, não importa onde um label seja definido em uma função: ele precisa ter um nome único dentro dessa mesma função. Para fazer o código funcionar, é preciso mudar o nome de um dos labels para que ambos sejam únicos, como no exemplo a seguir

#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;
        }
    }

}

Instruções de uma única linha podem ser "empilhadas" e ainda assim ficar dentro de um mesmo bloco sem o uso de chaves. 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, apenas a instrução if de uma única linha é avaliada como o corpo do laço for. A instrução printf de uma única linha da instrução if, por sua vez, é avaliada como o corpo dela. Se blocos aninhados tiverem cada um apenas uma linha, nenhum dos blocos precisa de chaves. Repare que o comentário não precisa de chaves para envolver a si mesmo nem a instrução printf dentro do bloco if, já que um comentário não é uma instrução. A indentação deixa claro o que está acontecendo, mas uma equipe de desenvolvimento pode ter suas próprias diretrizes de código sobre onde colocar chaves em situações como essa, para eliminar qualquer 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 valores definidos dentro de um bloco não podem ser acessados de fora do bloco. E vimos que valores definidos fora de um bloco geralmente podem ser acessados de dentro dele. É possível que uma variável de fora de um bloco fique escondida dentro dele se o identificador dessa variável for usado para definir uma variável do bloco. No exemplo a seguir, o identificador number é usado tanto para uma variável de nível de função quanto para uma variável de nível de bloco. Dizemos que a variável de nível de bloco esconde, ou sombreia, a variável de nível de função.

#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);
}

Compiladores podem gerar um erro se um parâmetro de função for sombreado. O exemplo a seguir

#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);
}

resulta nos 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'
Editar via GitHub O link abre em uma nova janela ou aba