블록은 문과 선언을 하나의 구문 단위로 묶은 집합이에요. 블록은 항상은 아니지만 흔히 중괄호 안에 정의해요. 블록은 함수 안에, 또 다른 블록 안에 중첩할 수 있어요.
다음 예제에서 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 함수 자체는 마지막 printf문에서 flag 식별자를 처음 보게 돼요.
마지막 printf문을 없애면 프로그램은 컴파일되고, 실행하면 1을 출력해요.
첫 번째 printf문이 들어 있는 블록 안에서는 flag 변수가 유효하기 때문이에요.
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을 볼 수 있다는 뜻이에요.
그래서 변수와 달리 label은 함수 안 어디에 정의되든 같은 함수 안에서 고유한 이름을 가져야 해요.
코드가 동작하게 하려면 다음 예제처럼 두 label 중 하나의 이름을 바꿔서 둘 다 고유하게 만들어야 해요.
#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문을 포함하고, 그 if문은 다시 printf문을 포함해요.
for 블록 안에는 모두 두 개의 문이 있지만, for 루프의 본문으로 평가되는 것은 한 줄짜리 if문뿐이에요.
if문의 한 줄짜리 printf문이 다시 그 본문으로 평가돼요.
중첩된 블록이 각각 한 줄뿐이라면 어느 블록도 중괄호가 필요하지 않아요.
주석은 문이 아니기 때문에, 주석과 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);
}
두 플랫폼에서 다음과 같은 오류를 발생시켜요.
/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'