최근에 긴 프로젝트를 마치고 결과 보고서를 쓰면서 시간 측정을 많이 했다. visual studio로 시간 측정을 하면 C 기반의 시간 측정 함수를 만들어서 쓰곤 했는데, C++11부터 생긴 chrono 시간 라이브러리를 알게 되었고 이를 사용하여 시간 측정을 하였다. 입무 시간에는 빠르게 만들어서 결과만 뽑으려고 코드 정리를 안 했는데, 이번 기회에 시간 측정 클래스로 구현해서 자주 끌어다 쓸 예정이다!


chrono 라이브러리

 

크로노라고 부르는 이 시간 라이브러리는 크로노스라는 시간의 신에서 유래되었다. C++11이라 나름 세련된? 이름을 가진 라이브러리다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>

using namespace std;

// 시간 측정 클래스 시작
class Timer
{
	using clock_t = std::chrono::high_resolution_clock;
	using second_t = std::chrono::duration<double, std::ratio<1>>;

	std::chrono::time_point<clock_t> start_time = clock_t::now();

public:
	void elapsed()
	{
		std::chrono::time_point<clock_t> end_time = clock_t::now();

		cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
	}
}; // 시간 측정 클래스 끝

int main()
{
	random_device rnd_device;
	mt19937 mersenne_engine{ rnd_device() };

	vector<int> vec(10);
	for (unsigned int i = 0; i < vec.size(); ++i)
		vec[i] = i;

	std::shuffle(begin(vec), end(vec), mersenne_engine);

	for (auto& e : vec) cout << e << " ";
	cout << endl;

	Timer timer; // 시간 측정 시작

	std::sort(begin(vec), end(vec));

	timer.elapsed(); // 시간 측정 끝

	for (auto& e : vec) cout << e << " ";
	cout << endl;

	return 0;
}

간단한 정렬 예제를 가지고 왔다. 일반적인 작동 방식은 시간을 측정하고 싶은 곳에 인스턴스를 생성하여 시작 시간을 저장하고 끝나는 부분에서 elapsed 멤버 함수를 불러서 끝 시간을 저장하여 총 걸린 시간을 출력한다.

 

using clock_t = std::chrono::high_resolution_clock;

클래스 가장 첫줄에 위와 같은 명령문을 볼 수 있는데, 최대한 정밀도를 높여서 시간을 측정하기 위함이다. using 사용에 대해 모르겠다면 이전 포스팅 링크를 참고하자!

 

또한 시간 측정은 Release와 Debug에서 차이가 나는데, 프로그램을 개발하면 Release 모드로 배포하니 Release 모드로 측정해서 내 프로그램의 시간을 측정하자! Debug 모드가 시간이 더 오래 걸린다!

현재 Debug 모드이다.


Reference

 

 

<chrono> - C++ Reference

 

www.cplusplus.com

 

 

Chrono in C++ - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

  • 4.3.1 함수에 대한 외부 연결(External Linkage)
  • 4.3.2 변수에 대한 외부 연결
  • 4.3.3 전역 상수에 대한 외부 연결

4.3.1 함수에 대한 외부 연결(External Linkage)

 

일반적으로 다른 cpp 파일에 있는 것을 끌어다가 쓰려면 #include 전처리기를 통해 해당 cpp 파일을 끌어다가 쓸 수 있다. 하지만 cpp 파일을 직접 include하는 것은 권장하지 않는다고 한다. 마치 어릴 때 먹는 불량식품 느낌으로 먹어도 되는데 안 먹었으면 한다~ 정도의 느낌으로 받아들이면 되겠다.

 

cpp 파일을 include할 수 있지만 클래스를 구성하게 되면 헤더에 선언을 전부 올려놓고 cpp에 정의를 하기 때문에 그냥 헤더 파일을 include하면 될 것이다. 정말 특수한 경우라서 cpp 파일 내부의 함수가 필요하다면 전방 선언을 통해 해당 함수를 끌어다가 쓸 수 있겠다.

// Extern c++ file
#include <iostream>

void doSomething()
{
	using namespace std;

	cout << "Hello" << endl;
}
// main 파일

// extern이 생략되어 있다.
void doSomething();

int main()
{
	doSomething();

	return 0;
}

전방 선언을 해서 외부 cpp 파일의 함수를 끌어다가 쓰는 예시가 위의 코드로 볼 수 있다. 전방 선언을 통해 main 함수에서 외부 파일의 함수가 호출되면 연결되어 사용할 수 있게 된다. 전방 선언 앞에는 extern 키워드가 숨어 있으며, 이 키워드를 통해 외부 파일의 함수를 끌어 쓸 수 있다.


4.3.2 변수에 대한 외부 연결

 

extern 키워드를 활용하면 외부에 있는 변수도 끌어 쓸 수 있다. 위와 비슷하게 한 번 설정하고 사용해보자!

// Extern c++ file

extern int exA;
// main file
#include <iostream>

using namespace std;


extern int exA;

int main()
{
	cout << exA << endl;

	return 0;
}

위의 코드를 실행하면 다음과 같은 에러가 뜬다.

LNK2001 and LNK1120

링커 에러라고 불리는 LNK2001과 LNK1120이다.

error LNK2001: unresolved external symbol "int exA"
fatal error LNK1120: 1 unresolved externals

해당 에러가 발생한 이유는 선언만 되어 있고 exA가 메모리에 할당이 안되어 있기 때문이다. 사용하기 위해서 외부 cpp 파일에 해당 변수를 초기화하고 사용하자! 아래처럼 수정하고 사용하면 된다.

// Extern c++ file

extern int exA = 123;

 

만약 양쪽 파일에 전부 exA를 초기화시켜주면 어떻게 될까? 이번에도 두개의 에러 메시지를 만날 수 있다. 바로 LNK2005와 LNK1169이다.

error LNK2005: "int exA" (?exA@@3HA) already defined in Source.obj
fatal error LNK1169: one or more multiply defined symbols found

위 에러를 해결하기 위해서는 중복된 초기화를 삭제해주면 되겠다!


4.3.3 전역 상수에 대한 외부 연결

 

전역 상수에 대한 외부 연결을 다뤄보자. 우선은 하나의 헤더 파일에 프로그램에서 쓸 상수를 모아둔다.

// My_const.h
#pragma once

namespace Constants
{
	const double pi(3.141592);
	const double gravity(9.8);
}

여기서 만들어진 상수를 test.cpp에서 사용하는 함수로 구현하였다. 상수에 대한 주소도 같이 보기 위하여 주소도 출력한다.

// test.cpp
#include <iostream>
#include "My_const.h"

void doSomething()
{
	using namespace std;

	cout << "Hello" << endl;

	cout << "In 4_2_1.cpp file" << Constants::pi << " " << &Constants::pi << endl;
}

이제 위의 상수가 담긴 헤더 파일과 test.cpp 파일을 include하여 사용하는 파일을 만들고 이 파일에서도 상수를 끌어와서 사용도 하고 test.cpp의 함수를 사용해서 써보기도 한다.

// main.cpp
#include <iostream>
#include "My_const.h"

using namespace std;

void doSomething();

int main()
{
	doSomething();

	// pi가 중복되게 존재한다. 메모리 낭비
	cout << "In main.cpp file" << Constants::pi << " " << &Constants::pi << endl;
    
	return 0;
}

총 3개의 파일을 만들고서 주소를 출력하면 pi의 주소가 다름을 확인할 수 있다. 이게 의미하는 것은 만약 해당 상수를 수십만번 사용할 경우 수십만번의 메모리 할당이 이루어지고 낭비가 생긴다는 것이다.

 

상수를 초기화해준 파일을 헤더와 cpp 파일로 분할하고 상수 앞에 extern 키워드를 붙이면 메모리 할당을 계속하지 않고 단 한번 수행하며, 해당 메모리에 접근하여 상수를 가져다가 쓰게 된다.

namespace Constants
{
	extern const double pi(3.141592);
	extern const double gravity(9.8);
}
  • 4.2.1 전역 변수(Global Variables)
  • 4.2.2 정적 변수(Static Variables)

4.2.1 전역 변수(Global Variables)

 

전역 변수란 함수 내에서 정의되지 않고 어디에도 종속되지 않은 변수를 뜻한다. 코드상에서 어느 중괄호 안에도 안 들어가면 전역 변수가 된다. 초기 할당이 진행되었다면 프로그램이 끝날 때까지 존재하며 extern 키워드를 사용하면 모든 파일에서 접근이 가능하다.

#include <iostream>

using namespace std;

// global variable
int value = 123;

int main()
{

	// 전역 변수가 출력
	cout << value << endl;

	// name hiding
	int value = 1;

	// 영역 연산자(전역 변수를 출력시킨다)
	cout << ::value << endl;

	// 지역 변수가 출력
	cout << value << endl;

	return 0;
}

전역 변수 value가 할당되고나면 접근이 가능해지는데, 지역 변수가 동일한 이름으로 작성되면 name hiding으로 인해 지역 변수를 먼저 접근하게 된다. 하지만 영역 연산자 ::를 사용하게 되면 지역 변수가 아닌 전역 변수로 접근해서 데이터를 사용할 수 있다.


4.2.2 정적 변수(Static Variables)

 

정적 변수도 할당이 되면 프로그램이 끝날 때까지 존재하기 때문에 전역 변수와 비슷하다고 생각될 수 있지만 조금 특성이 다르다. 정적 변수는 최초 초기화는 반드시 소스 파일에서 해야 하는 특징이 있다. 또한 정적 변수의 경우 선언된 파일 안에서만 사용이 가능하고 선언된 파일 이외에서는 사용이 불가능하다.

#include <iostream>

using namespace std;

// 해당 cpp 안에서만 전역으로 사용하는 변수 다른 곳에서는 접근 불가능
static int static_global_variable = 1;

void doSomething()
{
	// 정적 변수(메모리가 static이다, 메모리가 정적으로 선언된다)
	static int a = 1;
	++a;
	cout << a << endl;
}

int main()
{
	cout << "정적 변수 테스트" << endl;
    
	// 정적 변수 테스트
	doSomething();
	doSomething();
	doSomething();
	doSomething();

	return 0;
}

정적 변수는 특정 함수가 몇번 호출되는지 디버깅할 때 쓰면 유용하다. 전역 변수 같지만 선언된 함수 블럭 내에서만 같은 메모리를 사용하며, 함수 내부에 선언되어 있다면 외부에서는 접근할 수 없다. 물론 전역 변수의 위치에서 선언하면 전역 변수처럼 접근하겠지만 이마저도 초기화된 파일에서만 가능하지 외부 파일에서의 접근은 불가능하다. 이러한 특징 때문에 내부 연결(Internal Linkage)라고 한다.

+ Recent posts