프로그래밍에서 선언과 정의에 대해서 알아보자.
선언과 정의의 가장 큰 차이는 "메모리를 할당하는가"이다. 즉, 메모리를 할당하지 않고, 대상의 이름만 알려준다면 선언이고, 대상의 메모리가 할당된다면 그것은 정의이다. 다만 혼동하기 쉬운 예로 typedef는 사용자 정의 타입이라고 불리지만, 실제로 메모리 영역상에 올리지는 않기 때문에 정의라고 볼 수는 없다.
void main() {
int a;
int b = 10;
}
위의 예에서 int a; 는 선언과 동시에 정의한 것이다. 즉, int 4byte 만의 영역이 메모리 할당되었다는 말이다. 너무 헷갈리는데 선언과 정의가 무엇이며, 각자의 특징에 대해 좀 더 자세히 알아보려고 한다.
선언
컴파일러가 참조할 식별자와 이름을 알린다는 의미이다. 여기서 식별자란 변수의 타입과 함수의 인수목록을 뜻하며 이름은 변수, 함수, 클래스의 이름, 네임 스페이스를 뜻한다. 선언은 메모리 영역 상에 올리지 않기때문에 중복되어도 문제가 되지않으며 2번이상 할 수 있다.
정의
정의는 식별자와 이름으로부터 코드를 생성하여 함수가 호출되거나 변수를 사용할 때 생성된 코드를 참조한다. 또한 정의는 고유 사양으로 프로그램에는 정의가 하나만 있어야 한다. 같은 식별자와 이름의 정의가 2개 이상이라면 컴파일 에러가 발생한다. 반면에 선언만 하고 정의를 하지않은 경우에는 링커 단계에서 참조할 코드가 없기 때문에 링커 에러가 발생하는데 다음과 같이 예외인 경우도 있으므로 이 경우에는 정의가 필요가 없기에 알아두자.
- 함수가 선언만 있고, 정의가 없지만 함수를 호출하거나 함수의 주소를 참조하는 일이 없을 때
- 정의를 알 필요가 없는 방식으로만 사용된다. 대표적으로 전방 선언이 있다.
그럼 자꾸 전방 선언이라는 말이 나오는데 이에 대해서도 좀 알아보자.
전방 선언(forward declaration)
전방 선언이란 함수나 구조체(struct), 열거자(enum), 공용자(union), extern 변수, C++에서는 클래스 등을 그 실제 구현 시점보다 앞서서 선언(declaration)만하는만 하는 것을 말한다. 즉, 서로를 호출하는 함수, extern 변수, pimple 기법 등에서 사용되며, 헤더와 소스 파일로 구현부 및 선언부를 분리하는 행위는 기본적으로 모두 전방 선언에 속한다.
그럼 코드로 전방 선언이 무엇인지 좀 더 자세히 알아보자.
#include <stdio.h>
void funcA(int x){
if (x < 0) return;
printf("%d from A\n", x);
funcB(--x);
}
void funcB(int x){
printf("%d from B\n", x);
funcA(--x);
}
int main(){
funcA(3);
return 0;
}
위의 경우는 컴파일해보면 선언되지 않은 식별자 funcB를 사용했다는 에러 메시지가 나온다. 문제는 funcA의 시점에서 funcB가 보이지않는게 문제이므로 다음과 같이 funcB만 전방 선언해주면 된다.
#include <stdio.h>
void funcB(int x);
// void funcB(int); 전방 선언은 변수 명을 적지 않아도 됩니다(선택사항)
void funcA(int x){
if (x < 0) return;
printf("%d from A\n", x);
funcB(--x);
}
void funcB(int x){
printf("%d from B\n", x);
funcA(--x);
}
int main(){
funcA(3);
return 0;
}
이번에는 extern 변수. 즉, 외부 변수에 대해서 좀 알아보자.
extern , static 변수
C언어에서 정의하는 extern variable이란 여러 개의 소스 파일이 존재할 때, 각 소스 파일이 가지고 있는 변수 중에서 다른 소스 파일에서도 접근 가능한 변수를 의미한다. 그리고 C 언에서는 다른 소스 파일에서 선언한 변수를 다른 소스 파일에서 사용하기 위해 extern 변수를 사용하는 것이다.
그럼 static 변수와의 차이는 무엇일까? static 변수는 말그대로 정적 변수로 함수내부(지역)에서도 사용이 가능하고, 함수외부(전역)에서도 사용이 가능하다.
static 선언을 하는 방법은 변수를 선언할 때 static 키워드를 붙여만 주면 되는데, 전역공간에서 사용할 때는 아무 함수에도 속하지 않는 외부 정적변수가 된다. 이 변수는 우리가 초기화해주지 않아도 자동으로 0으로 초기화되며, (컴파일러가 해준다.) 프로그램이 시작될때 할당되고, 프로그램이 끝날때 파괴되는 특징을 가지고 있다. 또한 이렇게 전역으로 선언한 static 변수는 그 소스파일 내의 모든 함수에서 사용이 가능하다. 이 말은 곧 다른 소스파일에서는 사용하지 못하므로 정보은닉 효과가 있다는 소리이다.
반대로 정적변수를 함수 내부. 즉, 지역에 선언할 수 있는데 이렇게 함수 내부에 선언하게 되면 다른 함수에서는 값을 참조할 수 없다. 이를 내부 정적변수라고 하는데, 비록 함수 내부에서 선언되었지만 함수가 아닌 프로그램이 시작될때 할당되고, 프로그램이 종료될때 해제가 된다. 그렇기 때문에 함수가 여러번 실행되더라도 내부 정적변수의 초기화는 한번만 이루어진다는 특징을 가지고 있다. 내부 정적변수의 사용법을 살펴보면 아래와 같다.
int test1() {
static int num;
num++;
return num;
}
int main() {
for (int i = 0; i < 5; i++) {
printf("%d\n", test1());
}
printf("\n");
return 0;
}
이렇게 static 변수를 함수 내부에 선언하면 함수가 여러번 실행되더라도 변수의 선언 및 초기화는 한번만 이루어지는 특징을 활용할 수 있다. 따라서 static 변수는 함수가 다음 실행할 때도 그 값을 기억하고 있어야할 때 사용하면 된다.
그럼 static과 extern의 차이는 무엇일까? extern은 다른 파일의 전역 변수에 접근이 가능하고, static은 외부 파일에서 접근이 불가능하다는 특징을 가지고 있다. 즉, 범위의 제한을 받는 전역 변수로 범위는 파일, 네임스페이스, 클래스, 함수 속으로 범위가 지정된다. 즉, 다음 코드를 실행하면 링킹 과정중에서 globalValue의 주소를 가져오지 못하는 링킹 에러가 발생한다.
[ExternTest.h]
extern int globalValue;
void IncreaseValue();
[ExternTest.c]
// static 변수다, 다른 파일에서 접근 불가능 하다. extern이 있다고 하더라도
static int globalValue; void IncreaseValue()
{
globalValue++;
}
[main.c]
#include "ExternTest.h"
// 컴파일러에 의해 다음과 같이 치환
// extern int globalValue;
// void IncreaseValue();
void main()
{
// 링커 에러가 발생한다. "링킹" 과정중(컴파일 에러가 아니라...)에서 globalValue의 주소를 가져오지 못한다.
printf("$d", globalValue);
}
추가적으로 C++의 네임스페이스 관련해서도 좀 찾아봤는데, 뭔가 여기서 파생되는 개념중에 Method Overloading 도 있을 것 같다. 메서드 오버로딩을 그냥 사용만 해봤는데, 이번 기회를 비뤄서 Method Overloading이 어떻게 작동하는지도 좀 살펴봐야겠다.
'Language > C' 카테고리의 다른 글
#if, #elif, #else, #endif와 #ifdef, #ifndef, #endif 지시문 (0) | 2023.11.11 |
---|---|
#define에 대해서 (0) | 2023.11.11 |
구조체 (0) | 2023.11.05 |
키워드 _CRT_SEUCRE_NO_WARNINGS에 대하여 (0) | 2023.11.05 |
typedef, enum, union (0) | 2023.11.05 |