A blokk utasítások és deklarációk egyetlen szintaktikai egységbe csoportosított halmaza. A blokkokat gyakran, de nem mindig kapcsos zárójelek közé zárjuk. A blokkok függvényeken belül és más blokkokon belül is egymásba ágyazhatók.
A következő példában a number a main függvényen belül van definiálva.
A flag abban az if blokkban van definiálva, amely a main függvényen belül van egymásba ágyazva.
A blokkon belül definiált változók csak azon a blokkon belül érvényesek.
Ezért ha megpróbáljuk lefordítani a kódot, fordítási hibát kapunk.
#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 hibaüzenet zavaró lehet, hiszen a flag tényleg használatban van korábban a függvényben.
A flag viszont a függvényen belüli egyik blokkban van definiálva.
A belső blokk „látja” a külső függvényében lévő number változót, a külső függvény viszont nem „látja” a belső blokkjában definiált flag változót.
Amint a végrehajtás kilép a blokkból, a flag változó automatikusan megsemmisül, és a program számára többé nem elérhető.
A main függvény maga az utolsó printf utasításban találkozik először a flag azonosítóval.
Ha eltávolítjuk az utolsó printf utasítást, a program lefordul, és futtatáskor 1-et ír ki.
Ez azért van, mert a flag változó abban a blokkban érvényes, amely az első printf utasítást tartalmazza.
A C99 óta az egysoros if, for, while és do utasítások is blokknak számítanak.
A következő példában bár az i nincs kapcsos zárójelek között definiálva, csak abban a blokkban létezik, amelyet a for utasítás definiál, és a külső main függvény nem látja.
#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);
}
Ha eltávolítjuk az utolsó printf utasítást, a program lefordul, és futtatáskor a 0 2 4 6 8 10 szöveget írja ki.
Egyes nyelvek, például a Go és a Rust, egyértelműen kimondják, hogy az egysoros feltételes és ciklusutasítások is blokkot alkotnak.
Náluk az ilyen egysoros utasításokat is kapcsos zárójelek közé kell tenni.
A függvény némileg úgy viselkedik, mint egy blokk. A függvényben definiált változó azonosítója nem látható a függvényen kívül, akárcsak a blokkban definiált változó azonosítója a blokkon kívül. Két függvényben lehet ugyanaz a név, mivel a két függvény nem lát bele a másikba, akárcsak két blokk esetében: ott is lehet ugyanaz a név, mert a két blokk nem lát bele a másikba. A függvények és a blokkok azonban nem azonosak. Függvényt nem lehet egy másik függvényen belül definiálni, blokkot viszont igen: az egymásba ágyazható. Ráadásul a blokkban lévő egyes azonosítók a blokkon kívülről is láthatók. A következő példában a kód nem fordul le:
#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;
}
}
}
A fenti kód a következő hibával nem fordul le:
/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:
| ^~~~~
Bár az i mindkét blokkban definiálható, a label neveknek egy függvényen belül egyedinek kell lenniük.
Ez azért van, mert a labels függvényszintű scope-pal rendelkeznek, vagyis a függvény mindegyik címkét látja, függetlenül attól, hogy a függvény mely pontján van definiálva.
Így a változókkal ellentétben a label nevének egyedinek kell lennie ugyanazon függvényen belül, bárhol is definiáljuk.
Ahhoz, hogy a kód működjön, az egyik label nevét meg kell változtatni, hogy mindkettő egyedi legyen, mint a következő példában:
#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;
}
}
}
Az egysoros utasítások „egymásra pakolhatók”, és kapcsos zárójelek nélkül is egy blokkon belül maradnak.
Az alábbi példában a for utasítás tartalmaz egy if utasítást, az pedig egy printf utasítást.
Bár a for blokkjában összesen két utasítás van, a for ciklus törzseként csak az egysoros if utasítás értékelődik ki.
Az if utasítás egysoros printf utasítása pedig a saját törzseként értékelődik ki.
Ha az egymásba ágyazott blokkok mindegyike csak egyetlen sor, egyik blokknál sem kellenek kapcsos zárójelek.
Vegyük észre, hogy a megjegyzés és az if blokkban lévő printf utasítás körül nincs szükség kapcsos zárójelekre, mert a megjegyzés nem utasítás.
A behúzás világossá teszi, mi történik, de egy fejlesztőcsapatnak lehet saját kódolási irányelve arról, hogyan helyezze el a kapcsos zárójeleket az ilyen helyzetekben, hogy minden kétértelműséget kiküszöböljön.
#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);
}
Láttuk, hogy a blokkon belül definiált értékek a blokkon kívülről nem érhetők el.
És azt is láttuk, hogy a blokkon kívül definiált értékek általában elérhetők a blokkon belülről.
Előfordulhat, hogy egy blokkon kívüli változó elrejtőzik a blokkon belül, ha a változó azonosítóját egy blokkban definiált változó definiálásához használjuk.
A következő példában a number azonosítót egyszerre használjuk egy függvényszintű és egy blokkszintű változóhoz.
Azt mondjuk, hogy a blokkszintű változó elrejti, más szóval árnyékolja a függvényszintű változót.
#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);
}
A fordítók hibát jelezhetnek, ha egy függvényparamétert próbálunk árnyékolni. A következő példa
#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);
}
két platformon a következő hibákat eredményezi
/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) {
| ~~~~^~~~~~
és
redefinition of formal parameter 'number'