• 1.10.1 전처리기 include & define
  • 1.10.2 전처리기 ifdef & endif

1.10.1 전처리기 include & define


전처리기 중에 가장 익숙한 전처리기가 아닐까 싶다.

#include 전처리기를 사용하면 프로그램에 필요한 라이브러리를 끌어다가 쓸 수 있다. 가장 처음 쓰게 되는 라이브러리는 아마도 #include <iostream>인 것 같다.

#include <iostream> 
using namespace std; 
#define MY_NUMBER 333 
#define MAX(a, b) (((a)>(b)) ? (a) : (b)) 
#include <algorithm> 
#define LIKE_APPLE


#define을 통해 코드를 좀더 깔끔하게 작성할 수 있다. 매크로(marco)라는 명칭으로도 부르는 이 전처리기는 코드 내에 해당 매크로가 존재하는 경우 정의한 데이터로 컴파일 단계에서 교체를 해준다. 이를 통해 과거에는 많은 사람들이 max 함수를 만들어서 쓰곤 했는데, 지나친 괄호를 작성해야 올바르게 동작하기 떄문에 요즘에는 쓰질 않는다. 처음에는 공부하는 차원에서 해볼 수 있지만 max 함수 자체는 algorithm 라이브러리에 존재하니 끌어다가 쓰면 되겠다.

#define을 통해 교체하는 작업을 하지 않는 경우가 있는데, 바로 데이터를 작성하지 않고서 #define NAME만으로 작성된 매크로이다. 이전에도 사용하긴 했지만 #ifdef를 활용하여 코드를 작성할 때 사용하게 된다. 또한 해당 매크로는 작성된 파일 내에서만 영향력을 가지며, 그 영역을 벗어나서 동작하는 함수가 있다면 매크로가 없는 것으로 간주되니 주의하자. 이 부분은 뒤에 예시로 한번 보도록 하겠다!

참고로 매크로는 항상 대문자로만 구성해야하는 프로그래머의 관습이 존재한다.


1.10.2 전처리기 ifdef & endif

// 다른 코드에 정의되어 있는 함수 
void doSomething();


int main()
{
	cout << MY_NUMBER << endl; cout << std::max(1, 2) << endl;
#ifdef LIKE_APPLE 
	cout << "Apple " << endl; 
#endif 

#ifndef LIKE_APPLE 
	cout << "Orange " << endl; 
#endif 

#ifdef LIKE_APPLE 
	cout << "Apple " << endl; 
#else 
	cout << "Orange " << endl; 
#endif 
	
	doSomething(); 

	return 0;
}


먼저 작성된 코드와 함께 붙여서 쓰면 되는 코드이다. #ifdef LIKE_APPLE는 LIKE_APPLE이 정의되어 있다면 #endif까지 동작하게 해주는 전처리기이다. #ifndef는 반대로 정의되어 있지 않은 경우에 동작하게 된다. 위처럼 ifdef와 ifndef로 분할해서 만들수도 있지만, ifdef와 else를 활용하여 작성할 수도 있다. 둘 다 똑같이 작동하게 될 것이다.

앞에서 LIKE_APPLE을 정의했으니 Apple이 출력되도록 코드가 작동할 것이다. 실제 화면을 보면 Orange 파트의 코드는 희미하게 표기가 된다.

LIKE_APPLE이 정의된 경우

doSomething 함수는 다음과 같다.

doSomething

다른 파일에 작성된 함수이고 전방 선언을 통해 가져오도록 한다. 여기서는 Orange가 출력 될 것으로 보이는데, 앞의 코드들을 합치면 이미 LIKE_APPLE이 정의되어 있으니 doSomething도 Apple을 출력해야 할 것 같다는 생각이 든다.

result

실제 결과를 보면 doSomething에 의해 Orange가 출력되었다. doSomething 함수를 호출하는 곳에는 LIKE_APPLE이 정의되어 있어서 Apple을 출력시켰지만 doSomething 함수를 실행하기 위해 건너가는 파일에는 정의가 되어 있지 않아서 Orange가 출력된다. 이처럼 매크로의 정의는 해당 파일에만 한정되어 있으니 주의해야하며, 만약 Apple을 출력시키고 싶다면 해당 매크로를 또 작성하거나 매크로가 작성된 파일을 include로 끌어오면 되겠다.

  • 1.9.1 네임스페이스(namespace)
  • 1.9.2 using namespace

1.9.1 네임스페이스(namespace)

 

우리말로는 명칭 공간이라고도 하는 개념이 C++에 있다. 프로그램을 만들다 보면 다른 동작을 하면서 같은 이름의 함수를 많이 쓰게 되는데, 이때 컴파일러가 어떤 함수인지 명확히 선택할 수 있도록 구분해줘야 한다. 이를 위해 사용하는 것이 바로 네임스페이스이다.

 

#include <iostream>

namespace MySpace1
{
	// 네임스페이스 안에 네임스페이스 가능
	namespace InnerSpace
	{
		int myFunction()
		{
			return 0;
		}
	}
	int doSomething(int a, int b)
	{
		return a + b;
	}
}

namespace MySpace2
{

	int doSomething(int a, int b)
	{
		return a * b;
	}
}


using namespace std;

int main()
{
	
	// 3*4가 실행된다.
	cout << MySpace2::doSomething(3, 4) << endl;

	//앞으로 MySpace1이라는 namespace를 사용할 것이다.
	using namespace MySpace1;

	// 3+4가 실행된다.
	cout << doSomething(3, 4) << endl;
	InnerSpace::myFunction();

	return 0;
}

 

코드를 보면 같은 이름의 함수가 존재하지만 빌드가 잘되는 것을 볼 수가 있다. 바로 네임스페이스 개념을 사용하였기 때문이다. 네임스페이스 안에 함수를 정의하면 해당 함수를 사용할 때는 네임스페이스를 앞에 작성해야 하고 이를 통해 컴파일러가 어떤 함수인지 혼동하지 않고 바로 찾을 수가 있다.

 

이러한 방식이 왜 필요한지 독학을 하는 경우엔 이해가 되지 않을 수 있는데, 실무에 가면 협업하는 사람들이나 선배님들이 작성한 코드에서 함수의 이름을 잘 나타내기 위해 최대한 보편적인 언어로 작성을 한다. 그러한 방식을 사용하다 보니 서로가 같은 이름의 함수를 작성하는 경우가 생기기도 한다. 실제로 현 직장에서 다루는 프로그램의 코드를 보면 configure 함수가 여러 개 존재하는 것을 볼 수가 있다.


1.9.2 using namespace

 

아마 많은 C++ 개발자들이 기초 교육을 받을 때 많이 써본적이 있을 것이다. using namespace std;라는 명령문은 정말 친숙하게 사용했을 것이라 생각한다. using namespace를 통해 해당 영역을 벗어나지 않는다면 계속 언급한 네임스페이스를 사용할테니 네임스페이스를 제외하고 함수를 호출해도 알아서 찾아달라는 의미이다.

 

앞의 예시에서는 std를 그런식으로 쓰고 MySpace1도 사용하였다. 이처럼 네임스페이스를 구현하고 사용하는 방법이 다채로운데, 처음 접한다면 시간을 투자해서 이것저것 실험을 해보는 것이 좋겠다.

  • 1.8.1 링킹 에러
  • 1.8.2 중복 정의
  • 1.8.3 헤더 가드(Header Guards)

1.8.1 링킹 에러

 

Linking Error

프로그램을 만들다 보면 LNK2019, LNK1120이라는 링킹 에러를 만나는 경우가 종종 생긴다. 위와 같은 에러가 생기는 이유는 특정 함수에 대하여 선언은 존재하지만 정의가 존재하지 않는 경우이다. 

 

add function header

위처럼 add 함수에 대한 선언을 헤더 파일로 작성하고서 include를 통해 main 함수가 작성된 코드에서 add 함수를 사용했다면 전방 선언이 된 효과를 가지게 된다. 선언이 되어 있으므로 컴파일러는 통과를 시키고 빌드가 이루어지는데, 결국 해당 함수의 정의가 담긴 cpp 파일이 없다면 함수 호출 뒤에 어떤 연산을 해야 할지 모르는 상황이 발생하면서 링킹 에러가 생긴다. 따라서 링킹 에러가 생긴다면 혹시나 정의를 빼먹은 함수가 없는지 확인해보자!


1.8.2 중복 정의

 

위와는 다르게 과도한 정의로 인한 에러가 생기는 경우가 있다.

function already has a body

이처럼 C2084 에러가 잡히면 Description 의미 그대로 이미 중복 선언이 된 경우이다. 일반적으로 많은 함수를 선언과 정의를 해서 헤더 파일로 가지고 있다면 여기저기서 include를 통해 함수를 재사용하고 있을 것이다. 다음의 예시를 보자.

 

add header
dosomething header

add 함수가 있는 헤더 파일을 만들고 add 함수를 끌어다 쓰는 doSomething 함수를 만들어 다른 헤더 파일에 만들었다.

 

main 함수

그리고 main 함수가 담긴 cpp 파일이 위처럼 작성되어 있다. 여기서 #include 전처리기가 작동하는 방식을 이해하면 왜 중복 정의인지 알 수가 있는데, #include 전처리기는 해당 파일을 복사해서 가지고 오는 것처럼 작동한다. 따라서 위의 코드는 아래와 같은 상황이 벌어진다.

 

add 중복


1.8.3 헤더 가드(Header Guards)

 

아마 헤더 파일을 만드는 순간 #pragma once라는 전처리기를 보았을 것이다. 해당 전처리기는 이미 한번 정의된 함수라면 다시 정의하지 않도록 해주는 전처리기로 프로그램이 커질수록 해당 전처리기의 도움을 많이 받는다. 같은 방식으로 동작하는 것이 아래의 코드가 되겠다.

 

#ifndef  MY_ADD
#define  MY_ADD

int add(int a, int b)
{
	return a + b;
}

#endif // ! MY_ADD

 

다음과 같이 코드가 작성 되어 있다면 가장 앞에 #pragma once 전처리기를 둔 것과 같은 효과를 지닌다. #ifndef MY_ADD를 통해 만약 MY_ADD가 정의되어 있지 않다면 #endif 바로 위까지 코드를 읽어드리는 것이다. 이런 경우 #define MY_ADD가 최상단에서 정의가 되고 add 함수를 정의하기 때문에 추후 해당 헤더 파일을 다시 include하더라도 MY_ADD가 정의되어 있기 때문에 다시 add 함수를 정의하는 불상사는 발생하지 않는다.

+ Recent posts