Un bloque es un conjunto de sentencias y declaraciones agrupadas en una sola unidad sintáctica. Los bloques a menudo, aunque no siempre, se definen entre llaves. Los bloques pueden anidarse dentro de funciones y dentro de otros bloques.
En el siguiente ejemplo, number se define dentro de la función main.
flag se define dentro del bloque if, que está anidado dentro de la función main.
Las variables definidas dentro de un bloque solo son válidas dentro de ese bloque.
Por lo tanto, intentar compilar el código da como resultado un error de compilación.
#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);
}
El mensaje de error puede resultar confuso, ya que flag sí se usa antes en la función.
Sin embargo, flag se define dentro de un bloque que está dentro de la función.
El bloque interno puede «ver» la variable number de su función externa, pero la función externa no puede «ver» la variable flag definida dentro de su bloque interno.
Una vez que la ejecución sale del bloque, la variable flag se destruye automáticamente y deja de estar disponible para el programa.
La propia función main ve el identificador flag por primera vez en la última sentencia printf.
Si se elimina la última sentencia printf, el programa compilará e imprimirá 1 al ejecutarse.
Eso se debe a que la variable flag es válida en el bloque que contiene la primera sentencia printf.
A partir de C99, las sentencias if, for, while y do de una sola línea también se consideran bloques.
En el siguiente ejemplo, aunque i no se define entre llaves, solo existe en el bloque definido por la sentencia for, y no es visible para la función 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);
}
Si se elimina la última sentencia printf, el programa compilará e imprimirá 0 2 4 6 8 10 al ejecutarse.
Algunos lenguajes, como Go y Rust, dejan explícito que las sentencias condicionales y de bucle de una sola línea constituyen un bloque.
Exigen que ese tipo de sentencias de una sola línea sigan encerradas entre llaves.
Una función se comporta de manera parecida a un bloque. El identificador de una variable definida en una función no puede verse fuera de la función, del mismo modo que el identificador de una variable definida en un bloque no puede verse fuera del bloque. Dos variables en dos funciones pueden tener el mismo nombre, ya que las dos funciones no se ven entre sí, del mismo modo que dos variables en dos bloques pueden tener el mismo nombre, ya que los dos bloques no se ven entre sí. Pero las funciones y los bloques no son idénticos. Una función no puede definirse dentro de otra función, pero un bloque sí puede anidarse dentro de otro bloque. Y algunos identificadores de un bloque pueden verse fuera del bloque. En el siguiente ejemplo, el código no 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;
}
}
}
El código anterior no compila y da el siguiente error
/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:
| ^~~~~
Aunque i puede definirse en cada bloque, los nombres de label deben ser únicos dentro de una función.
Eso se debe a que los labels tienen ámbito de función, lo que significa que la función ve cada etiqueta sin importar dónde se defina dentro de la función.
Por eso, a diferencia de las variables, sin importar dónde se defina un label dentro de una función, debe tener un nombre único dentro de esa misma función.
Para que el código funcione hay que cambiar el nombre de uno de los labels para que ambos sean únicos, como en el siguiente ejemplo
#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;
}
}
}
Las sentencias de una sola línea pueden «apilarse» y seguir dentro de un mismo bloque sin necesidad de usar llaves.
En el ejemplo de abajo, la sentencia for contiene una sentencia if que, a su vez, contiene una sentencia printf.
Aunque en el bloque for hay dos sentencias en total, solo su sentencia if de una sola línea se evalúa como el cuerpo del bucle for.
La sentencia printf de una sola línea de la sentencia if, a su vez, se evalúa como su cuerpo.
Si cada bloque anidado ocupa solo una línea, ninguno de los bloques necesita llaves.
Fíjate que el comentario no necesita llaves que lo encierren a él y a la sentencia printf dentro del bloque if, ya que el comentario no es una sentencia.
La indentación deja claro lo que está pasando, pero un equipo de desarrollo puede tener sus propias guías de estilo sobre cómo colocar las llaves en esas situaciones para eliminar cualquier tipo de ambigüedad.
#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);
}
Hemos visto que a los valores definidos dentro de un bloque no se puede acceder desde fuera del bloque.
Y hemos visto que, por lo general, sí se puede acceder desde dentro de un bloque a los valores definidos fuera de él.
Es posible que una variable de fuera de un bloque quede oculta dentro del bloque si el identificador de esa variable se usa al definir una variable del bloque.
En el siguiente ejemplo, el identificador number se usa tanto para una variable a nivel de función como para una variable a nivel de bloque.
Decimos que la variable a nivel de bloque oculta o sombrea la variable a nivel de función.
#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);
}
Los compiladores pueden generar un error si se intenta sombrear un parámetro de función. El siguiente ejemplo
#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);
}
da como resultado los siguientes errores en dos 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) {
| ~~~~^~~~~~
and
redefinition of formal parameter 'number'