'extern'에 해당되는 글 1건

  1. 2006.10.18 extern 의 용법

원문: http://cafe.naver.com/dmz28x/111

extern의 사용법은 크게 두가지가 있죠.

1. 다른 파일에 선언된 변수나 함수를 선언할 때

◆ 기본적인 얘기

extern이라는 것은 저장공간에 대한 선언에 사용되는 키워드로서,
여러개의 소스파일에 대해서 작업할 때 필요한 것입니다.
무슨 얘긴고 하니... 쉽게 말해서 다른 파일에 선언된 변수가 있다
고 선언하는 겁니다.

[file1.cpp]
int x = 1;
int f() { /* do something */ }
int y = 2;
extern z;

[file2.cpp]
extern int x;
int f();
extern int y = 3;  // 링크 에러.
extern z;          // 링크 에러

void g() { x = f(); }

extern int x; 라는 줄은 정수 x가 여기서 정의(definition)되지
않는다는 것을 지시하며 단지 선언(declaration)을 해주는 일을 수
행합니다. 위에서 y는 링크시 에러가 나게 되는데, extern 키워드
를 사용하더라도 초기화를 하면 그 선언은 정의가 되기 때문입니다.
즉, y는 file1.cpp와 file2.cpp에서 모두 정의되게 됩니다. 반대
로 z는 두 파일에서 모두 선언만 되고 정의가 되지 않았기 때문에 역
시 링크에러가 나게 됩니다.

참고로 file2.c에서 f는 extern int f(); 라고 지정해도 상관없습
니다.

◆ const와 typedef

const와 typedef는 기본적으로 internal linkage입니다. 즉, 다
른 파일에서 영향을 받지 않는다는 것이죠. const에서 이미 살펴봤
죠? typedef도 마찬가지입니다.

const에서 다뤘던 포인터 예제는 적절하지 못한 예였습니다. -_-;
여기서 다시 보도록 하죠.

[t1.cpp]
typedef int T;
const char* pText0 = "SCV";
char* const pText1 = "marine";
extern char* const pText2;
extern char* const pText3 = "tank";
extern char* const pText4 = "zealot";

[t2.cpp]
typedef void T;
const char* pText0 = "SCV";
char* const pText1 = "lurker";
extern char* const pText2;
extern char* const pText3;
extern char* const pText4 = "dragoon";

T : t1.cpp에서는 int, t2.cpp에서는 void로 사용할 수 있음.
pText0 : 조심! const char*의 의미는 const char에 대한 const
가 아닌 포인터. 즉, const가 아님. 따라서 external linakage.
링크 에러.
pText1 : char* const의 의미는 char에 대한 const 포인터. 즉,
internal linkage. t1.cpp에서는 "marine", t2.cpp에서
는 "lurker"으로 잘 동작함.
pText2 : 양쪽 모두 선언만 되고 정의는 안함. 실제로 사용하게 되
면 링크 에러.
pText3 : t1.cpp에서 초기화를 했기 때문에 정의가 됨. "tank"
pText4 : 양쪽 모두 초기화를 했기 때문에 양쪽 모두 정의가 되서
중복 정의 에러가 생김.

◆ extern을 사용가능한 경우

그럼 마지막으로 어떤 경우에 static이, 어떤 경우에 extern이 사용
가능한지 정리한 표를 보죠. (from MSDN)

Construct                                static   extern
Function declarations within a block      No       Yes
Formal arguments to a function            No       No
Objects in a block                        Yes      Yes
Objects outside a block                   Yes      Yes
Functions                                 Yes      Yes
Class member functions                    Yes      No
Class member data                         Yes      No
typedef names                             No       No


2. 다른 언어로의 연결을 지시할 때

◆ 기본적인 얘기부터

가장 대표적인게
extern "C" int Calc(double, int, int);
이런 식으로 C의 함수를 선언하는 것입니다.

아시겠지만 C와 C++은 함수를 호출하는 방식이 다릅니다. 인자를 처
리하는 순서라거나 스택에서 관리하는 방법 같은게 다르죠. (자세한
건 다음 기회에...) 따라서 C++ 컴파일러에게 이 함수는 C++의 규칙
에 어긋나는 함수는 아니지만 사실은 C의 함수라고 알려줘야 C++ 컴
파일러가 실수하지 않고 처리할 수 있게 됩니다. (실제로 이 선언을
중요하게 처리해야 하는건 링커입니다.)
그리고 Fotran에서 만든 함수를 Visual C++에서 사용하기 위해서
도 extern "C"로 선언해야 하는 것 같습니다. VC++이 extern 다음
에 허용하는 문자열이 "C"와 "C++" 뿐이라네요. (제가 해본건 아니
라서...)

extern "C"
{
#include < string.h>               // C 헤더 파일을 포함
   int Calc(double, int, int);   // C 함수를 선언
   int g1;                       // 정의(definition)
   extern int g2;                // 선언(declaration,
not definition)
}

이렇게 해서 여러개의 선언을 묶어주기도 하죠.

참고로 아랫줄은 정의가 아니라 선언입니다.
extern "C" int g3;              // 선언(declaration, not
definition)

C코드를 만들면서 C와 C++에서 다 사용할 수 있도록 헤더 파일을 만
들기 위해서는 다음과 같은 방법을 사용합니다.

#ifdef __cplusplus
extern "C" {
#endif

// 실제 함수나 변수들의 선언

#ifdef __cplusplus
}
#endif

C++ 소스를 컴파일할 경우에는 __cplusplus 가 자동으로 정의되므
로 전처리기에 의해 extern "C" { } 선언이 들어가게 됩니다.

◆ 함수 포인터의 경우

case by case로 생각해보겠습니다. (책을 베끼겠습니다.)

typedef int (*FT)(const void* , const
void*);                // (1)

extern "C" {
   typedef int (*CFT)(const void* , const
void*);           // (2)
   void qsort(void* p, size_t n, size_t sz, CFT
cmp);       // (3)
}

void isort(void* p, size_t n, size_t sz, FT
cmp);            // (4)
void xsort(void* p, size_t n, size_t sz, CFT
cmp);           // (5)
extern "C" void ysort(void* p, size_t n, size_t sz, FT
cmp); // (6)

int compare(const void* , const
void*);                      // (7)
extern "C" int ccmp(const void* , const
void*);              // (8)

(1) FT는 (const void*, const void*)를 받고 int를 리턴하는
C++로 링크되는 함수.
(2) CFT는 (const void*, const void*)를 받고 int를 리턴하는
C로 링크되는 함수.
(3) cmp는 CFT 타입. 즉 C로 링크되는 함수. qsort도 C 함수.
(4) cmp는 FT 타입. 즉 C++로 링크되는 함수. isort도 C++ 함수.
(5) cmp는 CFT 타입. 즉 C로 링크되는 함수. xsort는 C++ 함수.
(6) cmp는 FT 타입. 즉 C++로 링크되는 함수. qsort도 C 함수.
(7) compare는 C++로 링크되는 함수.
(8) ccmp는 C로 링크되는 함수.

따라서... 다음과 같은 결과가 나옵니다.
void (char* v, int sz)
{
   qsort(v, sz, 1, &compare);      // error
   qsort(v, sz, 1, &ccmp);         // ok
   
   isort(v, sz, 1, &compare);      // ok
   isort(v, sz, 1, &ccmp);         // error
}

[The C++ Programming Language]의 설명에 따르면 C와 C++이 함
수를 호출하는 방법이 같을 경우에는 위의 에러들도 확장으로 인정해
줄 수 있다고 하네요. 참고하세요.

◆ 다른 언어와의 연결에 대한 조금 더 시시콜콜하고 Visual C++적
인 얘기

포트란이나 파스칼 함수에 대해서는 __stdcall 를 사용해서 선언해
줘야 합니다.
(모든 윈도우 API 함수들은 __stdcall을 사용해서 선언되어 있습니
다.)
함수의 선언 앞에 붙이는 PASCAL, WINAPI, CALLBACK이라는 선언자
는 모두 __stdacll이라는 의미를 가지고 있죠.
반면에 C나 C++의 함수는 기본적으로 __cdecl이 됩니다.

__cdecl로 선언된 함수의 경우 인자를 전달한 스택을 정리하는건 호
출한 함수(caller)입니다.
__stdcall로 선언된 함수의 경우 인자를 전달한 스택을 정리하는건
호출된 함수(callee)입니다.
양쪽다 인자는 오른쪽 인자부터 왼쪽으로 스택에 들어가지요.

그렇다면 스택을 정리하는게 누구인지가 왜 중요할까요?
다른 언어가 다 호출된 함수가 스택을 정리하니까 C나 C++도 똑같이
하면 편할 텐데요.

일단 치명적인 부분은 vararg를 사용하는 함수입니다. 가장 대표적
인게 printf죠.
printf 는 다음과 같은 형을 가집니다.
int printf(const char *format, ... );
... 는 인자의 수가 정해져 있지 않다는 뜻이죠? 하지만 인자의 갯수
나 종류같은게 정해져 있지 않기 때문에 호출된 함수가 스택을 정리하
는 __stdcall의 경우에는 제대로 청소를 할 수가 없게 됩니다. 따라
서 인자의 수가 정해져 있지 않은 함수는 __cdecl을 사용하게 됩니
다.

참고로 __cdecl의 경우에는 각 함수 호출마다 스택을 정리하는 부분
이 들어가야 하기 때문에 컴파일된 코드의 크기가 더 커질 수 있다고
합니다.

그리고 코드를 C++로 작성중이더라도 라이브러리 파일을 만들어서 다
른 언어에서도 사용할 수 있게 해주려면 extern "C"와 __stdcall
을 사용해서 선언해주는게 좋겠죠?

이런 부분에 대해 자세히 알고 싶으신 분은 MSDN에서 "Mixed-
Language Programming Topics"이라는 글을 참고하시기 바랍니다.
Posted by 창신다이

BLOG main image
오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다. -앙드레 말로- by 창신다이

공지사항

카테고리

분류 전체보기 (248)
공장이야기 (115)
Education (30)
회사이야기 (19)
일상 (73)

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

Total :
Today : Yesterday :