ブロックとは、文と宣言を1つの構文単位にまとめたものです。
ブロックは、多くの場合、波括弧({})の中に定義されますが、必ずそうとは限りません。
ブロックは、関数の中に入れ子にすることも、他のブロックの中に入れ子にすることもできます。
次の例では、numberはmain関数の中で定義されています。
flagは、main関数の中に入れ子になったifブロックの中で定義されています。
ブロックの中で定義された変数は、そのブロックの中でしか有効ではありません。
そのため、このコードをコンパイルしようとすると、コンパイルエラーになります。
#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以降では、1行で書かれた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など一部の言語では、1行の条件文やループ文もブロックを構成すると明確に定められています。
そうした言語では、1行の文であっても波括弧で囲むことが求められます。
関数は、ある意味でブロックのようにふるまいます。 関数の中で定義された変数の識別子がその関数の外から見えないのと同じように、ブロックの中で定義された変数の識別子も、そのブロックの外からは見えません。 2つの関数は互いの中を見ることができないので、2つの関数の変数が同じ名前でもかまわないのと同じように、2つのブロックも互いの中を見ることができないので、2つのブロックの変数が同じ名前でもかまいません。 ただし、関数とブロックはまったく同じものではありません。 関数を別の関数の内部に定義することはできませんが、ブロックは別のブロックの中に入れ子にできます。 また、ブロック内の一部の識別子は、ブロックの外からも見えます。 次の例のコードはコンパイルできません。
#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;
}
}
}
1行の文は「積み重ねる」ことができ、波括弧を使わなくても1つのブロックの中に収まります。
次の例では、for文の中にif文があり、そのif文の中にさらにprintf文があります。
forブロックには合計2つの文がありますが、forループの本体として評価されるのは、その1行のif文だけです。
そして、そのif文の1行のprintf文が、if文の本体として評価されます。
入れ子になったブロックがそれぞれ1行だけなら、どのブロックにも波括弧は必要ありません。
コメントは文ではないので、コメントとifブロック内のprintf文をまとめるのに波括弧は必要ないことに注意してください。
インデントを見れば何が起きているかは明らかですが、開発チームによっては、こうした状況で波括弧をどう置くかについて、あいまいさをなくすための独自のコーディング規約を設けていることもあります。
#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);
}
2つのプラットフォームで次のエラーになります。
/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'