Блок - це набір інструкцій та оголошень, згрупованих в одну синтаксичну одиницю. Блоки часто, але не завжди, визначають у фігурних дужках. Блоки можна вкладати у функції та в інші блоки.
У наступному прикладі number визначено всередині функції main.
flag визначено всередині блоку if, вкладеного у функцію main.
Змінні, визначені в блоці, дійсні лише в межах цього блоку.
Тож спроба скомпілювати цей код призводить до помилки компіляції.
#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);
}
Повідомлення про помилку може заплутати, адже flag справді використовується раніше у функції.
Однак flag визначено в блоці всередині функції.
Внутрішній блок може «бачити» змінну number у своїй зовнішній функції, але зовнішня функція не може «бачити» змінну flag, визначену в її внутрішньому блоці.
Щойно виконання виходить за межі блоку, змінна flag автоматично знищується й стає недоступною для програми.
Сама функція main уперше бачить ідентифікатор flag в останній інструкції printf.
Якщо прибрати останню інструкцію printf, програма скомпілюється й під час запуску виведе 1.
Це тому, що змінна flag дійсна в блоці, який містить першу інструкцію printf.
Починаючи з C99, однорядкові інструкції if, for, while і do також вважаються блоками.
У наступному прикладі, хоч i і не визначено у фігурних дужках, вона існує лише в блоці, визначеному інструкцією for, і не видима для зовнішньої функції main.
#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);
}
Якщо прибрати останню інструкцію printf, програма скомпілюється й під час запуску виведе 0 2 4 6 8 10 .
Деякі мови, як-от Go та Rust, прямо зазначають, що однорядкові умовні інструкції та інструкції циклів утворюють блок.
Вони вимагають, щоб такі однорядкові інструкції все одно були в фігурних дужках.
Функція поводиться дещо подібно до блоку. Ідентифікатор змінної, визначеної у функції, не видно поза цією функцією, так само як ідентифікатор змінної, визначеної в блоці, не видно поза цим блоком. Дві змінні у двох функціях можуть мати однакове імʼя, бо ці дві функції не заглядають одна в одну, так само як дві змінні у двох блоках можуть мати однакове імʼя, бо ці два блоки не заглядають один в одного. Але функції та блоки не тотожні. Функцію не можна визначити всередині іншої функції, а блок можна вкласти в інший блок. І деякі ідентифікатори з блоку можна побачити поза блоком. У наступному прикладі код не скомпілюється
#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;
}
}
}
Наведений вище код не компілюється й видає таку помилку
/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:
| ^~~~~
Хоча i можна визначити в кожному блоці, імена label у межах функції мають бути унікальними.
Це тому, що labels мають область видимості функції, тобто кожну мітку бачить функція, де б у ній її не визначили.
Тож, на відміну від змінних, де б у функції не визначили label, його імʼя має бути унікальним у межах цієї самої функції.
Щоб код запрацював, треба змінити імʼя однієї з labels, щоб обидві були унікальними, як у наступному прикладі
#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;
}
}
}
Однорядкові інструкції можна «нанизувати» одна на одну, і вони все одно залишатимуться в межах одного блоку без фігурних дужок.
У прикладі нижче інструкція for містить інструкцію if, яка своєю чергою містить інструкцію printf.
Хоча в блоці for загалом дві інструкції, як тіло циклу for виконується лише його однорядкова інструкція if.
Однорядкова інструкція printf цієї інструкції if своєю чергою виконується як її тіло.
Якщо кожен із вкладених блоків складається лише з одного рядка, жодному з блоків не потрібні фігурні дужки.
Зауважмо, що коментар не потребує фігурних дужок, щоб охопити його та інструкцію printf у блоці if, бо коментар не є інструкцією.
Відступи роблять зрозумілим, що відбувається, але команда розробників може мати власні правила щодо розміщення фігурних дужок у таких випадках, щоб усунути будь-яку неоднозначність.
#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);
}
Ми бачили, що значення, визначені в блоці, недоступні поза блоком.
І бачили, що значення, визначені поза блоком, зазвичай доступні зсередини блоку.
Змінна поза блоком може бути прихована всередині блоку, якщо ідентифікатор цієї змінної використано для визначення змінної блоку.
У наступному прикладі ідентифікатор number використано і для змінної рівня функції, і для змінної рівня блоку.
Кажуть, що змінна рівня блоку приховує, або затіняє, змінну рівня функції.
#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);
}
Компілятори можуть видавати помилку, якщо спробувати затінити параметр функції. Наступний приклад
#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);
}
спричиняє такі помилки на двох платформах
/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) {
| ~~~~^~~~~~
і
redefinition of formal parameter 'number'