static_cast 와 dynamic_cast

Programming/C++ Language 2015. 1. 7. 11:16

static_cast

: 컴파일러에게, 모든 형 변환에 대해 허용하도록 해주는 구문. 이 형변환에 대한 모든 책임은 프로그래머가 진다.

Car* pCar = new Truck();

Truck* pTruck = static_cast<Truck*>pCar;

pTruck->drive();

정상동작.

Car* pCar = new Car();

Truck* pTruck = static_cast<Truck*>pCar;

pTruck->drive();

비정상동작.

: 따라서, 정말 필요한 경우에만 제한적으로 사용하는 것이 좋다.

: 부모 포인터를 자식 포인터로 변환할때, 의도적으로 한다는 것을 표현할때 좋다.


dynamic_cast

: 상속관계에서의 안전한 형 변환

: 자식 객체의 포인터 및 참조형 변수를 부모형 포인터 및 참조형 변수로 형 변환 해주는데 사용 된다.

Car* pCar = new Truck();

Truck* pTruck = dynamic_cast<Truck*>pCar;

컴파일 에러

Car* pCar = new Car();

Truck* pTruck = dynamic_cast<Truck*>pCar;

컴파일 에러

Car* pTruck = new Truck();

Car* pCar = dynamic_cast<Truck*>pTruck;

컴파일 성공

'Programming > C++ Language' 카테고리의 다른 글

warning 에 대하여  (0) 2015.01.07
typeid 키워드  (0) 2014.04.15
Unique Pointer  (0) 2014.03.03
[C++] 동적바인딩( Dynamic binding )  (0) 2011.01.03
[C++] this 포인터 사용하기  (0) 2010.07.31

설정

트랙백

댓글

warning 에 대하여

Programming/C++ Language 2015. 1. 7. 11:05

1. field 'A' will be initialized after field 'B' [ -Wreorder ]


A가 B 다음에 초기화 될 거라는 경고 이다.Class 의 변수는 생성자에 나열된 순서대로 초기화 되는 것이 아니라, Class 에 선언된 순서대로 초기화가 된다. 따라서 A 와 B 가 다른 변수에 의존적으로 초기화가 될 경우, 의도치 않은 값으로 초기화가 되는 것을 방지하기 위한 경고이다.

Class TestClass{ int A; int B; }

TestClass() : B(n++), A(n++)

위와 같은 코드에서 n이 10일 경우, B에 10, A에 11이 들어가도록 의도적으로 개발을 했다고 할 경우

실제 결과는 A가 10이고 B가 11이 된다.


2. Suppressing warning


switch case 를 사용할 때, 의도적으로 break 를 빼는 경우가 있는데 

이때 컴파일러에서 no break at the end of case 라는 경고를 보여준다. 

이를 없애기 위해서는 eclipse 에서는 해당 경고에 마우스 오른쪽을 누르면 "Customize Problem..." 이라는 메뉴가 있다. 

여기에 보면 Comment text to suppress the problem 이라는 란이 있는데, 

여기에 있는 텍스트가 해당 경고가 표시되는 부분에 주석으로 포함되어 있으면 해당 경고가 사라지게 된다. 

switch(value){ 

case 1: 

... 

// no break

case 2:

break;

}


'Programming > C++ Language' 카테고리의 다른 글

static_cast 와 dynamic_cast  (0) 2015.01.07
typeid 키워드  (0) 2014.04.15
Unique Pointer  (0) 2014.03.03
[C++] 동적바인딩( Dynamic binding )  (0) 2011.01.03
[C++] this 포인터 사용하기  (0) 2010.07.31

설정

트랙백

댓글

typeid 키워드

Programming/C++ Language 2014. 4. 15. 13:36

typeid 키워드는 런타임에 오브젝트의 클래스를 결정하기 위해 사용 된다. std::type_info 오브젝트를 리턴한다.( 프로그램 종료까지 유지 되는 값 ) 단순히 class 정보만이 필요할 경우에, dynamic_cast<class_type> 보다는 typeid 를 사용하는 것을 선호한다.( typeid 의 수행속도가 짧음 )


#include <iostream>    // cout
#include <typeinfo>  //for 'typeid'
 
class Person {
public:
   // ... Person members ...
   virtual ~Person() {}
};
 
class Employee : public Person {
   // ... Employee members ...
};
 
int main() 
{
   Person person;
   Employee employee;
   Person* ptr = &employee;
   Person& ref = employee;
   // The string returned by typeid::name is implementation-defined
   std::cout << typeid(person).name() << std::endl;   // Person (statically known at compile-time)
   std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
   std::cout << typeid(ptr).name() << std::endl;      // Person* (statically known at compile-time)
   std::cout << typeid(*ptr).name() << std::endl;     // Employee (looked up dynamically at run-time
                                                      //           because it is the dereference of a
                                                      //           pointer to a polymorphic class)
   std::cout << typeid(ref).name() << std::endl;      // Employee (references can also be polymorphic)
 
   Person* p = nullptr;
   try {
      typeid(*p); // not undefined behavior; throws std::bad_typeid
                  // *p, *(p), *((p)), etc. all behave identically
   }
   catch (...)
   {}
 
   Person& pRef = *p; // Undefined behavior: dereferencing null
   typeid(pRef);      // does not meet requirements to throw std::bad_typeid
                      // because the expression for typeid is not the result
                      // of applying the unary * operator
}

Output (exact output varies by system):

Person
Employee
Person*
Employee
Employee

출처 : http://en.wikipedia.org/wiki/Typeid

'Programming > C++ Language' 카테고리의 다른 글

static_cast 와 dynamic_cast  (0) 2015.01.07
warning 에 대하여  (0) 2015.01.07
Unique Pointer  (0) 2014.03.03
[C++] 동적바인딩( Dynamic binding )  (0) 2011.01.03
[C++] this 포인터 사용하기  (0) 2010.07.31

설정

트랙백

댓글

Unique Pointer

Programming/C++ Language 2014. 3. 3. 14:59



Related


template <class T>

class rv

{

    T& r_;


public:

    explicit rv(T& r) : r_(r) {}

    T* operator->() {return &r_;}

    T& operator*() {return r_;}

};


template

rv<int> intRV; 와 같이 <> 안에 자료형을 넣어 T를 해당 자료형으로 변환 시켜준다.


explicit

class 생성자의 묵시적 호출을 방지하는 키워드.

int a;

rv<int> classRV = a;

위와 같이 호출을 하였을 때, 컴파일러가 암묵적으로 classRV(a) 를 호출해주는 것을 방지한다. 이로인해 의도치 않은 동작을 방지 할 수 있다.

생성자를 호출하기 위해서는 반듯이 rv<int> classRV(a) 와 같이 호출이 되어야 한다.



typename


typedef typename add_reference<deleter_type>::type deleter_reference;


typedef 는 기존 정의된 자료형사용자 정의 자료형으로 이름을 부여할 때 사용한다.

따라소 자료형 만이 다음에 올 수가 있는데, 위의 코드에서는 add_reference<deleter_type>::type 이 있는데 이는 변수가 될 수도 있고 자료형이 될 수도 있다. 따라서 컴파일러에게 혼돈을 주게되므로, 컴파일러에게 ~::type 은 자료형이라는 것을 알려주기 위해 사용하는 키워드이다.

'Programming > C++ Language' 카테고리의 다른 글

warning 에 대하여  (0) 2015.01.07
typeid 키워드  (0) 2014.04.15
[C++] 동적바인딩( Dynamic binding )  (0) 2011.01.03
[C++] this 포인터 사용하기  (0) 2010.07.31
[C++] 참조 변수  (1) 2010.07.31

설정

트랙백

댓글

[C++] 동적바인딩( Dynamic binding )

Programming/C++ Language 2011. 1. 3. 15:53
virtual 함수에 대해서 이야기를 해보겠습니다.

많은 개발자들이 상속을 사용하면서, virtual 함수도 많이 사용을 하게 되는데 virtual 함수를 사용함에 있어 실수 할 수도 있을 부분에 대해서 말해보겠습니다.

bool compare_garades( Core c1, Core c2 )
{
    return c1.grade() < c2.grade();
}

위와 같은 코드가 있고, Core라는 Base class가 있고, grade라는 virtual 함수가 있고, Core를 상속받는 Grad 라는 클래스가 있고, Grad 클래스는 grade함수를 오버라이드하여 재정의를 했다는 것을 전제 조건으로 설명하겠습니다.

실행할 virtual 함수를 런타임에 선택하는 것은 그 함수가 레퍼런스나 포인터를 통해 호출 될 때에만 가능한 것입니다. 하지만 객체 자체에 대한 virtual 함수를 호출하면 컴파일 시에 그 객체의 정확한 타입을 결정할 수 있습니다. 따라서 위와 같은 경우, Core라는 객체의 정확한 타입을 알수 있기 때문에 c1과 c2에 실제로 Grad함수가 전달이 되더라도, 매개변수가 객체에 대한 레퍼런스가 아니라, 객체 그 자체이므로 Grad객체의 Core파트만 잘라내어 그에 대한 복사본이compare_grades 함수의 매개변수로 전달이 되게 됩니다. 즉, 정적 바인딩이 수행되는 것입니다.

출처 : Accelerated C++

'Programming > C++ Language' 카테고리의 다른 글

typeid 키워드  (0) 2014.04.15
Unique Pointer  (0) 2014.03.03
[C++] this 포인터 사용하기  (0) 2010.07.31
[C++] 참조 변수  (1) 2010.07.31
[C++] 참조 반환  (0) 2010.07.31

설정

트랙백

댓글

[C++] this 포인터 사용하기

Programming/C++ Language 2010. 7. 31. 18:56
연속적인 함수 호출을 위해 this 포인터를 사용해 보도록 하자.

SetHour, SetMinute, SetSecond 라는 함수가 있다고 했을 때, 3번의 각각의 호출이 필요하다.
이때, this 포인터를 반환하는 즉, this 참조 반환을 이용하면 연속적으로 호출이 가능하다.
아래의 예문을 살펴보자.


CTimes& CTimes::SetHour(int nHour)

 hour = nHour;
 
 return *this;
}

CTimes& CTimes::SetMinute( int m )
{
 minute = m;
 
 return *this;
}
 
CTimes& CTimes::SetSecond( int s )
{
 second = s;
 
 return *this;

위와 같이 Set 함수에 *this 를 반환을 하게 되면, 호출 하는 쪽에서 *this를 호출하여 연속적으로 아래와 같이 호출 할 수 있게 된다.

CTimes time;
 
 time.SetHour(10).SetMinute(20).SetSecond(30);
 
 time.Print();

특별히 성능이 좋아진다거나 그런 것은 없지만, 이를 알고 있다면 코드가 간단해서 보기 쉽지 않을까 생각한다. 앞으로 잘 이용하면 좋은 문법이 될 것 같다.

'Programming > C++ Language' 카테고리의 다른 글

Unique Pointer  (0) 2014.03.03
[C++] 동적바인딩( Dynamic binding )  (0) 2011.01.03
[C++] 참조 변수  (1) 2010.07.31
[C++] 참조 반환  (0) 2010.07.31
[C++] 클래스 I  (0) 2010.07.21

설정

트랙백

댓글

[C++] 참조 변수

Programming/C++ Language 2010. 7. 31. 17:22
참조 반환에 이어 참조 변수에 대해 알아보자.

참조 변수는 DataType& 를 이용하여 선언을 한다. 그리고, 어떤 변수의 참조를 가지도록 초기화할 때는 변수를 선언 할 때만 가능하다.

아래의 코드를 보고, 결과를 예상해 보자.

int a = 10, b = 20;
int& nRef = a;
  
cout << "a is " << a << endl;
 
nRef = 100;
 
cout << "a is " << a << endl;
 
nRef = b;
 
cout << "a is " << a << endl;
cout << "b is " << b << endl;

결과는 아래와 같다.( 상자 안을 마우스로 클릭하여 영역을 잡으면 보인다. )
a is 10
a is 100
a is 20
b is 20


결과 처럼, nRef = b 구문을 실행 할 떄는 nRef 가 b 변수를 참조 하는 것이 아니라, 이미 nRef 는 a 변수를 참조 하고 있으므로, b의 값을 a 에 대입을 하게 된다.


이러한 참조변수를 이용하여 Call by Reference 를 이용 할 수 있다. C에서는 Call by Reference 를 이용하기 위해서 포인터 변수를 사용했지만, C++ 에서는 대신에 참조변수를 이용하면 된다.
-> void Func( int& param1 ) {}

설정

트랙백

댓글

[C++] 참조 반환

Programming/C++ Language 2010. 7. 31. 16:54
C++ 의 문법 중의 하나인, 참조변수가 있다. 이를 잘못 사용하게 되면 객체지향의 규칙을 깨버릴 수 있다. 그리고, 컴파일 에러는 전혀 나지 않는다. 한번 살펴 보도록 하자.

Times.h
class CTimes
{
public:
   CTimes( int = 0, int = 0, int = 0 );
   ~CTimes();
   int& GetHour();
   void Print();

private:
   int hour;
   int minute;
   int second;
};

Times.cpp
int& CTimes::GetHour()
{
return hour;
}

void CTimes::Print()
{
cout << "hour is " << hour << endl;
}

위와 같이 Times 클래스가 선언이 되어 있고, GetHour 유틸리티 함수는 int형이 아니라 int& 즉, int 참조변수형을 리턴하고 있다.
이제, 아래의 Main 문을 보도록 하자. 세번의 출력 결과를 예상해 보자.

main.cpp
int main()
{
Ctimes time( 1, 2 );
time.Print();

int a = time.GetHour();
a = 5;
time.Print();

int& b = time.GetHour();
b = 10;
time.Print();

return 0;
}

출력 결과는 아래와 같다..( 상자 안을 마우스로 클릭하여 영역을 잡으면 보인다. )

hour is 1
hour is 1
hour is 10


즉, 참조 변수 형으로 변수를 받게 되면 참조형 변수는 private, public 여부에 관계없이 변경을 할 수 있다. 물론 Get 함수에서 포인터로 변수의 주소를 넘겨줄 경우, 받는 쪽에서 private 이든, public 이든 관계없이 이를 변경 시킬수 있다. 클래스 캡슐화를 위반하는 문법을 사용 하는 것을 피하도록 하자.

값을 받아서 사용 할 때의 문법상의 차이점 말고는 실질적인 원리는 어떻게 다른지 잘 모르겠다. 알아봐야겠다. :)

설정

트랙백

댓글

[C++] 클래스 I

Programming/C++ Language 2010. 7. 21. 15:42
생성자
: 기본 생성자를 호출하지 않거나, 만들지 않더라도 묵시적으로 호출 및 생성이 되어서 호출이 된다. 물론, 생성자가 만들어지지 않았을 경우, 아무동작도 하지 않는다.
: 생성자는 오버로드 가능하여 클래스 객체를 초기화 하는 다양한 방법을 제공한다.
: 디폴트 생성자 - 클래스당 하나의 디폴트 생성자가 존재 할 수 있다. 라고 책에 되어 있다. 사실상 컴파일 상에서는 디폴트 생성자가 여러개 존재 할 수 있다.
class CTimes
{
public:
~CTimes(void);
CTimes( int = 0, int = 0, int = 0 ); - ①
CTimes( int = 0, int = 0 ); - ②
....
}


그리고, CTimes time( 1, 2, 3 ); 을 호출하게 되면, ① 의 생성자가 호출 된다.
하지만, CTimes time( 1, 2 ); 을 호출하게 되면, "ambiguous call to overloaded function" 이라는 에러 문구를 보여준다. 애매한 호출을 한다는 것이다. time( 1, 2 ) 를 호출하면, ① 도 가능하고, ② 도 가능하다. 그러므로 컴파일러에서 혼돈을 하게 되는 것이다.

소멸자
: 생성자와 마찬가지로 명시적으로 만들지 않더라도, C++ 컴파일러에서 아무것도 하지 않는 디폴트 소멸자를 만들어준다.
: 소멸자는 오버로드가 불가능하다.

생성자, 소멸자 호출 순서
: 일반적으로 소멸자는 생성자가 호출되는 순서의 반대이다.
: 전역적으로 정의된 객체의 생성자는 main 함수를 비롯한 파일 내의 함수가 실행을 시작하기 전에 호출 된다. 그리고, 전역 객체의 생성자가 호출되는 순서는 보장되지 않는다.
: 전역 및 static 객체의 소멸자는 main 함수가 종료되거나, exit 함수가 호출될 때 호출된다.
: abort 함수를 호출하여 종료하게 되면, 전역 및 static 객체의 소멸자는 호출되지 않는다.
: static 지역 객체의 생성자는, main에서 해당 코드 부분이 수행될 때 호출이 되고,

Inline 함수
Class A
{
result GetResult() { return __r; }
result SetValue( int nVal ) { __val = nVal };
result SetField( String str );
}

inline result A::SetField( String str )
{
__str = str;
}
위와 같이, 빨간색의 멤버함수가 클래스 몸체에 정의 되어 있으면, C++ 컴파일러는 그 멤버 함수를 인라인으로 호출하려 할 것이다.
SetField 같은 경우에는 클래스 외부에서 정의된 멤버 함수 이므로 inline 키워드를 명시적으로 붙여 줘야 inline으로 호출을 하도록 요청 할 수 있다.
참고로, inline 으로 명시한다고 해서 항상 inline 으로 호출된다는 보장을 하지 않는다.

private 데이터 멤버에 참조반환

Class A
{
public:
int &badSetHour(int );
private:
int hour;
}

int &Time::badSetHour( int h )
{
.....
return hour;
}

main()
{
     int& a = time.GetHour();
     a = 10;
}


위와 같이, private 변수를 참조반환을 하는 경우, hour 의 값을 반환하는 것이 아니라 hour 변수 자체 반환하는 것 처럼 되기 때문에, 외부에서 참조반환을 사용하여 private 변수를 직접 접근가능하게 되서, 객체지향에 위배되게 된다. 따라서 참조반환은 사용하지 않도록 하자.
위의 경우, a에서 10을 넣게 되면, time 객체의 private 변수인 hour 에 10이 들어가게 된다. 

설정

트랙백

댓글

[C++] C++상에서 발생하는 name mangling에 관한 내용

Programming/C++ Language 2010. 7. 16. 08:56
C++상에서 발생하는 name mangling에 관한 내용

1. name mangling 이란?
간단히 말하면 compiler 가 임의로 함수나 변수의 이름을 변경하는 것을 의미합니다.
그렇다면 왜 함수나 변수의 이름을 변경하는 것인가?
이를 설명하기 위해서는  C++언어의 성격상 function overloading을 먼저 설명해야 하는데요.

function overloading이란 개발자가 소스코드를 작성할때 같은 이름으로 다른 기능들을 수행하는 함수를 만들수 있도록 하는 기능을 예기합니다..
예를 들면,

int Add( int a,int b){return a+b;}
float Add( float a,float b){return a+b;}
float Add( float a,float b, float c){return a+b+c;}

위와 같이 Add라는 함수를 int형을 더하는 함수, float 형을 더하는 함수, 세 변수를 더하는 함수등등 여러개의 함수를 작성하고, 개발자가 이를 임의로 아무거나 불러 쓸 수 있습니다.


이런것이 가능하도록 해주는 기능이 바로 함수 overloading이라는 개념입니다..

그럼 compiler는 소스 코드에서 Add를 호출 했을때 어떻게 각각의 Add를 알아서 찾는가 하는 의문이 생길수 있을것인데,  이를 가능하게 해주는것이 name mangling 이다.

compiler는 symbol을 생성할때 함수 이름과 함수 parameter를 고려 하여 symbol을 만듭니다..
즉,
int Add( int a,int b)  -> Add + int+ int --> Addii 와 같은 형식으로 만든다는 것입니다..
물론, compiler 마다 다른 mangling 규칙을 가지고 있습니다..

인터넷에 떠도는 문서들을 찾다보니 아주 한눈에 잘 들어오도록 정리된 표가 있어서 샤샥!! 퍼옴...


Compiler void h(int) void h(int, char) void h(void)
Intel C++ 8.0 for Linux _Z1hi _Z1hic _Z1hv
HP aC++ A.05.55 IA-64 _Z1hi _Z1hic _Z1hv
GNU GCC 3.x and 4.x _Z1hi _Z1hic _Z1hv
HP aC++ A.03.45 PA-RISC h__Fi h__Fic h__Fv
GNU GCC 2.9x h__Fi h__Fic h__Fv
Microsoft VC++ v6/v7 ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Digital Mars C++ ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Borland C++ v3.1 @h$qi @h$qizc @h$qv
OpenVMS C++ V6.5 (ARM mode) H__XI H__XIC H__XV
OpenVMS C++ V6.5 (ANSI mode) CXX$__7H__FI0ARG51T CXX$__7H__FIC26CDH77 CXX$__7H__FV2CB06E8
OpenVMS C++ X7.1 IA-64 CXX$_Z1HI2DSQ26A CXX$_Z1HIC2NP3LI4 CXX$_Z1HV0BCA19V
SunPro CC __1cBh6Fi_v_ __1cBh6Fic_v_ __1cBh6F_v_
Tru64 C++ V6.5 (ARM mode) h__Xi h__Xic h__Xv
Tru64 C++ V6.5 (ANSI mode) __7h__Fi __7h__Fic __7h__Fv
Watcom C++ 10.6 W?h$n(i)v W?h$n(ia)v W?h$n()v


위와 같이 컴파일러 마다 다른 mangling 규칙을 가지고 있답니다..



자 그럼 C에 대한 이야기도 해야 하는데, C의 경우는 mangled 된 함수 이름이 필요 없습니다.
이유는 C에서는 function overloading을 지원하지 않기 때문에 한 binary안에서는 하나의 function만 존재 합니다.
하지만 !!!  실제로 compile을 해보면 각 함수 이름을 살짝 바꿔 놓기는 합니다. 예를 들면,

void Add(int a,int b) ---> _Add 라는 형식 으로 앞에 '_'를 붙이는 경우부터 해서... 이것 역시 compiler가 각자 규칙을 가지고 진행 합니다.

이런 mangled name 을 확인 해보는 방법은 compile 된 object file(xxx.o) 나 실행 화일 또는 .lib 파일을 hex editor로 열어서 확인 해보면 눈으로 볼 수 있답니다.


2. extern "C" ?
자 , 사실 name mangling 이 발생한다는 것이 개발자가 알고 있어야 하는 지식이기는 하나 그렇다고 mangled 된 이름 가지고 뭘 어떻게 하라는 예기가 사실 그다지 중요하지 않다.
이글의 본 취지는 사실 extern "C" 에 있다.

저도 개발 초기에는 막연하게 "extern "C"를 붙이면 함수의 이름이 C 처럼 나오기 때문에 C++로 짜여진 함수도 C에서 사용할 수 있다." 라고만 알고 있었습니다.

이런 저런 개발을 하다보니 정확한 용법이나 메카니즘을 이해하지 않고 막연하게 사용하는 코드들이나 개념들은 결국 부매랑이 되어 저에게 돌아오더군요.!!
무슨 말이냐 하면,C++에서 짠 함수가 C에서도 사용할수 있게 하려면  extern "C" 를 붙이라는데 어디다? 어떻게 ? 라는 의문이 생겨날 것입니다.
근데 extern "C"에 대해 잘 모르는 사람이 위와 같은 조언을 들었으면,, 분명 extern "C"를 남발하게 됩니다.!!  이는 결국 나중에 compile error를 양산하기도 하고, 코드를 지저분하게 만들기도 하고, 아무튼 문제를 많이 일으키게 되죠.!!! 그런적 없다구요? 전 당했습니다.!! 


자 그럼 하나 하나 차근 차근 짚어 보겠습니다.


extern "C"  --> 이 형식은 extern "C" 이후에 오는 내용을 name mangling 하지 않고 C처럼 symbol을 만들 어라 라는 명령어입니다.

즉, abc.cpp 라는 소스코드에서 아래 abc라는 함수와 def라는 함수를 예로 들어보면,
1 int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
2 {
3 return 0;
4 }
5   
6 extern "C" int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
7 {
8 return 0;
9 }
def는 ii 같은 것이 추가되지 않고 그냥 def라는 이름을 갖게 됩니다.

그러면, 모든 함수 구현 루틴에 extern "C" 를 붙이는것이 사실 귀찮고 힘든 일입니다.
그래서 , 연륜이 있는 개발자들은(저처럼 ㅋㅋㅋ)  extern "C" {}를 활용하게 되는 것이죠.
01 <FILE: abc.cpp="">
02   
03 extern "C"
04 {//<-- extern 시작
05 int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
06 {
07 return 0;
08 }
09   
10 int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
11 {
12 return 0;
13 }
14 }//extern 끝
15 </FILE:>
이렇게 extern "C" {}를 사용하면 {} 안에 있는 모든 내용은 name을 mangling하지 않는 다는 선언이 됩니다.

이렇게 사용하면 모든 함수들을 따라다니면서 extern "C" 를 붙일 필요가 없어지는 것입니다.


또, 다른 형식으로 사용하는 case가 Header file extern "C"를 사용하는 경우가 있습니다.
(대단히 많이 사용하는 형식입니다. 이유는 관리하기가 편해서? )
01 <FILE: abc.h="">
02 #ifndef __ABC_H__
03 #define __ABC_H__
04   
05 extern "C"
06 {//<-- extern 시작
07   
08 #include "sysconfig.h"
09 #include "hello.h"
10 #include "test.h"
11   
12 //function prototype
13 int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
14 int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.
15   
16   
17 }//extern 끝
18   
19 #endif //__ABC_H__
20 </FILE:>
자!! 이제 위와 같은 포멧으로 주로 많이들 사용하는데, 위의 내용을 보면 어떤가요? 문제가 있어보이나요 없어보이나요?

얼핏 보기엔 아무런 문제가 없어 보입니다.
하지만!!!!!

한가지 찾아낸 분이 있군요.... 그렇숩니다. ^^a 위의 해더파일은 C 파일에서는 include 해서 사용할 수 없습니다.

이유는 extern "C" 는 C++ compiler에서만 지원하기 때문입니다.
a.c 에서 #include "abc.h"를  사용하면 error가 발생합니다. 
그래서 
#ifdef __cplusplus 를 이용해서 extern "C" 를 묶어서 사용합니다. 
01 <FILE: abc.h="">
02 #ifndef __ABC_H__
03 #define __ABC_H__
04   
05 #ifdef __cplusplus 
06 extern "C" {//<-- extern 시작
07 #endif
08 #include "sysconfig.h"
09 #include "hello.h"
10 #include "test.h"
11   
12 //function prototype
13 int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
14 int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.
15   
16 #ifdef __cplusplus 
17 }//extern 끝
18 #endif
19 #endif //__ABC_H__
20 </FILE:>
이렇게 사용함으로 해서 C compiler에서는 extern "C"를 사용하지 않고 compile됩니다.
C compiler에서는 c니까 당연히 name mangling이 발생하지 않겠죠.!!!! <-- 요거 중요한 예기...


자 그럼 두번째 문제점은 어디에 있을까요??
못찾겠죠... 흔히들 실수 할 수 있는 부분입니다. ( 찾았다구요?? 이 글 왜 읽고 있습니까?? 자리로 돌아가서 일하세요.. ㅎㅎ 다 아시는 분이.. ~~)

두번째 문제는 사실 편하게 쓰고자 extern "C" {} 를 사용하는데 이게 아래와 같이 사용하면 문제를 만듭니다. 무슨 말이냐 하면.. extern "C" {} 중간에 header file을 포함하고 있죠??
그래서 abc.h 가 sysconfig.h를 include 하고 있고, sysconfig.h는 def.h를 include하고 있다면,
extern "C"{ //<-- abc.h
    extern "C" { //<--sysconfig.h
           extern "C"{//<-- sysconfig.h 내에서 include 하고 있는 def.h
 
이런 형식으로 가다가 결국 extern "C"가 너무 많이 중복 사용되었다는 에러메세지를 받고 당황하게 되실 것입니다. ㅎ

결국 VC에서는 아래와 같은 에러를 내면서 컴파일 에러를 냅니다.
fatal error C1045: compiler limit : linkage specifications nested too deeply
visual Studio 에서는 depth 제한이 10으로 되어있습니다.

그래서 당황하여 여기저기 extern "C"를 찾아서 지우고 옮기고 하다보면, 나중에 name mangling 되어 link error를 메세지를 받고 뒷목을 붙잡고 쓰러질 수도 있습니다.
이래서 어설프게 알고 사용하면 절대 안된다는 예깁니다.
저는 그래서 아래와 같이 header file 선언부 아래에 extern "C"를 사용하기를 권합니다.
01 <FILE: abc.h="">
02 #ifndef __ABC_H__
03 #define __ABC_H__
04   
05 //<<REMOVED>>
06 #include "sysconfig.h"
07 #include "hello.h"
08 #include "test.h"
09   
10 #ifdef __cplusplus 
11 extern "C" {//<-- extern 시작
12 #endif 
13 //function prototype
14 int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
15 int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.
16   
17 #ifdef __cplusplus 
18 }//extern 끝
19 #endif
20 #endif //__ABC_H__
21 </REMOVED></FILE:>
모든 header file들이 위와 같이 include file 아래서 부터 extern "C"를 사용하면, 아래와 같이 중복되지 않게 되어 depth가 늘어나지 않습니다.  <-- 요거 꽤 중요한 팁이니 자기가 맡고 있는 프로젝트가 있다면 한번 살펴보세요!! ㅎ

extern "C"
{//<-- sysconfig.h 내에서 include 하고 있는 def.h
}
extern "C" 
{ //<--sysconfig.h
}
extern "C"
{ //<-- abc.h
           :
           :
}


자!! 이제 가장 중요하고 핵심이 되는 문제가 남았습니다.

이 문제는 위에 있는 해더파일 내용으로만 봐서는 절대로 찾을 수 없는 내용인데요..

뭐냐하면..

위와 같이 header 파일을 잘 만들었다 하더라도 link error를 당할 수 있는 경우가 있다는 것입니다.!!

어떤 경우일까요? 흠흠.ㅋ ㅋㅋ


어떤 경우냐 하면 ,  우리가 만든 
abc 와 def 함수의 몸체는 abc.cpp 에 있죠?
function name 이 mangling 될때가 언제라고 했죠?
link 할때? 아니죠~~ compile 할때.. 즉 compiler가 abc.o를 만들때 name이 mangling됩니다.
즉, name mangling을 막을려면 abc.cpp가 compile될때 막아야 합니다.
즉 그말은 abc.cpp가 compile될때 이미 extern "C"가 함수들에 선언 되어 있어야 name mangling을 막을 수 있다는 예기 입니다.

이미 abc.h에 extern "C"로 잘 감싸 놨는데 무슨 예기냐 ~~ 모르겠다... 하시는 분들 계실 겁니다.

abc.cpp가 아래와 같이 extern "C"가 없다고 합시다. 그리고 main.c에서 아래와 같이 abc를 사용했다고 합시다.
01 <FILE: abc.cpp="">
02   
03   
04 int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
05 {
06 return 0;
07 }
08   
09 int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
10 {
11 return 0;
12 }
13   
14 </FILE:>
01 <FILE: main.c="">
02   
03 #include "abc.h"
04   
05 void main()
06 {
07     abc(10,20);
08 }
09   
10 </FILE:>
아.. 하시는 분 있을거 같은데 ㅎㅎㅎ. 좀 감이 오시나요? 어쨌든 설명을 이어 가겠습니다.

main 에서 abc를 호출하면 main은 c 파일이기 때문에 mangliing 안된 abc 를 symbol로 link 합니다.

하지만 abc.cpp를 compiler가 컴파일 할때는 abcii, defii 로 mangling 된 이름으로 symbol을 만듭니다.
왜? abc.cpp를 보십시오. abc.cpp안에는 어디에도 extern "C"라고 선언된 부분이 없습니다.
그래서 C++ compiler는 함수 이름을 mangling해버립니다.
그래서 main에서 부른 abc는 symbol만있고 body가 없기 때문에 link 시에 에러가 발생합니다.

이제 아셨죠.?? 이런 정확한 메카니즘을 모르면 왜 링크에러나는지 모르고 이것 저것 삽질하다 수정되면 그냥 넘어가 버리게 됩니다.!!! 코드만 지저분해지게 만들구요..

그러면 abc, def가  mangling 안되게 만들려면 어떻게 할까요? 맨 위에 예제 처럼 cpp 파일 처음부터 끝까지 extern "C"로 감싸면 되겠죠? 
제가 적극 추천하는 방법은  abc.h를 abc.c 에서 include 하는 방법을 추천합니다.
01 <FILE: abc.cpp="">
02   
03 #include "abc.h"
04   
05   
06 int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
07 {
08 return 0;
09 }
10   
11 int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
12 {
13 return 0;
14 }
15   
16 </FILE:>
이렇게 하면 compiler가 abc.cpp 를 컴파일 할때 abc.h에 있는 extern "C"{} 로 되어있는 영역 내부의 내용에 해당하는 abc,def함수들을 모두 mangling안되게 해준답니다. 


3.마치며!!

항상 강조하지만 100% 이해하지 못하는 code나 함수를 내 프로그램에 추가하게 되면 반드시 그 대가를 치르게 됩니다.!!! 이는 불변의 진리입니다.!!!
그러니 여러분들도 "남들이 이렇게 쓰니 나도 이렇게 써야지 " 이렇게 하지 마시고, 자신이 사용하고자 하는 것에 대한 내용을 최대한 깊게 이해하려고 노력하십시오.

그러면 나중에 돌아올 부매랑을 막을 방법도 갖게 될 것입니다.!!


출처 : http://spikez.tistory.com/19

'Programming > C++ Language' 카테고리의 다른 글

[C++] 참조 반환  (0) 2010.07.31
[C++] 클래스 I  (0) 2010.07.21
[C++] Errors : cannot allocate an object of abstract type '???'  (0) 2010.06.14
[C++] 복사 생성자  (0) 2010.05.31
[C++] Construct method  (0) 2010.05.27

설정

트랙백

댓글