기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

비주얼 스튜디오 2015 활용 가이드 : 최신 비주얼 C++로의 포팅 가이드

기술자료
DBMS별 분류
Etc
작성자
dataonair
작성일
2016-01-18 00:00
조회
4406



비주얼 스튜디오 2015 활용 가이드

최신 비주얼 C++로의 포팅 가이드



매년 새로이 릴리즈되는 비주얼 스튜디오를 출시 즉시 현업 프로젝트에 도입하기에는 예치기 못한 오류와 문제들이 두려워 망설여지는 게 사실이다. 그럼에도 최신 개발툴이 가져다주는 개발의 효율성 증대와 프로젝트 결과물의 더 나은 성능 등의 이점을 포기하기에는 아쉬운 것이 사실이다. 이런 고민을 하는 사람에게 이 글을 권한다. 현재 가장 최신 버전인 비주얼 스튜디오 2015에 포함된 비주얼 C++로 구 프로젝트를 포팅하는 데 유의해야 할 것들을 살펴본 가이드이기 때문이다.



마이크로소프트(이하 MS)는 지속적으로 더 향상된 개발 환경과 더 개선된 성능의 컴파일러를 선보이고 있다. 그러나 현업의 프로젝트에 쓰는 비주얼 스튜디오는 구 버전인게 대부분이다. 업데이트 및 지원이 중단된 윈도우98을 쓰는 클라이언트 탓에 비주얼 스튜디오 2008을 계속 쓰거나, 다양한 프로젝트에 쓰는 핵심 라이브러리로 인해 비주얼 C++ 6를 고집하는 경우가 여기에 해당한다.

최신 비주얼 스튜디오가 주는 이점은 명확하다. 더욱 향상된 컴파일러가 포함돼 있어 더욱 최적화되고 보안도 우수한 결과물을 얻을 수 있다. 업그레이드만으로 컴파일 속도가 떠 빨라지는 만큼 작업 효율성까지 높아진다. 모던 C++로 대표되는 최신 C++ 언어의 키워드, 라이브러리 함수도 빼놓을 수 없는 강점들이다. 그렇기에 최신 비주얼 스튜디오로 현재 진행 중인 프로젝트를 포팅하는 것은 개발자라면 바라는 일. 특히 일반적인 프로젝트라면 단순히 새로운 비주얼 스튜디오를 설치하고, 기존 솔루션을 더블 클릭하면 나타나는 포팅 마법사의 안내를 따라가는 것만으로 포팅이 완료되기 때문에 더 그렇다.



tech_img4254.png

마이그레이션 시 문제가 되는 케이스들

테스트 프로젝트나 간단한 예제들이라면 기존 솔루션 파일을 최신 비주얼 스튜디오에서 읽어들이는 것만으로 자동으로 마이그레이션이 된다. 반면 프로젝트의 구조가 복잡할수록, 프로젝트에 공용 코어 라이브러리를 쓴 경우에는 마이그레이션 시 예기치 못한 문제가 발생할 소지가 있다. 이처럼 솔루션이나 프로젝트가 자동으로 마이그레이션되지 않는 데에는 몇 가지 이유가 있다. 먼저 솔루션이나 프로젝트의 컴파일 속성, 혹은 링커, 메니페스트 등의 프로젝트 속성을 변경한 경우 자동으로 마이그레이션되지 않을 수 있다. 컴파일 규칙을 변경하는 것도 새로운 컴파일러에서 매끄럽게 컴파일되지 않는 원인 중 하나다. 대규모 프로젝트일수록 사용되는 API 개수가 많을 수밖에 없다. 마이그레이션 시 API의 이름이 바뀌거나 없어진 경우도 더러 있는 만큼 이들 또한 컴파일 에러의 원인이 된다. 새로운 컴파일 옵션이나 컴파일 옵션의 기본 값이 변경되는 경우도 더러 있다.

tech_img4255.png

이러한 다양한 문제의 해결 방법 중 가장 대표적인 것이 컴파일 옵션에 /NODEFAULTLIB를 명시하는 것이다. 프로젝트를 진행하다보면 정적 또는 동적 라이브러리를 혼합해 쓰는 경우가 많다. 자체적으로 구현한 라이브러리, 오픈소스 소스 코드를 그대로 가져와 사용하거나 오픈소스 빌드 시간 단축을 위해 라이브러리와 헤더 파일만 프로젝트에 적용하는 경우 등이 그러하다. 이 경우 라이브리러간 사용하는 기본 라이브러리가 서로 맞지 않아 컴파일 시 에러가 발생할 수 있다. 이럴 때에는 /NODEFAULTLIB 옵션으로 간편하게 문제를 해결할 수 있다. 한 가지 주의할 것은 /NODEFAULTLIB 옵션을 부여하면, 컴파일되는 프로젝트가 마이그레이션되는 과정에서 링커 오류가 발생할 수 있다는 점이다. 마이그레이션되는 새로운 라이브러리를 사용하지 않기 때문이다.



성공적인 마이그레이션 후

자동화 툴을 통해 기존 프로젝트를 새로운 비주얼 스튜디오에 성공적으로 마이그레이션하고 컴파일했다면, 마이그레이션 로그 파일을 확인해야 한다. 여기서 문제가 될 소지가 있는 부분을 반드시 체크할 필요가 있다. 또 새로운 컴파일러를 통해 빌드 과정에서 생긴 컴파일 로그 중 경고로 표시된 코드는 정리하자. 새로운 경고들은 차기 개발 툴에 대한 포팅 가능성을 높이고 좀 더 표준에 준수하는 코드를 추천해 결과물에 대한 보안성을 향상시켜주기 때문이다.

비주얼 스튜디오 2015의 비주얼 C++ 컴파일러에는 새로운 보안 관련 컴파일 옵션인 흐름 제어 보호가 추가됐다. 이를 사용하기 위해서는 기본값으로 켜져있는 /guard:cf 컴파일 옵션 등을 활용하는 게 좋다. 마지막으로 새로운 비주얼 C++에 도입된 향상된 언어 기능이나 프로그램 성능, 보안성 향상 등의 이점을 누리기 위해서는 새로운 표준 라이브러리와 추천 코드를 사용하도록 코드를 고쳐야 한다.



tech_img4259.png

빌드 환경 구축

비주얼 C++의 빌드 환경은 비주얼 스튜디오 2010에서부터 크게 바뀌었다. 비주얼 스튜디오 2008까지는 VCBuild로 프로젝트 및 솔루션 빌드 환경이 구축됐지만, 비주얼 스튜디오 2010부터는 VCBuild가 언어와 상관없는 통합 빌드 도구인 MSBuild로 변경됐다. 따라서 VCBuild를 사용하는 프로젝트를 포팅할 경우 빌드머신의 환경을 VCBuild에서 MSBuild로 변경해야 한다. MSBuild 환경을 이미 사용하고 있었다면 새로운 비주얼 C++로 포팅 시 빌드 환경에 대한 부분 포팅 및 업그레이드가 좀 더 수월해진다.

tech_img4256.png

MSBuild의 엔진은 오픈소스로 공개돼 있다. 깃허브(https://github.com/Microsoft/msbuild)에서 소스 코드를 다운로드할 수 있다. MS는 MSBuild 기반의 빌드 환경을 추천하나 실제 실무에서 비주얼 스튜디오 IDE를 실행하는 파일인 devenv.exe로 빌드 환경을 구축하는 경우도 많다. devenv.exe는 MSBuild를 이용한 방법보다 옵션이 좀 더 직관적이고 쉬워MSBuild를 사용하기 위해 옵션을 공부할 필요가 없다. 또 devenv.exe는 내부적으로 MSBuild를 사용하므로 동일한 결과를 얻을 수 있는 장점 덕분에 널리 쓰이고 있다.



tech_img4257.png

devenv.exe를 이용해 빌드 환경을 구축하면 비주얼 스튜디오 환경변수가 등록된 커맨드 프롬프트를 이용할 수 있다. 이 커맨드 프롬프트는 비주얼 스튜디오에 있는 스크립트를 수정해 설정할 수도 있지만, 가장 간단한 방법은 시작 버튼의 비주얼 스튜디오 그룹에 등록된 프롬프트들을 사용하는 것이다.

devenv.exe에 /Setup이나 /InstallVSTemplates 등의 스위치를 쓰지 않는 단순 빌드 작업을 한다면 devenvexe를 관리자 권한으로 실행할 필요는 없다. devenv의 인자로 프로젝트 파일을 넣어 빌드하면 devenv는 해당 프로젝트 파일의 부모 폴더에서 솔루션 파일을 찾아 자동으로 참조한다. 참고로 devenv에 /log 인자를 붙일 경우 컴파일 과정을 로그 파일로 남길 수 있다. devenv를 사용한 프로젝트에 대한 보다 자세한 정료의 Devenv 명령줄 스위치(Devenv Command Line Switches)를 참고하면 된다.

MSBuild를 사용한 빌드 문제 중 가장 흔한 문제 중 하나가 VisualStudioVersion 환경변수와 관련된 문제다. 만일 개발 PC에 새로운 개발툴을 설치하고 커맨드라인의 MSBuild를 통해 빌드했는데, Platform Toolset에 대한 빌드 에러가 발생할 경우 프로젝트가 VisualStudioVersion 환경변수를 올바르게 참조하고 있는지 확인해야 한다.



<리스트 3> VisualStudioVersion 환경변수 문제로 빌드가 안되는 MSBuild 메시지의 예Project "c:\test\test.sln" (1) is building "c:\test\test.vcxproj" (3) on node 1 (default targets).
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V120\Microsoft.Cpp.Platform.t
argets(64,5): error MSB8020: The build tools for v140 (Platform Toolset = 'v140') cannot be found.
To build using the v140 build tools, please install v140 build tools.
Alternatively, you may upgrade to the current Visual Studio tools by selecting the Project menu
or right-click the solution, and then selecting "Upgrade Solution...". [c:\test\test.vcxproj]
Done Building Project "c:\test\test.vcxproj" (default targets) -- FAILED.



tech_img4260.png

컴파일러 옵션 고려 사항

MS의 C/C++ 컴파일러는 편의성을 위해 표준 문법과는 조금 다른 MS 확장(Microsoft Extension)을 제공하는데, 컴파일 옵션을 통해 사용할 수 있다. 대표적인 예가 C 언어에서 한 줄 주석(//)을 용인해 주는 것이다. 이와 별개로 표준과 일부 문법이 다른 것 또한 허용된다. 그간 비주얼 C++는 다른 컴파일러와 비교했을 때 표준을 지키지 않는 편이었다. 이로 인해 비주얼 C++ 6를 이용해 작성한 소스 코드를 다른 컴파일러에서 사용하기 위해서는 두 컴파일러간 공통적인 표준 문법으로 수정하는 작업이 필요했다.

대표적인 예가 for 문 루프 범위 안에서의 변수 생명주기 문제다. 비주얼 C++ 6는 for 문에서 선언되는 루프 변수 선언부를 for 문 범위 밖으로 판정한다. for 문은 개발 시 매우 빈번히 쓰이는 키워드다보니 비주얼 C++ 6의 이러한 처리는 표준이 아니지만 당시 많은 소스 코드가 이에 맞춰 for 문 안쪽의 변수를 표준과 다르게 간주하고 개발됐다. 그런데 차기 비주얼 C++에 C++ 표준이 대거 도입됐다. 이로 인해 for 문에 사용된 루프 변수 선언부에 int를 추가하는 반복적인 추가적인 작업을 해야만 했다.



<리스트 4> 비주얼 C++ 6에서 정상적으로 컴파일됐던 for문 이터레이션 변수 사용의 예for (int i= 0; i < 9; ++i)
{
// ...
}
for (i = 0; i < 9; ++i) // C2065
{
// ...
}



일부 프로젝트에 한정돼지만 최신 비주얼 C++는 이러한 문제가 포함된 소스 코드를 컴파일할 수 있도록 ‘for 루프 범위의 강제 규칙(Force Conformance in for Loop Scope)’이라는 별도의 컴파일 옵션이 추가됐다. 비주얼 C++ 6 이후 버전이라면 컴파일 옵션에 /Zc:forScope-를 부여하면 for 문의 루프 변수 선언부 안의 변수를 for 문 범위 밖에서도 엑세스할 수 있다. 개발자가 임의로 컴파일 옵션을 변경하지 않는다면 기본적으로 컴파일러는 /Zc:forScope 옵션을 부여해 해당 코드가 컴파일되지 않는다.

tech_img4258.png

비주얼 스튜디오 2015에 포함된 비주얼 C++에서 /Zc:for Scope- 옵션을 사용하면 다음과 같은 D9035 경고가 발생한다.

cl : Command line warning D9035: option 'Zc:forScope-' has been deprecated and will be removed in a future release

이는 그간 지원되던 ‘for 루프 범위의 강제 규칙 컴파일’ 옵션으로 지원하던 문법을 비주얼 스튜디오 2015부터는 더 이상 지원하지 않기 때문에 발생한다. 기존 버전의 컴파일러에서는 발생하지 않던 경고이므로 컴파일러 마이그레이션 시 문제가 될 수 있다.



문법 고려 사항

비주얼 스튜디오 2015의 비주얼 C++에 추가된 문법 중 사용자 정의 리터럴(UDL, User Defined Literals)이 있다. 개발자는 문자열 뒤에 리터럴을 함께 사용해 사용자 정의 리터럴을 생성할 수 있으며, 해당 사항은 최신 비주얼 C++ 라이브러리 전반에 반영돼 있다.



<리스트 5> 사용자 정의 리터럴 예#define _x "there"
char* func() {
return "hello"_x; // C3688
}int main() {
char * p = func();
return 0;
}



문자열 뒤에 공백 없이 매크로 뒤의 문자열 리터럴이 위치할 경우 예상치 못한 결과가 나오거나 사용자 정의 리터럴로 해석될 수 있다. 기존 비주얼 C++에서는 “hello”_x의 문자열 구성이 매크로가 반영됐으나 새로운 비주얼 C++에서는 _x가 정의되지 않았다는 에러가 발생한다. 또한 char16_t와 char32_t 타입이 기본 타입으로 추가돼 typedef로 해당 타입을 사용할 수가 없다. 따라서 해당 타입을 다시 정의한 코드라면 재정의 부분을 제거하고 해당 타입이 사용된 부분의 코드를 점검해야 한다.



<리스트 6> char16_t와 char32_t는 기본타입으로 typedef를 통해 새롭게 정의할 수 없다#include < cstdint>
typedef uint16_t char16_t; //C2628
typedef uint32_t char32_t; //C2628



표준을 따르는 문법

int* 형에 mutable과 const 지시자(Specifier) 붙이는 경우를 생각해 보자. 표준에서는 포인터 변수에 붙였을 때와 포인터가 가리키는 변수에 붙였을 때를 다르게 판단한다. 변수 foo::a와 같이 const 키워드가 포인터 기호 앞에 있을 경우 해당 지시자는 int형 분수에 부여된다. 따라서 foo::a 변수는 정상적으로 컴파일된다.



<리스트7> mutable과 const 지시자에 따라 처리되는 코드의 예struct foo {
mutable int const * a;
mutable int * const b; // C2071
mutable int &c; // C2178
};



하지만 변수 foo::b처럼 포인터 변수 자체에 속성이 부여된 경우 컴파일 에러가 발생한다. 포인터 변수는 그 자체가 const와 mutable이므로 의미가 없기 때문이다. 마찬가지 이유로 레퍼런스 변수 역시 문제가 발생한다. 이전 버전의 비주얼 스튜디오는 mutable, const 포인터 변수를 표준으로 처리했지만 표준에서 벗어난 레퍼런스 변수까지 정상적으로 컴파일되는 문제가 있었다. 최신 비주얼 C++에서는 이러한 오류가 올바르게 수정돼 컴파일 에러가 발생한다. 만일 새로운 빌드 과정에서 이러한 빌드 에러가 발생할 경우 mutable 키워드만 제거하면 정상적으로 컴파일된다.

공용 구조체를 잘 활용하면 동일한 메모리상에 다양한 정보를 기록함으로써 메모리 사용량을 절약하고, 타입이나 네임스페이스 등이 제약하는 추상적인 바운더리에 관계없이 코드를 작성할 수 있다. C++ 언어의 제약을 뛰어넘을 수 있는 이러한 트릭의 대표적인 예가 MFC의 MessageMapFunctions 공용 구조체다. 메세지 맵의 후보가 될 만한 모든 맴버 함수 포인터를 공용 구조체로 묶어 클래스가 다른 맴버 함수라도 공통 포인터(Generic member function pointer)로 접근 가능하므로 사용자는 내부 구현사항 및 클래스 관계를 모르더라도 손쉽게 클래스를 이용할 수 있다.



<리스트 8> 마법과 같은 MFC의 MessageMapFunctions 공용 구조체 구현의 예union MessageMapFunctions {
AFX_PMSG pfn;
BOOL (CWnd::*pfn_bD)(CDC*);
BOOL (CWnd::*pfn_bb)(BOOL);
BOOL (CWnd::*pfn_bWww)(CWnd*, UINT, UINT);
HBRUSH (CWnd::*pfn_hDWw)(CDC*, CWnd*, UINT);
int (CWnd::*pfn_iwWw)(UINT, CWnd*, UINT);
int (CWnd::*pfn_iWww)(CWnd*, UINT, UINT);
int (CWnd::*pfn_is)(LPTSTR);
LRESULT (CWnd::*pfn_lwl)(WPARAM, LPARAM);
// ...
};



공용 구조체는 일반 구조체와는 달리 메모리를 공유한다는 특수성 때문에 클래스, 구조체와는 맴버의 구성상 차이가 있었다. 만약 공용 구조체에 정적 맴버 변수나 정적 맴버 함수가 있다면 별다른 문제없이 컴파일이 될 것이다. 정적 맴버 변수의 경우 컴파일러에 따라 컴파일을 허용하거나 허용하지 않는 경우가 있을 수 있으나 비주얼 C++의 경우 컴파일을 허용한다. 따라서 foo1::a와 foo1::b를 정상적으로 사용할 수 있다.



<리스트 9> 공용 구조체의 구현 사항의 예union foo1 {
static int a;
static int b();
};int foo1::a;union foo2 {
int & c; // C2625
int && d; // C2625
};



그러나 공용 구조체 맴버로 쓰이는 레퍼런스 타입은 컴파일되지 않는다. 이것이 표준이기 때문에 C2625 컴파일 에러가 발생한다. 또한 우측 값 참조 역시 컴파일되지 않는 것이 표준의 규칙이다. 반면 구 버전의 비주얼 C++에서는 공용 구조체의 레퍼런스 타입도 컴파일 됐었다. 만약 마이그레이션 대상의 코드에 이러한 것들이 포함돼 있다면 표준에 맞춰 코드를 수정해야 한다.



라이브러리 변경 사항

비주얼 스튜디오 2015에 이르러서는 지금까지의 라이브러리 변화보다 더 큰 변화가 하나 있었다. 중복된 코드도 많고, 여러 문제를 야기했던 CRT((C Runtime Library) 라이브러리가 유니버셜 CRT(Universal CRT)로 변경되면서 대대적으로 리펙토링된 것이다. 먼저 MSVCRXX.DLL이었던 단일 파일이 APPCRTXX.DLL, DESKTOPCRTXX.DLL 그리고 VCRUNTIMEXX.DLL로 분리됐다. 이름에서 알 수 있듯, 모바일 환경이라면 DESKTOPCRTXX.DLL과 같은 라이브러리는 필수 파일로 배포되지 않는다.

APPCRTXX.DLL에는 파일처리, 부동소수점 지원, 저수준의 I/O 처리 등과 같은 기능이, VCRUNTIMEXX에는 버퍼조작, 오류 처리, 예외 처리 루틴과 같은 기능이 구현사항에 포함돼 있다. APPCRTXX.DLL과 DESKTOPCRTXX.DLL은 범용 CRT 라이브러리인 UCRTBASE.DLL로 통합될 수 있는데, 이는 OS 차원에서 제공되기 때문에 파일 네이밍에 버전 정보 등이 표기되지 않고 운영체제의 콤포넌트로 제공된다.

MS는 최근 정적 라이브러리 사용을 적극 권장하고 있다. 디스크의 용량이 커지고 네트워크 속도도 빨라졌을 뿐만 아니라 애플리케이션의 용량도 증가함에 따라 여러 컴포넌트들이 모여 기능을 수행하는 형태가 일반화되면서 공유 라이브러리로 인해 발생할 수 있는 문제를 줄이는 데 정적 라이브러리가 매우 효과적이기 때문이다.

최신 비주얼 C++에 포함된 링커는 새로운 라이브러리를 기본적으로 사용한다. 기본 프로젝트 설정으로 프로젝트 업그레이드를 진행했다면 소스 코드가 새로운 기본 라이브러리를 쓰게 된다. 반면 링커 속성에 기본 라이브러리를 무시하도록 설정했거나 명령줄에서 /NODEFAULTLIB 링커 옵션을 부여한 경우 추가 종속성 속성에 라이브러리 목록을 업그레이드해 기존의 라이브러리인 LIBCMT.LIB, LIBCMTD.LIB, MSVCRTD.LIB를 변경된 라이브러리인 UCRTBASE.LIB, UCRTBASED.LIB, LIBVCRUNTIME.LIB, LIBVCRUNTIMED.LIB 등으로 연결시켜야 한다.

CRT 라이브러리뿐만 아니라 수학 함수 라이브러리인 math.h에도 C/C++ 언어에서 사용되던 오버로딩된 함수가 포함돼 있다. 최신 비주얼 스튜디오부터는 cmath 헤더를 포함할 수 있게 되면서 기존 math.h에 있는 C++ 오버로딩된 함수가 제거됐다. 따라서 C++ 오버로딩된 함수를 코드에서 쓴다면 cmath를 포함하도록 소스 코드를 수정해야 한다.

예컨대 abs() 함수의 오버로딩된 함수 중 double 타입이나 float 타입은 cmath로 옮겨지면서 math.h에서 제거됐다. 따라서 cmath를 포함하지 않으면 기존 비주얼 C++로 컴파일된 바이너리와 달리 int 타입으로 오버로딩된 함수를 호출하게 되므로 컴파일 경고가 발생할 뿐 아니라 의도하지 않은 치명적인 계산 오류가 발생할 수도 있으므로 주의해야 한다.



<리스트 10> vector< const T>, set< const T>와 같은 const 타입은 컴파일되지 않는다void foo() {
std::vector< const int> a;
a.push_back(1);
a.push_back(2);
}



C++ 표준에 따르면 컨테이너에 const 속성의 값을 넣을 수 없다. 예컨대 벡터가 다루는 타입으로 const int를 지정한 경우 기존 비주얼 C++에서는 정상적으로 컴파일이 되지만 최신 비주얼 C++에서는 static_assert로 인해 컴파일이 되지 않는다.



<리스트 11> 컨테이너 메모리 할당에 추가된 static_assert 루틴template< class _Ty>
class allocator {
public:
static_assert(!is_const< _Ty>::value,
"The C++ Standard forbids containers of const elements "
"because allocator< const T> is ill-formed.");
typedef _Ty value_type;
//...



새로운 비주얼 C++는 부동소수점의 서식 지정 및 구문 분석의 정확성 또한 향상됐다. printf나 scanf처럼 값의 포멧팅이 필요한 함수의 경우 이전 버전에서는 표시되는 최대 자릿수보다 제한된 17자리까지만 계산이 되고 이후 값은 0으로 채워졌다. 반면 최신 비주얼 C++는 자릿수 제한이 768자리까지 늘어나 더 정확한 결과값을 얻을 수 있다. 참고로 fesetround()를 통해 별도로 지정하지 않으면 마지막 값은 반올림된다.



<리스트 12> printf의 포멧팅을 통한 부동 소수점의 출력 정확성 향상의 예printf("%.0f\n", pow(2.0, 80))
이전 결과값 : 1208925819614629200000000
최신 버전값 : 1208925819614629174706176



fopen으로 파일을 열면 파일 열기 동작에 대한 모드를 지정할 수 있다. 이 모드는 문자열로 지정 가능한데, ‘w’나 ‘r’과 같은 지정된 문자와 ‘+’ 같은 문자를 서로 조합으로 지정할 수 있다. 구 버전의 비주얼 C++는 “r+b+”처럼 잘못된 모드 문자열을 지정하더라도 원활하게 동작하도록 유연하게 처리해줬으나, 최신 비주얼 C++에서는 모드 문자열을 엄격하게 처리해 잘못된 매개 변수로 인해 fopen의 동작이 실패로 간주한다. 또한 새로이 snprintf 및 vsnprintf 함수의 구현사항이 추가됐다. 안드로이드와 공유하는 소스 코드의 경우 대개 윈도우 환경을 위한 snprintf를 구현해 놓고 시작하는 게 일반적인데, 새 비주얼 C++에서는 해당 함수의 구현사항이 추가돼 있으므로 stdio.h를 포함할 경우 각 상황에 대응하기 위해 구현한 snprintf와 vsnprintf의 선언을 삭제해야 한다.

time.h의 clock 함수와 asctime에도 작은 변화가 있다. clock() 함수의 경우 내부적으로 GetSystemTimeAsFileTime()를 써 시스템 시간 설정에 따라 결과값이 달랐으나, 최신 비주얼 C++에서는 GetSystemTimeAsFileTime()가 QueryPerformanceCounter()로 변경돼 시간 설정에 영향을 받지 않는 모노토닉(Monotonik)한 시간을 얻을 수 있게 변경됐다. asctime의 경우 “Sun Nov 01 01:00:00 2015”처럼 일 단위의 자릿수가 한자리 일때 0이 추가됐었다. 반면 새 비주얼 C++에서는 “Sun Nov 1 01:00:00 2015”와 같이 통상적으로 많이 사용하는 형식의 문자열로 결과값이 변경됐다.



삭제된 표준 라이브러리의 함수

기존 비주얼 C++에서 제공했던 표준 라이브러리에서 제거된 함수 중 가장 빈번하게 사용되는 함수는 printf와 scanf이다. 이 함수들은 구현부 파일에서 제거되는 대신 인라인으로 새롭게 정의됐다. 즉 헤더 파일에 선언되고 정의돼 있는 것이다. 따라서 이 헤더 파일들을 포함하지 않고 로컬로 선언한 다음 소스 코드상에서 사용했다면 stdio.h 파일을 포함시켜야 한다. 만일 헤더 파일을 포함하지 않으면 구현부를 찾을 수 없으므로 링커가 LNK2019인 ‘확인되지 않는 외부 기호’ 에러를 낼 것이다. 만일 소스 코드 수정사항이 많거나 수정할 수 없는 상황이라면 프로젝트 속성의 링커 속성에서 legacy_stdio_definitions.lib 라이브러리를 포함시키면 된다. legacy_stdio_definitions.lib는 빈번하게 사용되는 stdio.h에 선언된 함수들의 구현 사항에 변경이 있어 구현부를 찾을 수 없을 경우 대체할 수 있는 함수 구현체 라이브러리다. 윈도우 SDK 8.1 이하 버전을 사용해 컴파일된 라이브러리의 경우 ‘확인되지 않은 외부 기호’ 오류가 빈번히 발생할 수 있다. 이러한 문제를 가장 빠르게 해결할 수 있는 방법 중 하나도 legacy_stdio_definitions.lib를 포함시키는 것이다.

소스 코드를 정적 라이브러리로 컴파일한 라이브러리를 포함할 경우 _iob, _iob_func, _imp_* 등의 참조 에러가 발생할 수 있다. 이 경우 해당 정적 라이브러리를 다시 빌드해야 한다. 만일 정적 라이브러리로 컴파일할 수 없다면 해당 라이브러리를 캡슐화하는 DLL 파일을 만들어 별도로 로드하는 로직을 구성해 문제를 우회해야 한다.

hash_map과 hash_set은 직관적인 이름이라서 종종 사용되곤 했었으나 이제는 동일한 기능을 하는 표준 함수인 unordered_map과 unordered_set을 써야 한다. 또한 gets와 _getws가 C 표준 라이브러리에서 제거된 것도 변화 중 하나다. 표준 함수였던 gets와 MS 플랫폼에서만 사용할 수 있는 _getws는 안전하게 사용할 수 없는 함수로 C11 표준에서 제거됐기 때문이다. 그러므로 최신 비주얼 C++에서도 사용할 수 없다. 그 대신 보안성이 높아진 fgets, fgetws 함수를 사용하거나 시큐어 계열 함수인 gets_s, _getws_s를 써야 한다.



출처 : 마이크로소프트웨어 12월호

제공 : 데이터 전문가 지식포털 DBguide.net