http://blog.naver.com/entraiger?Redirect=Log&logNo=120122764718
Extern, Static 변수와 함수
Extern, Static 키워드는 변수와 함수에서 쓰일 때 그 의미가 조금 다르다. 형식과 쓰임새가 차이가 있기 때문이다.
<Extern 전역변수, 지역변수>
extern 변수는 다른 파일에서 변수를 공유해서 쓰기 위해 있는 키워드인데, 전역변수는 키워드를 생략해도 기본으로 extern선언이 되는 성질이 있다.
아래 코드를 보면,
-----------------------
/*main.c*/
#include <stdio.h>
#include "fun1.h"
int a=1; // 변수 선언
void main(){
f();
printf("%d\n",a); // 여기서의 a는 위의 a
}
-----------------------
/*fun1.c*/
#include <stdio.h>
#include "fun1.h"
int a; // 또 다른 선언문
void f(){
a = 7;
printf("%d\n",a); //여기서의 a는 바로 위의 a
}
-----------------------
/*fun1.h*/
void f();
-----------------------
결과는: 7
7
-----------------------
fun1.c에서 바꾼 a값이 main.c의 a에도 적용되어있다.
따라서 main.c와 fun1.c의 전역변수 a는 동일한 변수이거나 항상 싱크가 되어있다는 것을 알 수 있다.
여기에서 main.c와 fun1.c의 선언문에 extern을 붙여보자.
(main.c : int a=1; -> extern int a=1;)
(fun1.c : int a; -> extern int a;)
한쪽파일에만 extern키워드를 쓰든 양쪽에 다 쓰든, 모든 경우에서 결과는 같다.
-----------------------
//간략화한 코드//
/*main.c*/
extern int a=1;
/*fun1.c*/
int a;
-----------------------
/*main.c*/
int a=1;
/*fun1.c*/
extern int a;
-----------------------
/*main.c*/
extern int a=1;
/*fun1.c*/
extern int a;
-----------------------
결과는 항상
7
7
-----------------------
이 된다. 즉, 전역변수로 선언할 경우 extern를 넣든 안 넣든 결과는 똑같아 보인다. extern키워드를 생략해도, 전역변수는 기본으로 extern이기 때문일 것이다. 그런데 초기화구문의 extern과 초기화가 없는 extern선언문은 의미에 차이가 있다. 초기화에 쓰이는 extern은 변수를 새로 정의하는 선언문이라는 의미의 extern이고 실제로 메모리를 할당해서 변수를 정의한다. 그런데 초기화가 없는 선언문은 변수를 새로 정의하는 것이 아닌, “다른 파일(혹은 같은 파일 내의 다른 부분)에서 찾아서 있으면 그것을 가져다 쓰겠다”는 ‘참조선언’의 의미를 갖는 extern이다. 그렇다면 어떤 것이 정의선언이고 어떤 것이 참조선언일까? 아래의 경우를 보자. 아래의 경우는 모두 전역변수로 선언된 경우이다.
-----------------------
전역변수 선언문 종류
1. extern int a=1 -> extern변수 정의 및 초기화
2. int a =1; -> extern 변수 정의 및 초기화
3. extern int a; -> extern 변수(새로 정의하지 않고)참조선언
4. int a; -> extern 변수 정의(0으로초기화) 또는 참조선언
-----------------------
4의 경우는 그 쓰임새가 애매하다. 1이나 2와 쓰일 때는 참조선언이 되고, 3과 쓰일 때는 정의구문이 된다.(컴파일러에서 자동으로 처리함) 애매한만큼 지양해야 할 코드이다.
따라서
-----------------------
/*main.c*/
(extern생략가능)int a=1;
/*fun1.c*/
(extern생략가능)int a=2;
-----------------------
같은 식으로 여러번 초기화를 해주면 둘 다 정의 선언문이므로 ‘여러번 정의했다’는 에러가 뜨게 된다. 같은 a를 두 번 정의했으니 에러가 뜨는 것이다. 아까처럼
-----------------------
/*main.c*/
(extern생략가능)int a=1; //extern변수 정의 및 초기화
/*fun1.c*/
(extern생략가능)int a; //a라는 extern변수를 다른 곳에서 찾아서 쓰겠다는 의미
-----------------------
로 코딩을 했을 경우, 에러가 없다. 이 관계는 다음의 예시에서 더욱 명백해진다.
-----------------------
/*main2.c*/
int a=1;
int a;
void main(){
int b=1; (1)
int b; (2)
}
-----------------------
새로운 main2.c파일에서 a는 전역변수, b는 지역변수로 선언되었다. 처음 int a=1;로 선언과 동시에 초기화되었고, 바로 아래줄에서 int a;는 선언이 아닌, “다른 곳에서 a를 찾아본 뒤에 그것을 쓰겠다”는 표현이 되므로 에러가 나지 않는다.(extern int a;의 생략형이라고 이해하면 쉬울 것이다.) 그러나 지역 변수 b의 경우 “여러번 재정의”에러가 뜬다. 잠깐 지역변수부분만 살펴보면, extern키워드를 통해 초기화하는 것이 아예 불가능하다.
-----------------------
void main(){
extern int b=1;
}
-----------------------
처럼 코딩을 하면, “블록 범위를 사용하여 extern변수를 초기화할 수 없습니다.”라는 에러메세지가 뜬다. 블록 범위, 즉 로컬변수선언시엔 무조건 그 블록 범위 내에서만 사용하게 되어있으므로, 다른 곳에서 이 변수를 사용할 수 있도록 하는 extern초기화 구문을 사용 못하게 막아놓은 것이다. 로컬변수를 다른 곳에서 사용하려면, 함수 결과값으로 끄집어내서 사용하는 수 밖에는 없다. 대신 초기화가 아닌 extern참조선언문은 함수 내에서도 전역에서 쓰듯이 사용이 가능하다.
-----------------------
// 간략화한 코드 //
/*main.c*/
(extern생략가능)int a=1; //extern변수 정의 및 초기화
/*fun1.c*/
void fun1(){
extern int a; // 다른 곳의 extern변수 a를 찾아 쓰겠다는 의미
}
-----------------------
그런데 지역변수에서 extern변수를 참조선언해서 가져다 쓸 때 주의할 점이 있다. 함수 밖에서 참조선언을 할 때는 참조에 실패하면 컴파일 에러가 나지만, 함수 내에서 참조선언을 하면 참조할 것이 없어도 컴파일 상의 에러가 없다.(헉쓰...) 아래의 코드를 보자.
-----------------------
/*main.c*/
int a=1; //extern변수 정의 및 초기화
/*fun1.c*/
extern int b; //extern변수 b참조선언.
-----------------------
결과: “b의 외부기호를 확인할 수 없다”는 에러.
-----------------------
/*main.c*/
int a=1; //extern변수 정의 및 초기화
/*fun1.c*/
void fun1(){
extern int b; //extern변수 b참조선언.
}
-----------------------
결과: no error
-----------------------
아래의 경우처럼 에러가 없으면 나중에 있지도 않은 b변수를 사용할 때 에러가 난다.
-----------------------
/*main.c*/
int a=1; //extern변수 정의 및 초기화
/*fun1.c*/
void fun1(){
extern int b; //extern변수 b참조선언.
printf("%d",b); //변수 b 사용
}
-----------------------
결과: “b의 외부기호를 확인할 수 없다”는 에러.
-----------------------
따라서 결국 b를 안전하게 사용하고 싶으면 전역변수로 참조선언 한 뒤에 함수에서 쓰는 것이 좋은 코딩이라는 이야기다. 아래의 코드가 그렇다.
-----------------------
/*main.c*/
int a=1; //extern변수 정의 및 초기화
int b=2; //extern변수 b를 추가해보았다.
/*fun1.c*/
extern int b; //extern변수 b참조선언은 밖에
void fun1(){
printf("%d",b); //변수 b 사용
}
-----------------------
결과: 2
-----------------------
정리하자면, 전역변수는 초기화인 경우 항상 쓰지 않아도 extern으로 정의가 되며, (즉 필연적으로 모든 전역으로 정의되는 변수는 이름 하나당 변수 하나가 최대다. 단, static변수로 만들지 않을 경우에 한해서) 초기화구문이 아닌 경우엔 extern키워드를 쓰면 확실히 참조선언문이 되지만 extern을 생략하면 경우에 따라 참조선언도 되지만 정의가 되어있지 않았다면 정의구문이 되어버린다. 따라서 extern은 기본적으로 생략 가능하지만 헷갈림을 방지하고 코딩의 가독성을 위해서 아래와 같이 여러 파일에서 사용할 때는 extern키워드를 명시해주는 것이 좋다.
-----------------------
/*main.c*/
extern int a=1; // extern변수를 선언하겠다는 의미. 다른 파일에서도 사용하겠다는 뜻.
생략해도 크게 상관은 없다.(외부에서 쓸 일이 있을 땐 생략 삼가)
/*fun1.c*/
extern int a; //다른 곳에 있는 extern변수를 가져다 쓰겠다는 의미.
생략하면 위의 경우와 구분이 어렵다.(쓰는 것을 필수)
-----------------------
덧붙이자면, 아래의 경우는 어떨까?
-----------------------
/*main.c*/
extern int a;
/*fun1.c*/
extern int a;
-----------------------
둘 모두 참조선언문이라 어디에서도 정의가 되어있지 않기 때문에, 외부 기호를 확인할 수 없다는 에러가 뜨는 것은 당연하다.(전문용어로 찾다가 GG) 그럼 아래의 경우는 또 어떨까?
-----------------------
/*main.c*/
int a;
/*fun1.c*/
int a;
-----------------------
둘 중 하나의 구문의 정의 선언문이 되고 나머지가 참조선언문이 되는 것은 확실하다. 그러나 그게 어느것인지는 나도 모르겠다.
<Static 전역, 지역변수>
아까 전역변수의 경우에는 extern키워드를 생략해도 자동으로 extern선언이 된다고 했다. 그렇다면 한 파일 내에서 전역변수로 선언해서 쓰고는 싶은데 다른 파일에서 참조하는 것은 막고 싶을 경우(전역변수지만 extern은 아닌)는 어떻게 할까? 그럴 땐 static선언을 하면 된다.
-----------------------
/*main.c*/
static int a=1; //static은 생략하면 안 된다.
/*fun1.c*/
int a; // int a=2, 혹은 extern int a=2도 가능
-----------------------
위의 경우, static변수로 선언한 main.c의 a는 main.c파일 안에서만 사용 가능하고 다른 파일에서는 extern키워드로 참조할 수 없다. fun1.c에서 선언한 a는 main.c의 a와는 다른 메모리에 있는 a이다. 이 경우는 fun1.c의 a가 extern변수이고 main.c의 a는 extern변수가 아닌 mian.c에 속박된 static변수가 되는 것이다. 따라서 아래와 같이 쓰면 에러가 난다.
-----------------------
/*main.c*/
static int a=1; //static은 생략하면 안 된다.
/*fun1.c*/
extern int a;
-----------------------
extern int a;에서 참조선언을 했으므로 다른 곳에서 a를 찾아서 가져다 써야할텐데, 유일한 a는 static으로 묶여있으니 참조를 할 수 없다. 그래서 외부기호 확인 불가능 에러가 뜨는 것이다. 지역변수의 경우엔 extern변수 정의가 불가능하므로 static으로 따로 정의할 필요가 없을 것이다.
<extern, static 함수>
함수의 경우 extern, static개념이 간단하다. 함수는 쓰임새에 따라서 외부함수, 내부함수로 나눠 부르지만 사실 전역, 지역의 개념이 없고 굳이 말하자면 모두 전역이기 때문이다.
기본적으로 함수선언은 생략해도 모두 extern이다.
아래 코드를 보자.
-----------------------
/*main.c*/
#include <stdio.h>
void f(); (1)
void main(){
f();
}
-----------------------
/*fun1.c*/
#include <stdio.h>
#include "fun1.h"
void f(){
printf("fun1.c\n");
}
-----------------------
/*fun1.h*/
void f();
-----------------------
결과: fun1.c
-----------------------
main.c에서 헤더파일을 추가하지도 않았는데, fun1.c의 함수를 가져다 쓴 것을 알 수 있다. fun1.c의 f()함수 선언 자체가 extern으로 되어있고, main.c의 (1)에서도 extern으로 선언해서 다른 파일의 함수를 자동으로 검색해서 call했기 때문이다. 이 점은 위 코드 어느 부분에 extern을 넣어도(심지어 함수정의부분에서도) 똑같은 결과가 나온다는 점에서 알 수 있다. 심지어 (1)을 생략해도 경고가 뜨지만 실행은 된다.(하지만 피해야 할 코딩방법) 함수는 기본적으로 extern으로 선언된다는 것을 알 수 있는 부분이다. 아래와 같이, main.c에서도 같은 이름의 함수 f()를 정의하면 어떻게 될까?
-----------------------
/*main.c*/
#include <stdio.h>
void f();
void main(){
f();
}
void f(){
printf("main.c\n");
}
-----------------------
/*fun1.c*/
#include <stdio.h>
#include "fun1.h"
void f(){
printf("fun1.c\n");
}
-----------------------
/*fun1.h*/
void f();
-----------------------
결과: 함수 재정의 오류
-----------------------
main.c에서 이미 정의한 f()함수가 자동으로 extern이기 때문에, fun1.c에서 또 정의를 해주면 이것도 자동으로 extern이고, extern함수는 고유해야하기 때문에 같은 함수를 두 번 정의한 것으로 인식해서 에러가 나게 된다. 이것을 막고, 두 함수를 모두 사용하고 싶다면
그 때 static 키워드로 선언을 하면 된다.
-----------------------
/*main.c*/
#include <stdio.h>
static void f(); (1)
void main(){
f();
}
void f(){
printf("main.c\n");
}
-----------------------
/*fun1.c*/
#include <stdio.h>
#include "fun1.h"
void f(){
printf("fun1.c\n");
}
-----------------------
/*fun1.h*/
void f();
-----------------------
결과: main.c
-----------------------
잘 보면 mian.c의 f()함수는 static으로 선언되었고, fun1.c의 함수는 extern으로 선언되었으므로, main.c에서 실행한 f()함수는 mian.c에서 정의한 함수가 되며 출력도 그렇게 나온다. 반대로 mian.c의 함수를 extern으로, fun1.c의 함수를 static으로 정의해도 결과는 똑같다. 둘 다 static으로 정의해도 마찬가지다. 이유는 생각해보시길...
'Program Language > C' 카테고리의 다른 글
printf debugging (0) | 2012.06.19 |
---|---|
sizeof (0) | 2012.06.04 |
caller 찾기 (0) | 2012.01.27 |
printf uint64_t uint32_t (0) | 2011.12.01 |
ignoring return value of 'system', declared with attribute warn_unused_result (0) | 2011.11.11 |
댓글