블록

블록 에서 C

{other: "%{count}개의 연습 문제"}

블록 소개

블록은 문과 선언을 하나의 구문 단위로 묶은 집합이에요. 블록은 항상은 아니지만 흔히 중괄호 안에 정의해요. 블록은 함수 안에, 또 다른 블록 안에 중첩할 수 있어요.

다음 예제에서 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'
GitHub에서 편집 링크가 새 창이나 탭에서 열려요