Visual Studio 에서 C++ 로 사용하기 위한 protobuf 설치

구글 protobuf 를 새로 개발하고 있는 서버에 적용해보려고 찾아보았다. 설치에 대한 링크는 구글 Protocol Buffer VS 설치라는 글이 보였는데 2015년에 작성된 글이라 그런지 지금과 맞지 않았다. vsproject라는 폴더가 있다고 하는데 이런 폴더 자체가 없었으니.

그래서 새로 빌드해보기로 하고 그 과정을 정리해본다.

구글 protobuf 홈페이지로 이동한다.

https://github.com/google/protobuf/

C++에서 사용하기 위해 Protobuf Runtime Installation 항목으로 이동했더니 src 폴더로 가보라고 되어있다.

https://github.com/google/protobuf/tree/master/src 에 가서 밑의 README.md 파일 내용을 읽어보니 C++ Installation – Windows 항목이 있어 이 내용을 읽어본다.

If you only need the protoc binary, you can download it from the release page:

https://github.com/google/protobuf/releases

In the downloads section, download the zip file protoc-$VERSION-win32.zip. It contains the protoc binary as well as public proto files of protobuf library.

To build from source using Microsoft Visual C++, see cmake/README.md.

To build from source using Cygwin or MinGW, follow the Unix installation instructions, above.

안내문에서 지시하는대로 cmake/README.md 파일을 열어보았다.

https://github.com/google/protobuf/blob/master/cmake/README.md

환경설정을 진행하고 cmake를 다운로드한다. 구글에서 cmake 를 검색하여 Win64용의 cmake 프로그램을 설치.

이미 윈도우용 git이 설치되어 있으므로 VS2015용 개발자 명령 프롬프트창을 열고 라이브러리를 모아놓은 폴더에 가서 소스를 다운 받는다. 릴리즈 버전은 지금 최신버전인 3.5.0 으로 받았다. 개발자 명령 프롬프트가 아니라 cmd 창으로 열면 컴파일러인 cl.exe가 인식이 안되기 때문에 오류가 난다.

git clone -b v3.5.0 https://github.com/google/protobuf.git

폴더가 만들어지면 그 안으로 들어가서 gmock 도 받는다.

git clone -b release-1.7.0 https://github.com/google/googlemock.git gmock

그 안으로 들어가 googletest 코드도 받는다.

git clone -b release-1.7.0 https://github.com/google/googletest.git gtest

protobuf를 받은 위치에서 cmake 라는 폴더를 들어간다. 이 밑에 build 라는 디렉토리를 만들고 그 밑에 debug, release, solution 이라는 3개의 디렉토리를 만든다.

debug 폴더로 이동하여 다음 명령 실행

cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../../../../install ../..

release 폴더로 이동하여 다음 명령 실행

cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../../../../install ../..

solution 폴더로 이동하여 다음 명령 실행

cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_INSTALL_PREFIX=../../../../install ../..

설명서에는 Visual Studio 12 2013 으로 설명되어 있지만 난 VS2015를 사용하므로 그에 맞도록 수정했다.

빌드하기 위해 release 폴더혹은 debug 폴더에서 nmake 명령과 nmake check 명령을 차례대로 실행한다.

nmake check 이후 아무 문제 없다면 nmake install 을 진행하면 되나 나같은 경우에는 아래와 같은 에러메시지가 떴다.

[----------] Global test environment tear-down
[==========] 2009 tests from 194 test cases ran. (54957 ms total)
[  PASSED  ] 2001 tests.
[  FAILED  ] 8 tests, listed below:
[  FAILED  ] CommandLineInterfaceTest.Win32ErrorMessage
[  FAILED  ] BootstrapTest.GeneratedDescriptorMatches
[  FAILED  ] CsharpBootstrapTest.GeneratedCsharpDescriptorMatches
[  FAILED  ] RubyGeneratorTest.GeneratorTest
[  FAILED  ] TokenizerTest.ParseString
[  FAILED  ] TextFormatMapTest.Sorted
[  FAILED  ] TextFormatTest.Basic
[  FAILED  ] TextFormatExtensionsTest.Extensions

이 에러메시지를 해결하기 위해 구글에 검색해봤지만 딱히 좋은 대안은 찾지 못했다. 자연스러운 에러메시지(?)라는 글도 있긴했다.

빌드가 완료되면 lib 파일들과 헤더파일들이 생성되는데 이 생성되는 위치가 protobuf 폴더 바로 위의 install 이라는 폴더에 생성된다.

lib 파일을 비주얼스튜디오에 연결하고 헤더파일을 가져다 사용하면 된다. 이에 대한 내용은 Protobuf 실제 사용글을 참조하면 된다. 아니면 검색엔진에 검색해봐도 많이 나온다.

자세한 사용방법은 protobuf 홈페이지에서. https://developers.google.com/protocol-buffers/docs/cpptutorial

MySQL 라이브러리 사용시 config.h 파일 문제

리눅스에서 빌드시 다음과 같은 오류가 있었다.

In file included from /root/DServer2/src/dserver/protocol/../../dserver/database.h:6:0,
from /root/DServer2/src/dserver/protocol/../../dserver/define.h:41,
from /root/DServer2/src/dserver/protocol/base_protocol.h:3,
from /root/DServer2/src/dserver/protocol/base_protocol.cpp:1:
/root/Library/mysql-connector-c++-1.1.9/cppconn/resultset.h:30:20: fatal error: config.h: 그런 파일이나 디렉터리가 없습니다
#include "config.h"
^

config.h 파일이 없다는 에러 메시지인데 cppconn 디렉토리를 살펴보면 config.h.cm 파일이 있다.

mysql 커넥터를 다운 받은 디렉토리로 가서 cmake를 실행한다. 실행이 되지 않는다면 yum 으로 cmake를 설치한다.

[root@localhost Library]# cmake mysql-connector-c++-1.1.9
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Could NOT find Boost
-- Could NOT find Boost
CMake Error at CMakeLists.txt:194 (MESSAGE):
Boost or some of its libraries found. If not in standard place please set
-DBOOST_ROOT:STRING=


-- Configuring incomplete, errors occurred!
See also "/root/Library/CMakeFiles/CMakeOutput.log".
[root@localhost Library]#

https://dev.mysql.com/doc/connector-cpp/en/connector-cpp-installation-source-unix.html 를 참조했다.

https://dev.mysql.com/doc/connector-cpp/en/connector-cpp-source-configuration-options.html 를 읽어봤더니 BOOST 경로를 설정해줘야한다.

/etc/profile 에 BOOST_ROOT 경로를 지정한다.

export BOOST_ROOT=/부스트경로/

다시 cmake를 실행했다.

[root@localhost mysql-connector-c++-1.1.9]# cmake .
-- Boost version: 1.65.1
-- BOOST_INCLUDE_DIRS=/root/Library/boost_1_65_1
-- You will link dynamically to the MySQL client library (set with -DMYSQLCLIENT_STATIC_LINKING=<bool>)
-- Searching for dynamic libraries with the base name(s) "mysqlclient_r mysqlclient"
CMake Error at FindMySQL.cmake:531 (message):
Could not find "mysql.h" from searching "/usr/include/mysql
/usr/local/include/mysql /opt/mysql/mysql/include
/opt/mysql/mysql/include/mysql /usr/local/mysql/include
/usr/local/mysql/include/mysql /MySQL/*/include /MySQL/*/include"
Call Stack (most recent call first):
CMakeLists.txt:252 (INCLUDE)


-- Configuring incomplete, errors occurred!
See also "/root/Library/mysql-connector-c++-1.1.9/CMakeFiles/CMakeOutput.log".
[root@localhost mysql-connector-c++-1.1.9]#

mysql 라이브러리 파일이 있어야한다고 나온다. CentOS 7에서는 MySQL이 빠지고 그대신 MariaDB가 들어가 있기 때문에 mariadb-libs 와 mariadb-devel 을 yum으로 설치해준다.

다시 cmake 하니…

[root@localhost mysql-connector-c++-1.1.9]# cmake .
-- Boost version: 1.65.1
-- BOOST_INCLUDE_DIRS=/root/Library/boost_1_65_1
-- You will link dynamically to the MySQL client library (set with -DMYSQLCLIENT_STATIC_LINKING=<bool>)
-- Searching for dynamic libraries with the base name(s) "mysqlclient_r mysqlclient"
-- mysql_config was found /usr/bin/mysql_config
-- MySQL client environment/cmake variables set that the user can override
--   MYSQL_DIR                   :
--   MYSQL_INCLUDE_DIR           : /usr/include/mysql
--   MYSQL_LIB_DIR               : /usr/lib64/mysql
--   MYSQL_CONFIG_EXECUTABLE     : /usr/bin/mysql_config
--   MYSQL_CXX_LINKAGE           :
--   MYSQL_CFLAGS                : -I/usr/include/mysql
--   MYSQL_CXXFLAGS              : -I/usr/include/mysql

(...중략...)

-- Configuring performance test - statement
-- Configuring bugs test cases - unsorted
-- Configuring unit tests - group template_bug
-- Configuring done
CMake Warning (dev) in driver/CMakeLists.txt:
  Policy CMP0022 is not set: INTERFACE_LINK_LIBRARIES defines the link
  interface.  Run "cmake --help-policy CMP0022" for policy details.  Use the
  cmake_policy command to set the policy and suppress this warning.

  Target "mysqlcppconn" has an INTERFACE_LINK_LIBRARIES property which
  differs from its LINK_INTERFACE_LIBRARIES properties.

  INTERFACE_LINK_LIBRARIES:

    mysqlclient;pthread;z;m;ssl;crypto;dl

  LINK_INTERFACE_LIBRARIES:



This warning is for project developers.  Use -Wno-dev to suppress it.

-- Generating done
-- Build files have been written to: /root/Library/mysql-connector-c++-1.1.9
[root@localhost mysql-connector-c++-1.1.9]#

이제서야 완료. config.h 파일이 정상적으로 생성되었다.

InitializeCriticalSectionAndSpinCount, CriticalSection에 대한 정보 이것저것

개인적으로 만들고 있는 프로젝트, 프로그램들은 대부분 리눅스/윈도우간의 크로스플랫폼을 지향하고 있는데 그래서 그런지 락을 쓸 때는 std::mutex를 자주 쓰고 있다.

최근에는 윈도우의 critical section이 더 빠르다는 얘기가 자꾸 들려서 윈도우일 때는 critical section을 더 쓰도록 변경하는 중.

그러다가 InitializeCriticalSection 이라는 초기화함수 대신 InitializeCriticalSectionAndSpinCount 라는 함수를 보게 되었는데 이게 무엇인지 찾아보았다. 이 함수의 인자는 2개인데 첫번쨰는 당연히 크리티컬섹션 객체의 포인터이고 뒤에 붙는 인자는 스핀 횟수이다.

어떤 스레드가 작업을 하다가 크리티컬섹션에 걸려서 InitializeCriticalSection 을 썼다면 대기상태에 들어가게 된다. InitializeCriticalSectionAndSpinCount 를 썼다면 바로 대기상태에 들어가지 않고 인자로 줬던 SpinCount 횟수만큼 기다리게 된다. 만약 그 사이에 크리티컬섹션이 풀리면 바로 다시 작업 진행. SpinCount 횟수만큼 기다렸는데도 크리티컬섹션이 풀리지 않았다면 대기상태로 들어간다.

InitializeCriticalSectionAndSpinCount는 멀티프로세서 시스템에서만 유효하며 싱글프로세서 시스템에서는 뒤에  SpinCount 값은 무시되고 0으로 설정된다. 멀티프로세서 시스템인 경우, 크리티컬섹션에 걸려 대기에 들어가지 않으므로써 스레드간 Context Switching이 적어지므로 결과적으로 InitializeCriticalSection 을 사용할 때보다 성능이 더 나아질 수 있다.

또한 InitializeCriticalSection 의 리턴은 void 이기 때문에 크리티컬섹션 초기화가 제대로 되었는지 알 수가 없다. 메모리 부족 상황 등에서 초기화 실패가 일어날 수 있고 이 경우 InitializeCriticalSection 함수 부분에서 exception이 일어나게 되어 추적하기 힘든 버그가 발생할 수 있다. InitializeCriticalSectionAndSpinCount 함수는 리턴값이 있고 이러한 버그 상황을 피할 수 있도록 만들어준다.

예제 코드

CRITICAL_SECTION g_cs;
int main()
{
    if (!InitializeCriticalSectionAndSpinCount(&g_cs, 4000))
    {
        // 예외 처리
    }
    ...
}
// [출처] [VC++] 크리티컬 섹션 초기화 관련 간단 팁|작성자 데브머신

이와 관련하여 스핀카운트의 단위가 무엇인지, 스레드 컨텍스트 스위칭이 이루어지지 않고 크리티컬섹션이 풀릴 때까지 어디서 어떻게 기다리는지 더 알아보고 싶지만 구글을 뒤져봐도 자료가 없는듯하다.

<참고자료>

메모리복사시 침범 문제

boost asio를 이용하여 코드를 작성 중에 다음과 같은 부분이 있었다.

클래스의 멤버변수로 네트워크 처리에 사용할 버퍼를 만들고 변수를 생성한 코드다.

class BasicSocket : public std::enable_shared_from_this<BasicSocket>
{
// 중략
protected:
    std::shared_ptr<Socket> socket_ptr_;

    Socket socket_;

    char packet_buffer_[RECV_BUFFER_SIZE * 2];
    int32_t remain_size_;

    char recv_buffer_[RECV_BUFFER_SIZE];
    char send_buffer_[SEND_BUFFER_SIZE];
};

이후 이 변수들을 사용해서 다음과 같은 코드를 작성했다.

std::cout << "OnReceive 1 remain_size_:" << remain_size_ << " - bytes_transferred:" << bytes_transferred << std::endl;

// 패킷을 담아둘 버퍼에 수신버퍼의 내용을 복사한다.
memcpy(&packet_buffer_[remain_size_], recv_buffer_, bytes_transferred);

std::cout << "OnReceive 2 remain_size_:" << remain_size_ << " - bytes_transferred:" << bytes_transferred << std::endl;

데이터 수신 후 처리하는 부분인데 단순히 packet_buffer에 memcpy를 하기만했는데 remain_size의 값이 변화한다는 사실.

위에서 출력된 remain_size와 밑에서 출력된 remain_size의 값이 다르다는 것이었다. 더 문제는 이 출력이 어떤 경우에는 정상적이고 어떤 경우에는 다르게 출력되었다. remain 값을 변화시키는게 없는데 왜 그럴까 한참 살펴보다가 VS2015의 메모리 디버거를 보다가 허탈하게 이유를 알아냈다.

remain_size가 할당된 위치가 packet_buffer의 바로 다음 메모리 공간이었다. 메모리 복사시, recv_buffer와 packet_buffer 크기가 같고 remain_size가 0보다 크다면, packet_buffer 바로 다음 메모리 공간(=remain_size가 할당된 위치)까지 메모리 복사가 일어나서 오염되었던 것.

그런데 이와 동일한 코드를 쓴 다른 서버는 같은 코드임에도 에러가 일어나지 않았다. 같은 코드더라도 프로그램마다 메모리 공간 할당이 다르게 이루어진다는 것으로 이해해야할까.

boost::property_tree의 위험성

.ini 파일을 파싱하기 위해 boost::property_tree 클래스를 사용했었다.

서버의 성능을 테스트하던 도중 어디사 CPU를 많이 잡아먹나 계속 테스트했는데 네트워크 전송 부분이 계속 문제였다. 아무리 수정을 해도 고쳐지지 않는 상황. 비주얼스튜디오의 성능프로파일러를 돌려보니 boost::property_tree 가 문제의 원인이었다. 환경설정에서 특정한 값을 가져오기 위해 쓰느 코드가 네트워크 속도를 느리게 만드는데 기여하고 있었다.

이 부분을 수정하고나니 CPU 점유율이 아주 낮아졌다.

boost를 마구 쓰면 안된다는걸 깨닫게되었네.

boost에서 메모리풀 사용

프로그램 구현 중 메모리풀이 필요한 경우가 있어 어떻게 할까 하다가 boost의 메모리풀을 찾아보게 되었다.

boost의 메모리풀은 4가지.

  1. pool
  2. singleton_pool
  3. pool_alloc
  4. object_pool

네가지 메모리풀 중에 어떤 것이 어떤 상황에 가장 적합한지는 많이 써봐야 알 수 있을듯하다. 찾아보니 내가 가지고 있던 몇몇 게임서버 소스는 singleton_pool을 사용하고 있는 케이스가 있었다. 이것부터 먼저 찾아보는 것으로.

singleton_pool은 다른 메모리풀 클래스들과는 다르게 스레드-세이프 특성을 지니고 있어서 가장 유용하게 쓰일 것 같다.

singleton_pool을 아래와 같은 코드로 좀더 편하게 사용 가능. 출처는 ‘아지크의 좌충우돌 IT 이야기’

#include <stdio.h>
#include <iostream>
#include <vector>
#include <boost/pool/singleton_pool.hpp>
#include <boost/pool/pool_alloc.hpp>


template<typename T,  unsigned int NEXT_SIZE = 32U, unsigned int  MAX_POOL_SIZE = 0U>
class CMemoryPoolT
{
public:
     static void* operator new(size_t size)
     {
          return boost::singleton_pool<T,
               sizeof(T),
               boost::default_user_allocator_new_delete,
               boost::details::pool::default_mutex,
               NEXT_SIZE,
               MAX_POOL_SIZE>::malloc();
     }

     static void operator delete(void* p)
     {
          boost::singleton_pool<T,
               sizeof(T),
               boost::default_user_allocator_new_delete,
               boost::details::pool::default_mutex,
               NEXT_SIZE,
               MAX_POOL_SIZE>::free(p);
     }
};


class CMemoryPoolTest  :public CMemoryPoolT<CMemoryPoolTest>
{
public:
     char Dumy[124] ;
};


int _tmain(int argc, _TCHAR* argv[])
{
     using std::vector ;

     CMemoryPoolTest *p = new CMemoryPoolTest() ;

     delete p ;

     printf("Using MemoryPool... \n") ;


     return 0;
}

프로그램 종료시에는 purge_memory()를 꼭 호출해주어야 한다고 한다.

혹은 다음과 같은 방법으로도 사용 가능하다.

struct SEND_BUFFER_TAG {};
typedef boost::singleton_pool<SEND_BUFFER_TAG, SEND_BUFFER_SIZE> SendBufferPool;

사용할 클래스의 헤더파일에서 위와 같이 선언. TAG를 여러개 사용하고 메모리풀마다 다르게 적용하면 서로 다른 메모리풀이 된다.

메모리를 할당 받고 싶다면,

char* send_data = static_cast<char*>(SendBufferPool::malloc()); // 메모리를 할당 받는다.

SendBufferPool::free(send_data); // 다 사용한 메모리를 해제한다.

종료시에는 당연히,

SendBufferPool::purge_memory();

를 호출해야한다.

출처와 참고사이트

 

Google Breakpad 설치 (1)

Google Breakpad를 사용하기 위한 방법에 대해 고생했던 것을 정리한다.

윈도우와 리눅스에서 동시에 사용할 수 있는 서버프로그램을 개발하고 있는데 윈도우에서야 미니덤프를 이용하여 덤프를 남기면 되지만 리눅스에서는 coredump라는 생소한 시스템을 이용해야해서 아예 크로스플랫폼 덤프 시스템을 찾다가 구글브레이크패드를 이용해봐야겠다는 생각에 시작했다.

https://chromium.googlesource.com/breakpad/breakpad 로 이동한다. 많은 블로그에서 http://google-breakpad.googlecode.com/svn/trunk 에서 체크아웃 받으라고 나와있지만 이것은 옛날 정보이다. 현재를 기준으로 사이트는 이동되었으며 SVN이 아니라 GIT을 이용해야 소스를 받을 수 있다.

GIT을 이용하면 되긴하나 귀찮으므로 master 브랜치를 선택하고 tgz로 압축된 파일을 받는다. 7zip을 이용하여 압축파일의 압축을 해제한다. 윈도우에서는 tgz 파일을 풀어서 tar 파일을 만들고 다시 또 한번 아카이빙을 풀어야한다. 물론 7zip 하나로 다 가능하다.

프로그램 설명서를 보기 위해 doc 폴더로 이동을 하면…. 아오… 마크다운 형식의 .md 파일만 잔뜩 들어있다. 브라우저로 볼 수가 없으므로 귀찮아서 그냥 구글 브레이크패드 홈페이지의 도큐먼트를 읽어보면 된다.

….근데 모르겠다.

대충 다른 블로그를 찾아보니 gyp 라는 시스템으로 비주얼스튜디오 솔루션 파일을 생성하면 되고…. gyp는 오픈소스이고… 구글 브레이크패드 소스를 받으면 포함되어 있단다. 근데 내가 보기에는 아무리 찾아도 gyp 라는 시스템이 포함되어 있진 않은 것 같다.

구글에 찾아보니 https://chromium.googlesource.com/external/gyp 에 가면 받을 수 있덴다.

가보니 또 git으로 받으라고 한다. 그냥 tgz 파일을 받고 압축을 푼다. 이 프로그램은 홈페이지에 도큐먼트도 없고 도움말도 없다. 뭐 어쩌라는건지…?

한참 애먹은 끝에… 일단 파이선을 설치하고(일단 최신버전인 3.5.2로 설치했다.), cmd 를 관리자권한으로 열고, python setup.py install 을 실행하면 된다는걸 알아냈다. 아오 짜증… 여튼 명령어를 입력하면 뭔가 텍스트가 촤르르륵 올라간다.

자 이제 다시 구글 브레이크패드를 다운 받은 폴더로 이동해서… src/build 에 있는 all.gyp를 실행하기 위해 gyp all.gyp 를 입력한다.

…..는 실패. 문법 오류가 있다고 한다.

그럼 다시 src/client/windows 의 breakpad_client.gyp 를 실행해본다.

…는 실패. 문법 오류가 있다고 한다.

파이선버전 문제인가 싶어서 3.5.2 버전을 다 지우고 2.7.12 버전으로 다시 설치하고 아까 all.gyp 명령을 다시 실행해본다.

안된다. 파이선을 재설치하는 과정에서 gyp가 다 삭제되었다. 다시 gyp를 설치한다.

다시 해봤는데 똑같다… 아오 힘들어.

http://yardbirds.tistory.com/107 를 보니

src\client\windows 폴더에서 ..\..\tools\gyp\gyp.bat breakpad_client.gyp 를 실행하면 솔루션 파일과 프로젝트 파일이 생성되는 것을 볼 수 있다.

라고 한다. 해봤다.

D:\Library\google-breakpad\src\client\windows>d:\Library\gyp-master\gyp.bat breakpad_client.gyp
gyp: Cycles in .gyp file dependency graph detected:
Cycle: breakpad_client.gyp -> sender\crash_report_sender.gyp -> breakpad_client.gyp
Cycle: unittests\client_tests.gyp -> breakpad_client.gyp -> unittests\client_tests.gyp
Cycle: unittests\client_tests.gyp -> crash_generation\crash_generation.gyp -> breakpad_client.gyp -> unittests\client_tests.gyp
Cycle: breakpad_client.gyp -> crash_generation\crash_generation.gyp -> breakpad_client.gyp
Cycle: unittests\client_tests.gyp -> handler\exception_handler.gyp -> crash_generation\crash_generation.gyp -> breakpad_client.gyp -> unittests\client_tests.gyp
Cycle: breakpad_client.gyp -> handler\exception_handler.gyp -> crash_generation\crash_generation.gyp -> breakpad_client.gyp
Cycle: breakpad_client.gyp -> tests\crash_generation_app\crash_generation_app.gyp -> handler\exception_handler.gyp -> crash_generation\crash_generation.gyp -> breakpad_client.gyp

갑자기 무슨 사이클을 돈다는 개소리를 하면서 안된다. 구글에서 Cycles in .gyp file dependency graph detected 라는 문장으로다시 검색.

http://stackoverflow.com/questions/2925094/how-to-build-google-breakpad 를 보니 –no-circular-check 옵션을 붙이면 된단다. 옵션을 붙였더니 그제서야 비주얼스튜디오 솔루션 파일이 생성되었다.

아이고 힘들다…

GCC 최적화 옵션

GCC로 컴파일하던 중 spdlog 라이브러리에서 컴파일시 -O3 옵션을 사용하는 것을 보고 궁금증이 생겨 찾아보았다.

GCC 최적화 옵션에 대해 자세하게 설명되어 있는 페이지.

http://jinynet9.tistory.com/113

* 커널 컴파일 시 최적화 옵션 -O2만 사용하는 이유
커널은 인라인 함수를 많이 사용하고 있다. -O3 최적화는 컴파일러가 판단해서 인라인을 인라인이 빠른 것은 인라인으로, 함수가 빠른 것은 함수로 바꿔버린다. 커널은 최적화된 수행 속도를 위해 의도적으로 인라인 함수를 사용하고 있어서 컴파일러에 의해서 자의적으로 함수로 바뀌는 것을 막기 위해 -O2 옵션을 사용한다.

커널 컴파일은 아니지만 최적화 옵션을 -O2 로 변경했다. 컴파일 옵션을 변경하고 -O3일 때와 파일 크기를 비교해보았는데 거의 차이가 나지 않았다. 앞으로도 -O2로 사용하면 될듯하다.

dependent-name is parsed as a non-type, but instantiation yields a type 에러 해결 방법

다음과 같은 코드를 작성했다.

template<typename T1, typename T2>
class ObjectMap
{
public:
    typedef boost::mutex                    Mutex;
    typedef boost::lock_guard<Mutex>      LockGuard;
    typedef std::map<T1, T2>              Map;

    bool InsertObject(T1& key, T2& object)
    {
        LockGuard lock(object_map_mutex_);
        auto result = object_map_.insert(Map::value_type(key, object));
        return result.second;
    }

대략 템플릿으로 std::map을 래핑해서 사용하려는 클래스인데 이 코드는 Visual Studio 2015 에서 아무 문제 없이 컴파일되며 실제로 작동상에도 아무 문제가 없는 코드이다.

하지만 이 코드를 gcc에서 컴파일 하려고 하면 다음과 같은 오류가 발생하며 컴파일 자체가 되질 않는다. 참 신기한 노릇… (컴파일러는 CentOS 7의 gcc 4.8.2 버전이다.)

/container/object_map.h:28:63: error: dependent-name ‘ObjectMap<T1, T2>::Map:: value_type’ is parsed as a non-type, but instantiation yields a type
   auto result = object_map_.insert(Map::value_type(key, object));
                                                               ^
/container/object_map.h:28:63: note: say ‘typename ObjectMap<T1, T2>::Map:: value_type’ if a type is meant

이 문제에 대해 한참을 찾아본 결과 다음의 주소에서 힌트를 얻을 수 있었다.

http://stackoverflow.com/questions/6021686/c-iterator-to-stdmap

원인은 해당하는 부분에 컴파일러가 타입을 알 수 없다는 얘기.

코드를 다음과 같이 바꾸어주었더니 gcc에서도 컴파일이 잘 된다.

auto result = object_map_.insert(typename Map::value_type(key, object));

예전에도 느꼈지만 리눅스에서 빌드하는거 참 귀찮고 어렵다는거 다시 한번 느낀다.

std::thread 사용법

간단히 스레드를 만들어 테스트 해야할 일이 있어 std::thread를 찾아보고 만들어봤다.

예전에는 boost::thread를 이용했었는데 C++11에 오면서 아예 다 내장되었다는게 놀랍고 신기하다.

#include <iostream>
#include <thread>
#include <windows.h>

char* target_msg = "test";
char msg[1024] = {0,};

void callback_function(int thread_num)
{
    while(true)
    {
        strcpy_s(msg, target_msg);
        //std::cout << "copy (" << thread_num << ")" << std::endl;
        Sleep(10);
    }
}

int main(void)
{
    std::thread thread1(callback_function, 1);
    std::thread thread2(callback_function, 2);

    thread1.join();
    thread2.join();

    return 0;
}