스코프
스코프 (Scope)
프로그램에서 이름을 쓸 때, 그 이름이 '보이는' 영역은 어디까지일까요? 예를 들어 함수 안에서 선언한 변수는 왜 함수 밖에서 쓸 수 없을까요? 그 이유가 바로 스코프예요. C의 스코프 종류와 규칙을 이 페이지에서 살펴볼게요.
출처: cppreference
본문
C 프로그램에 나타나는 각 식별자는 소스 코드의 일부(아마도 끊어져 있을 수 있는 부분)에서만 보여요 (즉 사용될 수 있어요). 그 부분을 그 식별자의 스코프(scope)라고 불러요.
스코프 안에서 하나의 식별자가 두 개 이상의 엔티티를 지정할 수 있는 것은, 그 엔티티들이 서로 다른 이름 공간(name space)에 있을 때뿐이에요.
C에는 네 종류의 스코프가 있어요.
- 블록 스코프 (block scope)
- 파일 스코프 (file scope)
- 함수 스코프 (function scope)
- 함수 프로토타입 스코프 (function prototype scope)
중첩 스코프 (Nested scopes)
같은 식별자로 이름 붙은 두 개의 서로 다른 엔티티가 동시에 스코프 안에 있고 같은 이름 공간에 속한다면, 그 스코프들은 중첩돼요 (다른 형태의 스코프 겹침은 허용되지 않아요). 그리고 안쪽 스코프에 나타난 선언이 바깥쪽 스코프의 선언을 숨겨요:
// The name space here is ordinary identifiers.
int a; // file scope of name a begins here
void f(void)
{
int a = 1; // the block scope of the name a begins here; hides file-scope a
{
int a = 2; // the scope of the inner a begins here, outer a is hidden
printf("%d\n", a); // inner a is in scope, prints 2
} // the block scope of the inner a ends here
printf("%d\n", a); // the outer a is in scope, prints 1
} // the scope of the outer a ends here
void g(int a); // name a has function prototype scope; hides file-scope a
블록 스코프 (Block scope)
복합문(함수 본문 포함) 안에 선언된 식별자, 또는 if, switch, for, while, do-while 문(since C99)에 나타나는 아무 표현식·선언·문, 또는 함수 정의의 매개변수 목록 안에 선언된 식별자의 스코프는 선언 지점에서 시작해서 선언된 블록이나 문의 끝에서 끝나요.
void f(int n) // scope of the function parameter 'n' begins
{ // the body of the function begins
++n; // 'n' is in scope and refers to the function parameter
// int n = 2; // error: cannot redeclare identifier in the same scope
for(int n = 0; n<10; ++n) { // scope of loop-local 'n' begins
printf("%d\n", n); // prints 0 1 2 3 4 5 6 7 8 9
} // scope of the loop-local 'n' ends
// the function parameter 'n' is back in scope
printf("%d\n", n); // prints the value of the parameter
} // scope of function parameter 'n' ends
int a = n; // Error: name 'n' is not in scope
C99까지는, 선택 문과 반복 문이 자체 블록 스코프를 만들지 않았어요 (단, 그 문에서 복합문을 사용했다면 그 복합문은 평소대로 자신의 블록 스코프를 가졌어요):
enum {a, b};
int different(void)
{
if (sizeof(enum {b, a}) != sizeof(int))
return a; // a == 1
return b; // b == 0 in C89, b == 1 in C99
}
(since C99)
블록 스코프 변수는 기본적으로 링크(linkage)가 없고 자동 저장 기간(automatic storage duration)을 가져요. 비-VLA 지역 변수의 저장 기간은 블록에 진입할 때 시작되지만, 선언을 볼 때까지 그 변수는 스코프 안에 있지 않아서 접근할 수 없어요.
파일 스코프 (File scope)
어떤 블록이나 매개변수 목록 밖에 선언된 식별자의 스코프는 선언 지점에서 시작해서 변환 단위(translation unit)의 끝에서 끝나요.
int i; // scope of i begins
static int g(int a) { return a; } // scope of g begins (note, "a" has block scope)
int main(void)
{
i = g(2); // i and g are in scope
}
파일 스코프 식별자는 기본적으로 외부 링크(external linkage)와 정적 저장 기간(static storage duration)을 가져요.
함수 스코프 (Function scope)
함수 안에 선언된 레이블(label)(그리고 레이블만)은 그 함수 어디에서나, 모든 중첩 블록에서, 자신의 선언 앞과 뒤 모두에서 스코프 안에 있어요. 주의: 레이블은 아무 문 앞에서, 그 외에는 쓰이지 않는 식별자 다음에 콜론을 붙여 사용함으로써 암시적으로 선언돼요.
void f()
{
{
goto label; // label in scope even though declared later
label:;
}
goto label; // label ignores block scope
}
void g()
{
goto label; // error: label not in scope in g()
}
함수 프로토타입 스코프 (Function prototype scope)
정의(definition)가 아닌 함수 선언의 매개변수 목록에 도입된 이름의 스코프는 함수 선언자(declarator)의 끝에서 끝나요.
int f(int n,
int a[n]); // n is in scope and refers to the first parameter
선언에 여러 개 또는 중첩된 선언자가 있다면, 스코프는 가장 가까운 바깥쪽 함수 선언자의 끝에서 끝난다는 점을 주의하세요:
void f ( // function name 'f' is at file scope
long double f, // the identifier 'f' is now in scope, file-scope 'f' is hidden
char (**a)[10 * sizeof f] // 'f' refers to the first parameter, which is in scope
);
enum{ n = 3 };
int (*(*g)(int n))[n]; // the scope of the function parameter 'n'
// ends at the end of its function declarator
// in the array declarator, global n is in scope
// (this declares a pointer to function returning a pointer to an array of 3 int)
선언 지점 (Point of declaration)
구조체, 공용체, 열거형 태그(tag)의 스코프는, 그 태그를 선언하는 타입 지정자에서 태그가 나타난 직후부터 시작돼요.
struct Node {
struct Node* next; // Node is in scope and refers to this struct
};
열거 상수의 스코프는, enumerator 목록에서 그것을 정의하는 enumerator가 나타난 직후부터 시작돼요.
enum { x = 12 };
{
enum { x = x + 1, // new x is not in scope until the comma, x is initialized to 13
y = x + 1 // the new enumerator x is now in scope, y is initialized to 14
};
}
그 외 다른 식별자의 스코프는 그 선언자의 끝 바로 뒤, 그리고 초기화자(있을 경우) 앞에서 시작돼요:
int x = 2; // scope of the first 'x' begins
{
int x[x]; // scope of the newly declared x begins after the declarator (x[x]).
// Within the declarator, the outer 'x' is still in scope.
// This declares a VLA array of 2 int.
}
unsigned char x = 32; // scope of the outer 'x' begins
{
unsigned char x = x;
// scope of the inner 'x' begins before the initializer (= x)
// this does not initialize the inner 'x' with the value 32,
// this initializes the inner 'x' with its own, indeterminate, value
}
unsigned long factorial(unsigned long n)
// declarator ends, 'factorial' is in scope from this point
{
return n<2 ? 1 : n*factorial(n-1); // recursive call
}
특수한 경우로, 식별자 선언이 아닌 타입 이름의 스코프는, 그 식별자가 생략되지 않았다면 나타났을 타입 이름 안의 위치 바로 뒤에서 시작하는 것으로 간주돼요.
참고 사항 (Notes)
C89 이전에는, 외부 링크를 가진 식별자는 블록 안에서 도입됐어도 파일 스코프를 가졌어요. 그래서 C89 컴파일러는 스코프 밖으로 나간 extern 식별자의 사용을 진단할 필요가 없어요 (그런 사용은 정의되지 않은 동작이에요).
루프 본문 안의 지역 변수는 C에서 for 루프의 init 절에 선언된 변수를 숨길 수 있어요 (그들의 스코프가 중첩되기 때문이에요). 하지만 C++에서는 그럴 수 없어요.
C++과 달리 C에는 구조체 스코프가 없어요: 구조체/공용체/열거형 선언 안에 선언된 이름들은 구조체 선언과 같은 스코프에 있어요 (데이터 멤버가 자신의 멤버 이름 공간에 있는 경우 제외):
struct foo {
struct baz {};
enum color {RED, BLUE};
};
struct baz b; // baz is in scope
enum color x = RED; // color and RED are in scope