글 목록 보기

Lyn
조회 수 967 추천 수 0 댓글 0
Atachment
첨부 '2'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

정말 오랫만에 로우레벨 관련 질문을 받았습니다...

바로 아래 코드에서

 

#include <smmintrin.h>

int main()
{
    __m128i m_128i;
    m_128i.m128i_i16[0] = 0;

    for (int i = 0; i < 15; i++)
    {
        auto a = _mm_extract_epi8(m_128i, i); // <-- 컴파일 에러
    }
}

 

error C2057: expected constant expression 에러가 발생 한다는 얘기입니다.

 

_mm_extract_epi8 의 prototype 을 확인 해 보면 extern int   _mm_extract_epi8 (__m128i /* src */, const int /* index */); 입니다. 일반적으론 변수 -> const 로의 변환은 자유로우므로 문제가 없어야 하는데... 오류가 나죠.

 

원인은 간단한데 _mm_extract_epi8 는 C++의 "함수" 가 아니라, 기계어와 매칭되는 Intrinsics 입니다. 저게 선언되어있는 헤더가 smminstrin.h 라는 것에서도 알 수 있지요. 해당명령어는 intel 명령어이니 intel 사이트에서 한번 해당 내용을 검색 해 봅시다

 

inst01.PNG

 

 

몇가지 정보가 나오네요. 해당 Intrinsics 에 대응되는 어셈블리는 pextrb r32, xmm, imm 이고, SSE4.1 지원이 필요하며, Latency 는 CPU 종류에 따라 2~3 클럭이네요.

 

여기는 Visual Studio 에 들어있는 헤더와 달리 각 파라메터에 이름이 붙어있습니다. a, 와 imm8 이네요.
그럼 imm8 은 뭐냐가 중요해지는데, 이 imm은 이름만 보면 무슨 레지스터 비슷한걸로 착각 할 수도 있는데, 실제로는 Immediate value 의 약자입니다.
한자로는 즉치값(즉시 계산되는 값) 이라고도 하는 것 같은데 C++ 프로그래머 입장에서 보자면 컴파일타임에 계산하여 결정이 가능한 상수(const) 를 이야기 합니다. 8은 뭐냐구요? 걍 bit 수 입니다. 저 값은 8bit 범위에서 작동한다는 거죠. 즉 imm8 을 풀어서 써보면 8-bit immediate value 라고 할 수 있겠습니다.

 

그럼 결론을 내보자면. Intrinsics 를 호출 할때는, C++의 호출/캐스팅 규칙 보다 CPU 명령어의 제약사항이 우선하며, _mm_extract_epi8 의 2번째 argument는  Immediate value 일 것을 요구하므로 변수를 사용할 수 없습니다.

 

기왕 하는김에 하나 더 해보자면, imm8 이라고 되어있긴 하지만, 128bit 변수를 8bit 단위로 쪼개서 값을 읽는 명령이므로, 실제 imm8의 사용 범위는 0~15 일 것입니다. 그런데 만약 그 이상의 값(ex : 16) 을 넣으면 어떻게 될까요?

 

 inst02.PNG

 

다행히도 컴파일러는 해당 명령어의 허용 범위를 알고 있으므로 

warning C4556: value of intrinsic immediate argument '16' is out of range '0 - 15' warning 을 띄워 줍니다.

 

TAG •
?

2016.06.20 21:58

Visual C++ C4503 Warning Fix

Lyn
조회 수 3565 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
#include <map>
 
using namespace std;
 
struct VeryLongClassNameType1
{
 
};
struct VeryLongClassNameType2
{
 
};
struct VeryLongClassNameType3
{
 
};
struct VeryLongClassNameType4
{
 
};
struct VeryLongClassNameType5
{
 
};
typedef map<VeryLongClassNameType1VeryLongClassNameType2LongNameStdMap;
typedef map<VeryLongClassNameType3LongNameStdMapLongNameStdMap2;
typedef map<VeryLongClassNameType4LongNameStdMap2LongNameStdMap3;
typedef map<VeryLongClassNameType5LongNameStdMap3LongNameStdMap4;
 
int main()
{	
	LongNameStdMap4 a;
}

 

위와 같은 코드를 빌드 하면 컴파일과 실행엔 문제가 없지만 C4503 Waring 이 뜹니다.

이 Warning 은 http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(C4503)&rd=true 에서 보다 시피, type명이 너무 길어서 짤릴 수 있다는 경고인데, 이 경우 나중에 크래시 덤프 등을 얻어서 처리할때 type 이 매칭 되지 않을 우려가 있습니다. 사실 4098 라는 type 길이는 충분했... 어야 했는데 template 가 겹치고 겹치면서(주로 Map을 겹쳐서 tree 형태의 자료구조를 구성 할 경우겠지만 ....) 의도하지 않게 긴 type 명을 가지게 되어 버리는 경우가 있습니다.

 

이 경우 위의 MSDN 링크 에서는 아래와 같은 방식의 다른 클래스로 한번 Wrapping 하는 해결책을 제시 하고 있습니다.

typedef map<VeryLongClassNameType1VeryLongClassNameType2LongNameStdMap;
typedef map<VeryLongClassNameType3LongNameStdMap_LongNameStdMap2;
struct LongNameStdMap2
{
	_LongNameStdMap2 Element;
};
typedef map<VeryLongClassNameType4LongNameStdMap2LongNameStdMap3;
typedef map<VeryLongClassNameType5LongNameStdMap3LongNameStdMap4;
struct NewClass
{
	LongNameStdMap4 Element;
};

이 경우 map 에 대한 접근이 a.Element.find() 처럼 한단계가 더 들어가게 됩니다... 만약 이름이 그 이상으로 길어서 2~3번 더 중간에 이름을 끊어 줘야 하는 경우, a.Element.Element.find() 와 같은 상황이 발생합니다. warning 을 제거하기 위해 의도와 다른 불편한 사용 방법을 강요받게 되는거지요. 이건 좋은 방법이라고 할 수 없습니다.

 

이럴때는 

typedef map<VeryLongClassNameType1VeryLongClassNameType2LongNameStdMap;
typedef map<VeryLongClassNameType3LongNameStdMap_LongNameStdMap2;
class LongNameStdMap2 : _LongNameStdMap2
{
 
};
typedef map<VeryLongClassNameType4LongNameStdMap2LongNameStdMap3;
typedef map<VeryLongClassNameType5LongNameStdMap3LongNameStdMap4;
class NewClass : LongNameStdMap4
{
 
};

 

처럼 public 상속을 해버리면 간단합니다... 그럼 일반적으로 map을 쓰는것과 전혀 다르지 않는 방법으로 코딩하면서 워닝만을 제거 할 수 있습니다.

TAG •
?

Lyn
조회 수 4349 추천 수 0 댓글 0
Atachment
첨부 '3'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

예전 x86 시절에는 다양한 calling convention 이 있었습니다.

MS가 OS API 의 디폴트로 사용한 pascal 식 호출방식(__stdcall) 막상 언어로 파스칼을 쓰는 델파이가 파스칼식을 사용 하지 않고 사용하고 있던 fastcall, 그리고 c에서 사용하는 cdecl

 

이 각각의 호출 방식은 각각 장단점이 있었는데, pascal 식은 바이너리의 크기가 작아진다, fastcall 은 인자 전달에 레지스터를 사용 하므로 속도에 유리하다, cdecl 은 최적화 단계에서 스택 push ,pop 을 생략할 수 있으므로, 최적화에 유리하고 가변인지를 지원 할 수 있다는 장점이 있었습니다.

 

그러나 바야흐로 x64 시대, 컴파일러를 설계하는 사람들은 각각의 장점을 통합한 새로운 호출 방식을 만들게 되었습니다. 그 이후 calling convention 을 지정하는 모든 키워드는 x64 컴파일시에 무시되며, 그런거 신경 안써도 되는 평화로운 세월이 이어져 왔습니다.

 

그러나 시간이 흘러 CPU는 클럭경쟁의 시대가 막이 내리고, SMID 와 멀티코어의 시대로 넘어 왔는데... 당연히 CPU 제조사들은 새 SIMD 명령어를 추가 하기 시작 했고, 그에 맞춰 새 명령어용 레지스터가 추가 되었습니다. 그 시점에서 컴파일러 개발자들은 이렇게 생각 햇나 봅니다 "어? 레지스터가 놀고있네?" 라고. 그리하여 VS2013 이후부터는 __vectorcall 이라는 키워드가 추가되어, 더이상 x64 바이너리에서도 calling convention 을 무시 할 수 없게 되었습니다.

 

간단한 예제를 보겠습니다.

 

#include <cstdio>
#include <intrin.h>

__m256i func(double d1, double d2, double d3, double d4, __m256i f)
{
    printf("%f %f %f %f\n", d1, d2, d3, d4);

    printf("%lld %lld %lld %lld\n", 
        f.m256i_u64[0], f.m256i_u64[1], f.m256i_u64[2], f.m256i_u64[3]);

    return f;
}

int __cdecl main()
{
    __m256i f;

    for (int i = 0; i < 4; ++i)
    {
        f.m256i_u64[i] = i;
    }
    
    f = func(0.1, 0.2, 0.3, 0.4, f);

    return 0;
}

 

이코드를 빌드해서 실행 해 보면 다음과 같은 코드를 볼 수 있습니다.

vc1.PNG

 

 

그리고 함수의 프로토타입에 __vectorcall 을 추가하여

__m256i __vectorcall func(double d1, double d2, double d3, double d4, __m256i f)

와 같이 만든 후 다시 빌드 해 보겠습니다

 

 

 

vc2.PNG

 

차이가 보이시나요? 첫 줄에서 ymm4 라는 avx 레지스터가 추가적으로 함수 전달에 쓰이는 것을 볼 수 있습니다.
매번 __vectorcall 을 붙이기 싫다면 

vc3.PNG

 

옵션에서 조정 하거나, /Gv 플래그를 추가하여 사용 할 수도 있습니다.
단 /Gv 플래그를 켤 경우, main 은 항상 __cdecl 이어야 한다는 조건이 있으므로, 위 코드처럼 __cdecl 을 붙여주지 않으면 경고가 발생 할 것이니 조심하세요.

TAG •
?

Lyn
조회 수 4172 추천 수 0 댓글 0
Atachment
첨부 '1'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

지인이 물어 온 김에 정리 해 둡니다.

 

결론부터 말하면 C++ 의 Lambda 의 Calling Convention 은 상황에 맞춰 "그때그때 다르다" 입니다.
 

기본적으로 cdecl(물론 VC++ 프로젝트 설정에서 옵션을 바꾸면 다른 것도 가능합니다) 멤버여야 한다면 thiscall 그렇지 않으면 대입 되는 함수 포인터의 type 에 따라서 결정됩니다.
즉 원하는 형태로 대입만 하면 거기에 맞춰 적절히 컴파일 됩니다.

 

lambda.png

 

외부에 callback 함수를 넘길 때 stdcall 로 선언해야 하는 경우가 꽤 되는데(특히 윈도우 API들) 이 때도 부담없이 Lambda 를 사용 할 수 있습니다.

 

단 아쉽게도 auto 와의 조합은 제공하지 않는데, 

 

auto __stdcall f1 = [](int num1, int num2, int num3) { printf("%d %d %d\n", num1, num2, num3); };

 

와 같은 문법은 경고를 내며 __stdcall 이 무시됩니다... 

TAG •
?

Lyn
조회 수 3900 추천 수 0 댓글 1
Atachment
첨부 '1'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

C++ 은 C에서 유래한 오래된 언어 답게 가능한 한 최소한의 라이브러리만 가지고 있던 시절이 있었습니다. 

하지만 시대의 흐름은 거스를 수 없었고, STL이 추가 된 이후 200년대 이후 TR1을 시작으로 매우 많은 라이브러리들이 추가 되었습니다.

 

이 많은 라이브러리들은 모두 (당연하게도) 모두 C++로 만들어 져 있었으며 template 을 상당히 예전부터 가지고 있던 언어 답게 매우 많은 라이브러리가 template 기반으로 되어 있었고 template 의 특성상 라이브러리의 소스가 모두 공개될 수 밖에 없기 때문에 별다른 작업이 없이도 C++ 프로그래머들은 표준 라이브러리의 소스를 마음껏 볼 수 있었습니다(의외로 특정 언어의 라이브러리가 그 언어로 다 만들어진 경우가 드뭅니다)

 

덕분에 대체 tuple 의 소스는 어떻게 구현되어 있을까 궁금해서 열어봤다가, 2개부터 N개까지의 tuple 이 전부 따로 구현되어 있는 것을 보고 매우 실망했던 (T.T) 가슴아픈 기억도 있는데, C++ 표준위원회는 이러한 특정 라이브러리를 더 깔끔하게 만들기 위해 새 문법이 필요하다면 컴파일러 개발사들의 구현 가능여부 (....) 따위는 고려하지 않고 새 문법을 추가하였고(tuple 을 구현하기 위해 추가된 variadic template 라던가... 물론 이것도 고수들은 나름대로 다른곳에 쓸 방도를 찾아 내더군요. 그러라고 만들어준게 아닌 template 으로 metaprogramming을 한 것 처럼) 결국 영원히 추가되지 않을 듯 한 export 키워드라는 흑역사도 만들어 내었습니다.

 

하지만 C++11에서 Type Traits가 추가되면서 사정이 좀 달라지는데, 당시까지의 C++ 문법으로는 구현이 불가능한 함수들이 표준에 포함 되었기 때문입니다.

예를들어 std::is_integral<T> 는 해당 T type 이 정수형인가를 판정하는 함수인데, 이는 아주 단순하게 존재하는 모든 기본type 에 대해 해당 template 을 특수화 함으로서 해결하였습니다. 그 일부를 보면

 

01.png

 

대략 뭐 이런식이죠.

 

하지만 std::is_enum<T>, std::has_virtual_destructor<T> 등은 분명 라이브러리 형태로 제공 되어야 하나, 어떤 방법으로도 해당 정보를 프로그래머가 얻어낼 수 없습니다. 그렇다고 해당기능을 연산자로 넣기에는 너무 많은 연산자가 추가되어야 하고, 연산자 주제에 상당히 긴 이름을 가질 수 밖에 없는 문제가 있습니다. 물론 reinterpret_cast 같이 기존에도 이름이 긴 연산자가 있었으나, 이는 의도적으로 길게 만들어진 작명으로서 가능하면 쓰지 말라는 의미와 함께 만약 사용하였을 경우 눈에 잘 띄도록 하는 의미도 있습니다.

 

결국 언어에 문법을 추가되지 않았으나, 제공해야 하는 기능은 생겨 났고, 그 결과는 별 수 없이 컴파일러에 의해 코드가 자동 생성되도록 하는 대량의 컴파일러 확장키워드의 추가로 나타났습니다. 예를들어 VC++2015는 아래와 같은 매크로를 제공하는데, __(언더바 2개) 로 시작하는 함수는 전부 실제 존재하는것이 아닌 컴파일러가 컴파일 시점에 알아서 채워 주는 값들입니다.

 

// COMPILER SUPPORT MACROS

// VC++ V14 SUPPORT
  #define _IS_BASE_OF(_Base, _Der) \
: _Cat_base<__is_base_of(_Base, _Der)>
  #define _IS_CONVERTIBLE(_From, _To) \
: _Cat_base<__is_convertible_to(_From, _To)>
  #define _IS_UNION(_Ty) \
: _Cat_base<__is_union(_Ty)>
  #define _IS_CLASS(_Ty) \
: _Cat_base<__is_class(_Ty)>
  #define _IS_POD(_Ty) \
: _Cat_base<__is_pod(_Ty)>
  #define _IS_EMPTY(_Ty) \
: _Cat_base<__is_empty(_Ty)>
  #define _IS_POLYMORPHIC(_Ty) \
: _Cat_base<__is_polymorphic(_Ty)>
  #define _IS_ABSTRACT(_Ty) \
: _Cat_base<__is_abstract(_Ty)>
  #define _IS_FINAL(_Ty) \
: _Cat_base<__is_final(_Ty)>
  #define _IS_STANDARD_LAYOUT(_Ty) \
: _Cat_base<__is_standard_layout(_Ty)>
  #define _IS_TRIVIAL(_Ty) \
: _Cat_base<__is_trivial(_Ty)>
  #define _IS_TRIVIALLY_COPYABLE(_Ty) \
: _Cat_base<__is_trivially_copyable(_Ty)>
  #define _HAS_TRIVIAL_DESTRUCTOR(_Ty) \
: _Cat_base<__has_trivial_destructor(_Ty)>
  #define _HAS_VIRTUAL_DESTRUCTOR(_Ty) \
: _Cat_base<__has_virtual_destructor(_Ty)>
  #define _UNDERLYING_TYPE(_Ty) \
__underlying_type(_Ty)
  #define _IS_LITERAL_TYPE(_Ty) \
: _Cat_base<__is_literal_type(_Ty)>
  #define _IS_ENUM(_Ty) \
: _Cat_base<__is_enum(_Ty)>
  #define _IS_DESTRUCTIBLE(_Ty) \
: _Cat_base<__is_destructible(_Ty)>
  #define _IS_NOTHROW_ASSIGNABLE(_To, _From) \
: _Cat_base<__is_nothrow_assignable(_To, _From)>
  #define _IS_NOTHROW_DESTRUCTIBLE(_Ty) \
: _Cat_base<__is_nothrow_destructible(_Ty)>
  #define _IS_TRIVIALLY_ASSIGNABLE(_To, _From) \
: _Cat_base<__is_trivially_assignable(_To, _From)>
  #define _IS_CONSTRUCTIBLE \
__is_constructible
  #define _IS_NOTHROW_CONSTRUCTIBLE \
__is_nothrow_constructible
  #define _IS_TRIVIALLY_CONSTRUCTIBLE \
__is_trivially_constructible

 

 

전 이런 결과가 된 것이 언어가 제 컨트롤을 벗어난 것 같아 좀 아쉽기도 하고 한편으로는 라이브러리의 형태로 제공되느라 지저분해진 것들이 많은데(예를들면 std::map 이라거나 ... 요즘은 많은 언어들이 map 정도는 라이브러리가 아니라 문법차원에서 지원 하죠) 언어를 깔끔하게 만들기 위해선 좋다고 생각합니다. 

TAG •
?
  • ?
    사무엘 2015.12.31 19:16
    요즘 C++ 라이브러리는 반쯤은 언어를 확장해 가면서 제공되고 있군요. ㄷㄷㄷ
    reinterpret_cast는 일부러 가능한 한 쓰지 말라고 저렇게 길게 작명된 것이었고.. 좋은 정보 감사합니다!

Lyn
조회 수 113 추천 수 0 댓글 0

"비밀글입니다."


Lyn
조회 수 2659 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

Windows 는 Vista 부터 고 DPI 지원을 하기 시작 했는데, 이게 버전마다 좀 차이가 있다.

 

Vista : DPI 변경시 재부팅 필요함

7 : DPI 변경시 로그아웃 필요함

8 : DPI 변경시 로그아웃 필요함.

8.1 : DPI 변경시 로그아웃 필요 없음!!!

 

여기서 중요한 이슈가 발생 하는데, 8.1에서 로그아웃이 필요 없다는 것은 프로그램 실행중에 DPI 가 바뀔 수 있다는점이다. 결국 MS는 이 문제를 해결하기 위해 메시지 하나를 추가하는데 바로 WM_DPICHANGED 이다. 당연하겠지만 Windows 8.1 이상에서만 발생하며, Windows SDK 도 8.1 이상을 써야한다(뭐 어차피 달랑 정수값 하나니 그냥 선언해서 써도 되긴 하지만 ...)

 

 

참조 : https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx

TAG •
?

2015.04.20 14:08

Resharper C++ 사용기 - 1

Lyn
조회 수 3358 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

코드의 네이밍 스타일을 지정하는 옵션입니다.
 

1.png2.png

 

더블클릭하면 스타일을 지정 할 수 있게 되어있습니다.
기본은 C++표준에서 자주 쓰이는 전부소문자 형태이네요. 전 Windows API 에서 주로 쓰는 Pascal 스타일을 주로 쓰니 본격적으로 쓰기 시작하면 바꿔놔야 할 것 같습니다

 

 

아래는 특정 코드 패턴에 대해 색으로 표시를 해 주는 기능입니다(보통 정적분석이라고들 부르죠)
목록을 보면 boost를 사실상 표준 라이브러리 취급을 해줘 버리는 느낌이네요. boost 라이브러리 관련 기능도 들어 있습니다.
사실 저도 굉장히 많이 씁니다. 특히 boost::format 같은건. stringstream 은 정말 극혐이라서요.


어쨋든 잘못된 패턴의 코드에 눈에 띄게 색을 입혀준다는것은 코딩 실수를 방지하기 위해 아주 좋은 방법이지요. 몇가지만 테스트 해 보겠습니다.

5.png

 

아래처럼 구형 C스타일의 캐스팅을 사용하면 녹색으로 밑줄이 그어지면서 전구 모양의 아이콘이 생깁니다.
어떤 문제로 전구가 떳는지 알려며, 아래같은 경우는 Use static_cast 를 클릭하면 자동적으로 C++ 스타일의 캐스팅 연산자를 쓰도록 변환됩니다

6.png

 

 

아래처럼 printf(위 메뉴목록을 보시면  알겠지만 boost::format 도 지원 합니다) 의 파라메터가 잘못된것은 빨간줄로 나타납니다.
이 줄 색은 등급을 어떤것으로 해 놓았냐의 문제인데, 기본 옵션에서 format string이 잘못된 것은 error 취급하게 되어 있으므로 빨간줄입니다.11.png

 

잘못된 format string에 커서를 갖다 대면 빨간 전구 아이콘이 뜨고, 클릭하면 %s를 %d로 자동으로 변환 해 줍니다.

12.png

 

의외로 모르는 사람이 많은 부분인데 64bit 정수의 경우 %d로 출력할 수 없고 %lld 를 사용해야 합니다.(gcc의 경우 %I64d를 사용합니다)
이 부분에 대해서도 정확히 처리를 해 주고 있습니다.

20.png

 

한가지 아쉬운것은 error로 세팅을 하더라도 빌드 자체는 문제 없이 진행됩니다.
오류 메세지를 띄워서 빌드를 막아줫으면 좀더 좋지 않았을까 하는 아쉬움이 있습니다

13.png

 

하지만 처음부터 하는게 아니라 프로젝트 중간에 툴을 도입하여 코드를 정리하려고 하면 눈으로 일일히 찾으려면 고생좀 해야 할텐데요...

Next Issue in File 을 클릭하면 이슈를 하나 하나 추적해서 고쳐나갈 수 있습니다.

14.png

아래는 코드를 ReFormatting 할때 사용할 옵션들입니다.
거의 모든 경우의 옵션을 다 지원 합니다... 만 이미 VS2013의 Format 옵션도 충분히 다양해져서 그리 큰 의미가 있어 보이진 않습니다.
 

코드가 어떻게 바뀌는지 미리보기 하는 기능도 VS 자체에서 이미 제공되는 기능입니다.

 

3.png

 

그나마 장점이라면 VS에 내장된 옵션의 불성실한(....) 미리보기 화면과 달리 유저가 실제로 보게 되는것과 같은 코드 색을 입혀준 정도의 장점이 있겠습니다.
옵션도 아주 쬐금 더 다양하구요. 유명한 프리셋을 몇 종류 제공한다는것도 일단 장점이라면 장점입니다(애초에 편의성때문에 사용하는 툴이니까)

21.png

 

Source Reformat 은 영역 선택으로도 가능하고, 파일 전체 역시 가능합니다.
이 역시 원래 VS 내장 기능으로도 제공되던 부분입니다.

10.png

뭐 별로 눈에 띄지 않는 헤더 자동완성입니다.

8.png

 

만약 헤더를 못찾을 경우 붉은 밑줄을 그어 주며, 자신이 어디에서 include를 시도했는지 알려줍니다.

7.png

 

이거 굉장히 맘에 들었던 기능인데... 사용 하지 않는 헤더를 어둡게 처리 해 줍니다.
다른 언어에선 많이들 지원되는 기능인데(주로 VM 언어들에서...) C++ 에서는 쓰는지 안쓰는지 잘 몰라서 그냥 냅둔 경우가 많았습니다.
안그래도 다른 언어에 비해 거지같이 컴파일 느린 C++인데 쓸데없는 헤더를 제거하는것으로 조금이나마 컴파일 속도를 올릴 수 있다면 그것만으로도 유용하겟죠 

9.png

 

제가 굉장히 많이 쓰는 기능중 하나인, 헤더 선언하고 구현부 자동생성하는 기능입니다.

15.png

 

이부분이 Visual Assist 대비 장점이 있는데, VA 는 여러번 클릭하면 컴파일이 되던말던 중복생성 (....) 해 주는 무식한 짓을 하는데 비해, Resharper 의 경우 즉시 아이콘이 navigate 로 바뀝니다.

16.png

 

흔히 사용하는 Extract Method 기능입니다.
뭐 대부분의 툴과 비슷해서 별로 설명할게 없습니다.

17.png

 

Refactoring 기능은 좀 많이 아쉽습니다.
코드가 어떻게 바뀌는지 전혀 미리 보여주지 않습니다. 실수하기전에 체크할수 없고 바뀐 뒤에 한번 더 코드를 눈으로 확인해야합니다.

18.png

 

 

아래 기능은 코드 자동생성 기능인데... 뜬금없이 닷넷용 소스 생성 기능이 들어 있습니다.
문제는 이 글을 쓰기 위해 새로 세팅한 VM위에 달랑 VS2013과 Resharper C++만 설치해 놓은 상태인데도 이게 뜹니다 (....) 절대로 닷넷용 Resharper 가 설치되어 있는 상황이 아닙니다. 심지어 C++/CLI 는 없고 vb.net 과 C#만 있습니다;

4.png

 

 

 

PS. Resharper 는 생각하는 중입니다 (....)

19.png

 

PS2. 가격은 Visual Assist 279달러 Resharper C++ 229달러로 50달러 쌉니다.
가격을 생각하면 선택 해 볼만한 툴중 하나입니다.

 

PS3. 전체적으로 Visual Assist 는 결과가 좀 이상하더라도 일단 빨리 보여주는 쪽으로 개발이 되어있는-0-;; 느낌이 강하게 나는데(코드 분석하는 동안엔 정말 말도 안되는 결과도 가끔 보여주죠....) 이쪽은 확실히 정확한 결과를 보여줍니다. 단지 분석이 좀 느리네요

 

PS4. 사실 치명적인문제는... 베타시절보단 많이 빨라 졋지만 그래도 느립니다.
제가 예전에 작업하던 중대형 프로젝트(프로젝트 크기가 약 25만라인)를 열어 봤는데... 이건 머 코딩이 불가능할 정도입니다. 정말 심각하게 느려요.
사실 전 툴이 느리다고 불평하는 사람한텐 똥컴쓰지 말고 장비좀 좋은거 쓰라고 하는데(특히 맥북에어 쓰면서 느리다고 징징대는놈들한테....) 이건 뭐 답이 안나옵니다.

이클립스랑 IntelliJ 느린건 뭐 애교로 보이는 수준으로 툴이 느려집니다.
컴퓨터를 업그레이드 하려고 해도 제가 저 프로젝틀을 열어봣던 장비가 Intel i7 4770K이 달려있어서.. 더이상 딱히 올릴 방법도 없는 상태구요

 

가장 골때리는건 Visaul Assist 처럼 Enable/Disable 전환이 그리 쉽지 않아서 필요할때만 켜는것도 좀 애매하단 겁니다... 차기 버전에서 속도 개선이 필수적으로 이루어 져야 겠네요. 뭐 중규모 정도의 프로젝트라면 별 문제 없어 보입니다만...

 

일단 어차피 생겼으니 더 써보면서 두번째 글 올려보겠습니다.

TAG •
?

Lyn
조회 수 9583 추천 수 0 댓글 0
Atachment
첨부 '10'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

nuget이 뭔가 하니... 

java의 maven, ruby의 gem, node.js 의 npm 같은 패키지 저장소입니다. ms가 만든거죠


필요한 라이브러리를 매번 찾아서 세팅하려면 귀찮으니 인터넷에서 받아서 자동설치 하자... 의 개념입니다.
저도 C#으로 개발할 때는 매우 잘 이용하고 있었는데... 멍청하게도 이게 C++을 지원한다는걸 모르고있었습니다.

얼마전 알게된 김에 한번 테스트 해 보았습니다.


모든 스크린샷은 최근에 무료 배포되는 VS2013 Community Edition입니다.


그럼 새 Console 프로젝트를 하나 만들고, 

Project -> Manage Nuget Packages... 를 클릭합니다


1.png



그리고 Serch Online 텍스트박스에 설치하고자 하는 패키지명을 넣습니다.
전 흔히 쓰는 boost로 해 보겠습니다


2.png


VC++ 2013 는 버전으로는 12.0 이므로 boost-vc120 을 선택하여 설치합니다

3.png


install을 누르면 설치되는데 속도는 꽤 빠릅니다.
boost가 5분정도만에 설치 되더군요


4.png


설치가 끝나면 프로젝트에 패키지 파일하나만 달랑 추가됩니다. 깔끔하네요

5.png


그럼 정말 잘 되는지 테스트를 해야겟죠?

boost::shared_array 를 사용한 후 빌드를 해 봤습니다


6.png


깔끔하게 잘 진행 되네요.


그럼 패키지 파일들은 어디에 저장 되는지 궁금해 지는데,

솔루션 폴더(프로젝트가 아님!) 내의 package 폴더에 저장됩니다


7.png



그런데 이상한점이 있는데, 프로젝트 옵션 어디에도 저 Path에서 lib나 include 를 하고 있지 않습니다.
그럼 대체 어떻게 include 와 링크가 성공하는걸까요?

9.png


해답은 프로젝트파일에 있습니다.

ExtensionTargets 을 추가해서 빌드 targets 파일을 추가 해 주는거죠

10.png


그래서 프로젝트에 파일이 등록되어 있지 않아도 정상적으로 include 와 link 가 가능해 집니다.


PS. 다른 라이브러리는 몰라도 boost는 nuget 안쓰는게 좋을지도...

5G 가 프로젝트 생성될때마다 추가되면 (.....)


#본격_SSD_용량_줄어드는_파일.jpg

8.png


TAG •
?

Lyn
조회 수 3561 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

Devpia 에 질문 올라온김에 정리 해 두는데


크게 특별한 것이 아니라 ... 

http://www.jiniya.net/tt/788 여기 있는 내용 그대로다.
혹시 링크 깨졋으면 http://lunapiece.net/Article/27343 여길 봐라


하면안되는짓이 같은 이유는 InitInstance / ExitInstance  는 결국 DllMain 의 Reason 에 따라 분기하는 함수이기 때문


관련 자료는 아래 링크에서 찾을 수 있다 
https://msdn.microsoft.com/ko-kr/library/66f127e5.aspx


"MFC에서 제공하는 DllMain 함수는 DLL이 로드될 때 InitInstance를 호출하고, DLL이 언로드되기 전에 ExitInstance를 호출합니다"

TAG •
?

Lyn
조회 수 4188 추천 수 0 댓글 0
Atachment
첨부 '3'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

상당히 많은 언어가 예외 처리기를 가지고 있는데, 각각의 언어별로 특성이 있습니다.
try - catch - finally 를 모두 가지고 있는 언어(C#, Java 등) 도 있고

try - except (C 계열의 try - catch 에 대응) 와 try - finally 를 가지고 있지만 try - except 뒤에 finally 가 붙을 수 없어서 아래와 같이 중첩을 해야 하는 Pascal 계열의 언어(주로 Delphi 겠지만..) 도 있습니다


try

  try 

    //do something

  except

    //do something

finally

  //do something 


네 그리고 대망의 C++은.. finally 그런거 없습니다 (...)
C++ 표준협회에서는 stack 에 생성된 객체의 소멸자를 이용하라고 하는데 다른 언어의 코드를 C++로 옮기다가 보면 해제할것 하나하나마다 객체 만들어 주기도 보통 일이 아닙니다. 하지만 의외로 타 언어의 샘플을 C++로 옮기는게 자주 발생 하는 일이기도 하지요.


그래서 전 람다를 이용한 꼼수를 이용해서 코드를 옮길때 자주 써먹고 있습니다.
바로 아래와 같은 객체를 사용 해서요


class Finalizer
{
public:
    Finalizer()
    {

    }
    Finalizer(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
    ~Finalizer()
    {
        if (fPtr != nullptr) fPtr();
    }
    void SetFunction(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
private:    
    std::function<void(void)> fPtr;
};


소멸자에서 function 을 호출해 주느 매우 단순한 구조입니다.
이걸 어떻게 쓰냐면 


void test1()
{
    char* arr = new char[100];
    Finalizer final([&]
    {
        delete[] arr;
        wcout << L"Test1 : Delete Arr" << endl;
    });

    //do Something
    wcout << L"Test1 Code End" << endl;
    
    return;
}


이렇게 씁니다.

저 Finalizer 의 생성자로 lambda 를 넘겨서 finally 블록으로 삼는거죠.
그럼 포커스를 잃는순간 호출 되면서 실행 되게 됩니다.

final1.png


그럼 exception 이 발생 했을때는 어떨까요?
간단하게 아래와 같은 코드를 생각해 볼 수 있습니다


void test2()
{
    try
    {
        char* arr = new char[100];
        Finalizer final([&]
        {
            delete[] arr;
            wcout << L"Test2 : Delete Arr" << endl;
        });
        //do Something
        wcout << L"Test2 : Code End" << endl;
        
        throw L"Exception";
        
        return;        
    }
    catch (...)
    {
        wcout << L"Test2 : Exception" << endl;
    }    
}


하지만 이 코드는 문제가 하나 있는데, 코드 흐름이 다르다는겁니다

final2.png


블록을 빠져나오는것이 catch 로 넘어가는것보다 우선이기 때문에 finally 로 사용하려 했던 코드가 예외처리 코드보다 먼저 실행되어 버립니다.

이걸 해결하는건, C++은 블럭을 자유롭게 구성할 수 있다는걸 이용해서.. 바깥에 블록을 하나 더 만들어서 해결합니다




void test3()
{
    {
        Finalizer final;
        try
        {
            char* arr = new char[100];
            final.SetFunction([&]
            {
                delete[] arr;
                wcout << L"Test3 : Delete Arr" << endl;
            });
            //do Something
            wcout << L"Test3 : Code End" << endl;

            throw L"Exception";

            return;
        }
        catch (...)
        {
            wcout << L"Test3 : Exception" << endl;
        }
    }
}


위 코드는 어차피 저 코드밖에 없기 때문에 추가적인 블록이 필요 없긴 하지만... 일반적으론 그런 코드는 많지 않겠지요


final3.png


실행결과는 다음과 같습니다.
finally 블록이 다른 언어에서의 코드와 다르게 맨 뒤가 아닌 중간에 와야 하는게 맘에 들진 않는데...
대신 다른 언어에서 try 블록 내부에 선언된 변수를 finally 에서 쓸 수 없어 변수만 위쪽으로 옮기거나 하는 단점이 사라져 좋아진 것도 있습니다.


뭐 어차피 급하게 쓸때 쓰는 "꼼수" 라서요


PS. 테스트는 메모리 해제 코드로 했는데..... 메모리는 그냥 unique_ptr 이나 shared_ptr 계열 쓰세요 (...)

PS2. 더 좋은 방법 있으시면 좀 알려주세요.


아래는 전체 테스트 코드입니다

#include <iostream>
#include <functional>

using namespace std;

class Finalizer
{
public:
    Finalizer()
    {

    }
    Finalizer(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
    ~Finalizer()
    {
        if (fPtr != nullptr) fPtr();
    }
    void SetFunction(std::function<void(void)> fPtr)
    {
        this->fPtr = fPtr;
    }
private:    
    std::function<void(void)> fPtr;
};


void test1()
{
    char* arr = new char[100];
    Finalizer final([&]
    {
        delete[] arr;
        wcout << L"Test1 : Delete Arr" << endl;
    });

    //do Something
    wcout << L"Test1 Code End" << endl;
    
    return;
}

void test2()
{
    try
    {
        char* arr = new char[100];
        Finalizer final([&]
        {
            delete[] arr;
            wcout << L"Test2 : Delete Arr" << endl;
        });
        //do Something
        wcout << L"Test2 : Code End" << endl;
        
        throw L"Exception";
        
        return;        
    }
    catch (...)
    {
        wcout << L"Test2 : Exception" << endl;
    }    
}

void test3()
{
    {
        Finalizer final;
        try
        {
            char* arr = new char[100];
            final.SetFunction([&]
            {
                delete[] arr;
                wcout << L"Test3 : Delete Arr" << endl;
            });
            //do Something
            wcout << L"Test3 : Code End" << endl;

            throw L"Exception";

            return;
        }
        catch (...)
        {
            wcout << L"Test3 : Exception" << endl;
        }
    }
}

void wmain()
{
    test1();
    wcout << endl;
    test2();
    wcout << endl;
    test3();
}
TAG •
?

Lyn
조회 수 22676 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

정성태님의 홈페이지를 정독하던중 오랫만에 C++ 관련 글이 하나 올라 왔길래 확인해 보았습니다.
바로 g++에서 inner 클래스를 사용한 템플릿이 제대로 선언 되지 않는다는 문제인데요, 코드는 아래와 같습니다(조금 다릅니다. 원본에 오타가 있어서 좀 고쳣습니다)


원본 : http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0&detail=1&wid=1791


#include <iostream>
#include <vector>
#include <map>

typedef char BYTE;
typedef int _int;
typedef std::vector<BYTE> _byteArrayHolder;
typedef long _long;

template <class T, class V>
class KeepAliveReceiver
{
};

template<class Receiver, class ErrorAction, _int MAX_CONTENT_SIZE = 8192>
class MultiPartByteArrayReceiver : public KeepAliveReceiver< MultiPartByteArrayReceiver<Receiver, ErrorAction>, ErrorAction>
{
	class MultiPartByteArray
	{
	public:
		_int key;
		std::vector<BYTE> byteHolders;
		_long totalSize;

	public:
		MultiPartByteArray(_int _key) : key(_key), byteHolders(), totalSize(0)
		{
		}

		void AddBytes(_byteArrayHolder bytes)
		{
			totalSize += bytes.size();
		}
	};

	typedef KeepAliveReceiver< MultiPartByteArrayReceiver<Receiver, ErrorAction, MAX_CONTENT_SIZE>, ErrorAction> Super;
	typedef MultiPartByteArrayReceiver<Receiver, ErrorAction, MAX_CONTENT_SIZE> This;

public:
	typedef std::map<_int, MultiPartByteArray>  MultiPartBytesMap;
	MultiPartBytesMap receivedMultiPartedBytes;

public:

	MultiPartByteArrayReceiver() : Super(), receivedMultiPartedBytes()
	{}

	void Test()
	{
		MultiPartByteArray a(1);
		std::map<_int, MultiPartByteArray>::iterator iter = receivedMultiPartedBytes.find(1); //컴파일 오류
	}
};

int main()
{
	std::cout << "Hello World" << std::endl;

	MultiPartByteArrayReceiver<int, int> t;
	t.Test();

	return 0;
}


바로 위와 같은 코드인데, 이는 template 이 완전히 생성 되기 전에, 그 템플릿에 의존하는 type을 인식할 수 없기 때문에 발생합니다
(이런 경우는 템플릿 클래스가 부모 템플릿의 함수를 호출한다거나 할때도 발생합니다)

VC++은 그냥 적당히 알아서 (...) 인식해서 넘어 가는데 이걸 오류로 처리하는 컴파일러도 있습니다. 뭐 만드는사람 취향문제겟지만.


이 문제의 해결법은 바로 type을 인식하지 못하는 것이니 이게 template의 type 명을 나타낸다고 알려주는겁니다.
바로 typename 키워드를 쓰는것인데 그럼 아래와 같게 됩니다



typename std::map<_int, MultiPartByteArray>::iterator iter = receivedMultiPartedBytes.find(1); //컴파일 오류 


이렇게 하면 뒤의 템플릿 선언문을 타입으로 인식하여 정상적으로 컴파일 됩니다. 물론 VC에서도 마찬가지입니다.
또 템플릿을 선언 할 때 처럼 class 키워드를 써도 OK 입니다. 전 typename 키워드가 헤깔리지 않아 선호하는 편이지만요


PS. 사실 이 모든 문법적 지저분함을 해결하는 마법의 키워드가 C++에 있으니...

그냥 auto 쓰면 됩니다 (...) C++11에서 추가된 auto와 decltype 의 타입추론 시스템은 템플릿을 정말 간단하게 만들어주는 축복이지요

TAG •
?

Atachment
첨부 '3'
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

오늘 같은 MS VC++ MVP이신 유영천 님께서 개인 프로젝트로 출시하신 Project D Online(Steam에서 즐기실 수 있습니다)에 재밋는 이슈가 발생하였습니다.

바로 vmovd eax,xmm0 명령어에서 illegal instruction 오류를 내면서 크래시가 난다는건데요, 원인분석의 결과를 한번 공유해 보겠습니다.


인텔에서는 2011년 새로운 SIMD 명령어인 AVX를 지원하는 CPU를 출시 하였고, 당연히 그 이후 각 컴파일러들은 해당 명령어에 대한 지원을 시작하엿고 당연히 현재 최신의 MS C++ 컴파일러인 VC++2013버전에서는 2011년에 출시된 AVX 명령어를 지원 하고 있습니다.


AVX를 지원하는 CPU는 아래와 같습니다. 출처는 영문판 위키입니다

원본 : http://en.wikipedia.org/wiki/Advanced_Vector_Extensions

 


인텔은 샌디브릿지 이상, AMD는 불도저 이상이군요. 저도 하스웰-E 가지고싶습니다...


VC++2013의 intrin.h에는 각 어셈명령어와 매칭되는 함수들과 타입이 제공 되고, dvec.h 에는 2개의 클래스 F32vec8과 F64vec4가 제공됩니다.
각각 단정밀도 8개, 배정밀도 4개를 동시에 계산할 수 있는 클래스인데요


극히 간단한 샘플 코드를 돌려 보겠습니다


#include <cstdio>
#include <dvec.h>

int main()
{	
	F32vec8 A(1.1f, 2.1f, 3.1f, 4.1f, 5.1f, 6.1f, 7.1f, 8.1f);
	F32vec8 B(1.1f, 2.1f, 3.1f, 4.1f, 5.1f, 6.1f, 7.1f, 8.1f);		
	
	A += B;

	printf("%f %f %f %f %f %f %f %f", A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7]);
		
	return 0;
}


아래와 같은 경고가 발생하는데, 뭐 일단 여기에선 무시해도 상관 없고, 빌드 옵션에 추가해도 상관 없습니다.
어차피 바이너리엔 영향 없으니까요.


01.png


그럼 위 코드는 어떤 바이너리를 만들어 낼까요?

02.png


대충 이와 같습니다.
v 로 시작하는 명령어들이 AVX명령어 들인데 8개의 float += 연산을 단 한줄로 끝내는것을 볼 수 있습니다.


하지만 세상엔 똥컴(.....) 을 쓰시는분들이 참 많고 당연히 AVX를 지원하지 않는 CPU에서도 프로그램을 돌려야할 경우가 있습니다. 하지만 그렇게 되면 최적화를 상당부분 포기하게 되죠. java나 .net 처럼 JIT 을 지원하는 환경이라면 처음 실행할때 가상머신이 CPU 를 확인해서 최적의 명령어셋으로 만들어 내겠지만(이게 닷넷, 자바의 속도가 간혹 C보다 빨라지는 이유중 하나이기도 합니다) 미리 다 바이너리로 만들어져 있는 Native Application 들은 이런 방법이 불가능 합니다. 

그래서 자주 쓰는 방법은, 미리 CPU 종류별로 다 코드를 컴파일 해 두고, 실행할때 CPU가 지원하는 최적의 명령어를 실행하도록 분기하는거죠... 실제로 극한의 속도를 요하는 라이브러리 (Intel IPP 등) 에서는 당연하다시피 쓰이고 있는 방식이고, 일반적인 라이브러리를 만들때도 쓰이는 경우가 있습니다. 혹시 리눅스에 관심있으신분이라면 최근버전의 리눅스 커널에서 아주 오래된 CPU의 지원이 조금씩 끊기고 있다는것을 아실 수 있는데, 이게 특정 이상의 명령어 셋을 기본적으로 사용하겟다는 의미이기도 합니다.


네 중요한건 바로 여기에 있습니다. VS2013은 내부적 라이브러리에 AVX코드를 생성해서 쓰는 경우가 있다는것이고, cpu의 명령어 셋을 체크 한 후에 쓰기때문에 실행엔 문제가 없어야 합니다만... 현재 명령어 셋을 체크하는것은 cpu만이 아닙니다. 각종 코드 보안 관련 기능에 사용하기 위해 OS도 명령어를 체크하지요.
아래 내용은 역시 영문위키에서 가져왔습니다.... 일일히 찾기귀찮아서


원본 : http://en.wikipedia.org/wiki/Advanced_Vector_Extensions

  • Apple OS X: Support for AVX added in 10.6.8 (Snow Leopard) update[13] released on June 23, 2011.
  • Linux: supported since kernel version 2.6.30,[14] released on June 9, 2009.[15]
  • Windows: supported in Windows 7 SP1 and Windows Server 2008 R2 SP1,[16] Windows 8
  • Windows Server 2008 R2 SP1 with Hyper-V requires a hotfix to support AMD AVX (Opteron 6200 and 4200 series) processors, KB2568088
  • FreeBSD in a patch submitted on 21 January 2012,[17] which was included in the 9.1 stable release[18]
  • DragonFly BSD added support in early 2013.
  • Solaris 10 Update 10 and Solaris 11

  • 리눅스는 굉장히 삐른 편인데 대체 어디서 정보를 얻어서 구현했는진 모르겠습니다(....), 윈도우는 날자가 적혀있지 않아 확인해보니 2011년 3월 15일입니다. 맥은 좀 늦었군요 뭐 어차피 자기들이 하드웨어 내놓을때 같이 내 놓으면 상관없겠습니다. 어차피 OS가 아니라 펌웨어에 가까우니까요


    문제는 여기에서 발생합니다. 바로 CPU는 지원하는데 OS가 지원을 안할 경우입니다 (...)
    위에 써있는것을 보시면 알겠지만 Windows 7은 SP1 이상부터 AVX를 지원합니다. 즉 SP1이 설치되지 않은 구버전의 Windows 7 이라면 AVX명령어가 실행이 되지 않는다는 겁니다....


    그래서 MS는 VC++2013 재배포 패키지( http://www.microsoft.com/en-us/download/details.aspx?id=40784 )  에 다음과 같이 명시 해 두었습니다.

    Supported Operating System

    Windows 7 Service Pack 1, Windows 8, Windows 8.1, Windows Server 2003, Windows Server 2008 R2 SP1, Windows Server 2008 Service Pack 2, Windows Server 2012, Windows Server 2012 R2, Windows Vista Service Pack 2, Windows XP 


    네 분명히 Windows7 SP1 이라고 적혀 있지요.
    문제는... 저게 SP1이 아닌데도 설치가 된다는겁니다 (....)


    03.png



    그냥 재배포 패키지설치시 확인 해서 올리라고 했었어야 할거 같은데... 어쨋든 안해줍니다 그래서 만약을 대비해 버전 체크를 하시는게 좋습니다.
    빌드넘버로 확인할 수 있는 Windows 7은 6.1.7600, Windows7 SP1은 6.1.7601 로 다르니까요.

    안그러면 이런 크래시가 발생할 수 있습니다.


    사실 AVX 같은 확장명령어로 최적화 될 상황이 그리 많지는 않습니다... 저런걸 쓸 상황이 오는건 진짜 대량의 계산을 요하는 프로그램이나, 영상관련 프로그램, 아니면 게임정도니까요.
    그래도 만약을 대비해서 나쁠건 없겟죠.


    그리고 서비스팩이 설치되지 않은 7600버전의 Windows 7은 더이상 어떤 패치도 제공 되지 않는 상황입니다(Sp1을 올려야 나머지 패치가 적용 됩니다) 그러니까 웬만하면 패치좀 하고 씁시다 (...)

    PS. Project D Online 많이좀 해주세요.

    TAG •
    ?

    Lyn
    조회 수 207773 추천 수 0 댓글 0
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    주의 : 본 글은 거의 2년전 시점에 씌여진 글을 깜빡하고 안올리고 (...) 있던걸 꺼낸라 현재의 boost 최신버전하고는 상황이 다를 수 있습니다.


    boost::interprocess::managed_shared_memory(이하 msm)에 관하여.

     

    msm는 내부적으로 Red-Black Tree 를 이용하여 구현되어 있다

    서로 다른 프로세스에서 공유 가능한 std::map<string, void*> 라고 보면 간단하게 이해된다.

    공유메모리를 map의 형태로 기능확장한 것.

     

    msm 는 생성시 mutex를 가지게 됨. Msm rb-tree를 구성하는 boost::rbtree_best_fit boost::interprocess::interprocess_mutex 를 상속받은 header_t 구조체를 소유하고 있음.

    header_t 구조체는 추가적으로 msm의 헤더를 관리하기위한 멤버를 소유하고 있고, 이것들은 메모리 세그먼트를 관리할때 자동으로 scoped lock 이 걸림

     

    msm 생성시 POXIS에서는 shm_open 를 이용하고윈도우에서는 CreateFile를 이용한다

     msm 생성 과정에서는 OS에 의해서 보호된다여기서 의문인 것은 왜 CreateFileMapping 을 이용한 Windows OS의 공유메모리 메커니즘을 사용 하지 않았는가 이다.

    물론 FILE_ATTRIBUTE_TEMPORARY 플래그를 주어 파일을 메모리에 생성 하긴 했지만 그다지 효율적으로 보이지는 않는다궂이 예측 하자면 CreateFileMapping이 생성시의 이름을 어떻게 주느냐에 따라  세션관련 제어 기능을 가지게 되는데 그것을 의도적으로 피하고자 함이 아닌가 싶다

    boost의 기본 설계 방침은 “플랫폼 특화” 된 기능을 막아서라도 같은 인터페이스를 제공 하는 것이라고 하니까아니면 그냥 단순히 POSIX랑 똑같이 하고 싶어서 일수도 있고.

     

    msm는 윈도우에서는 추가적인 과정이 있는데만약 공유위반으로 인해 생성이 실패 할 경우 250ms 간격으로 최대 3번까지 재시도 하게 된다

    이는 MSDN 에 명시된 동작으로 실제 사용 하지 않더라도 OS의 백그라운드 조각모음 때문에 일시적으로 파일이 잠길 수 있다고 하고 있다또한 FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE 권한이 있다 하더라도 파일 생성 중에는 lock이 걸려 공유위반이 발생 할 수 있다단 이것은 임시파일이 아닐 경우인데실제 테스트 결과 임시파일에서는 아무리 빠르게 반복해도 관련 현상을 재현할 수 없었다.

     

    어쨋든 Windows, POSIX 양쪽 다 OS 차원에서 “파일 생성” 이라는 부분은 완벽히 동기화 시켜주기 때문에 절대로 “같은 이름을 가진 msm 객체가 생성 될 수 없다” 또한 open_or_create를 사용 하여 생성 할 경우 반드시 성공한다는 것을 보장 하게 된다.

     

    msm 의 읽기,쓰기 동작(find, construct )도 일부러 nolock 버전의 함수를 호출하지 않는 이상 thread safe 하다 find → add 의 동작을 반복 할 필요가 없다.

     

    construct 는 이미 존재하는 Key 대해 요청 할 경우 exception 을 발생 시킨다.

    이것의 예외 처리는 매우 중요한데만약 한쪽에서 construct 가 중복 실행되어 exception 이 발생 될 경우 예외처리를 제대로 하지 않으면 msm 내부의 lock이 풀리지 않아 무한히 대기 하게 된다. Windows , POSIX 양쪽 다 프로세스가 Crash 되어 종료된 상황에서도 lock 이 해제되지 않는것이 확인되었다이런 경우를 방지하기 위해 find_or_construct 를 제공 하므로 이쪽을 사용 하는 것이 바람직하다.

     

    위의 내용에 덧붙이면 사실 윈도우에서 제공하는 모든 동기화 객체는 lock의 재진입을 지원하고 프로세스 레벨의 레퍼런스 카운팅을 제공하여 프로세스가 죽으면 그 프로세스가 획득한 모든 Lock이 해제되도록 되어있다문제는 boost의 구현방법에 있는데 posix 에서는 pthread mutex를 사용하는것이 기본인 반면 Windows 시스템에서는boost가 자체 구현한 spin_mutex를 사용하는것이 default로 되어 있다그런데 이녀석은 커널 레벨에서 시스템 전체를 관리하는게 아니라 프로세스 레벨에서 atomic operation을 이용해 임의로 구현한 기능이므로프로세스가 Exception에 의해 Crash 되었을 경우의 뒷처리를 전혀 할 수 없다 Windows 시스템의 안정적인 관리 기능을 사용하려면 boost::interprocess::windows_mutex를 명시적으로 선언해야 한다이는 공유메모리도 마찬가지로기본적으로는 위에 적은것 처럼 CreateFile 을 사용 하고 있지만 boost::interprocess::windows_shared_memory 를 명시적으로 사용하면 CreateFileMapping를 이용하는 공유메모리가 생성되며 Windows에서 제공하는 기능 전체를 사용할 수 있다.

     

    TAG •
    ?

    Lyn
    조회 수 23253 추천 수 0 댓글 0
    Atachment
    첨부 '1'
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    전 직접 빌드해서 써서 별로 쓴일이 없긴 했는데..

    문닫는거 보니 가슴이 좀 아프네요


    boostpro.png

    TAG •
    ?

    Lyn
    조회 수 15337 추천 수 0 댓글 3
    Atachment
    첨부 '3'
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    이 코드는 Visual C++ 2013 RC에서 테스트 되었습니다.


    C#을 자주 쓰시는 분들이라면 아시겠지만... C# 에는 @ 로 시작하는 문자열 리터럴이 있습니다.
    저 안에서는 줄바꿈도 자유롭게 허용되고 Escape Sequence 할 필요도 없이 다양한 문자열을 쓸 수 있지요.
    저같은 경우는 특히 쿼리를 날릴때 매우 유용하게 쓰고 있습니다.


    csharpa.png


    이런식으로요.


    이게 C++ 할때도 참 부러웠는데... VC++ 2013 에서 드디어 사용 가능하게 되었습니다.
    바로 R 접두사를 이용해서요..... 정확히는 괄호도 필요하지만


    간단한 예제 만들어 보겠습니다.

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    void wmain()
    {
    	string s = R"(Hello World
    동해물과 백두산이 마르고 닳도록
    \r\n\t
    \가 안먹어!!
    "따옴표도 그냥 써져!")";
    
    	cout << s << endl;
    }


    의 실행 결과는 다음과 같습니다.


    11.png


    오! 멋지게 출력 되네요. (" 와 ") 가 문자열의 범위를 지정하는 태그로 쓰이는겁니다
    하지만 여기 함정이 있습니다...

    바로 )" 를 출력할 수 없단겁니다 (.....)

    왜냐면 종료 태그로 쓰기 때문이죠.


    즉 

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    void wmain()
    {
    	string s = R"("(Test)")";
    
    	cout << s << endl;
    }


    이런 코드는 오류라는 겁니다....

    하지만 근성의 C++ 위원회 (...) 이번에도 그냥 넘어가지 않습니다. 
    C++ 에서는 이것을 "시작 토큰 커스터마이징" 기능을 넣음으로서 해결했습니다...

    이게 뭐냐면,  Raw String Literal 의 첫 ( 를 만날때 까지의 문자열을 "태그" 취급 합니다.

    즉 아래와 같은 코드가 가능해집니다.


    #include <iostream>
    #include <string>
    
    using namespace std;
    
    void wmain()
    {
    	string s = R"Lyn("(Test)")Lyn";
    
    	cout << s << endl;
    }


    RSL 의 첫 (를 만날때 까지 Lyn 이라는 문자열을 추가로 만났습니다.
    그럼 위 리터럴의 시작을 나타내는 문자는 Lyn(, 종료를 나타내는 문자는 )Lyn 이 되는겁니다.
    실행해보면 아래와 같습니다.


    22.png


    이로서 알아보긴 더럽게 힘들지만 (...) 맘껏 문자열을 만들수 있게 되었습니다.
    아직 실제로 활용해보진 못햇지만, R"SQL(INSERT INTO TABLE VALUES(1, 2, 3))SQL" 처럼 이 문자열은 SQL 이다! 라는 것 처럼 표시하는식으로 사용해 볼 수 있을 것 같네요.


    마지막으로 한가지... 유니코드 리터럴은 어떻게 하냐면... LR로 시작하면 끝입니다 (...) 간단하죠

    TAG •
    ?
    • profile
      Lyn 2013.10.07 09:01
      아직 거의 쓰이지 않는 문법이라 그런지 syntax highlighter 가 제대로 컬러링을 못해주네요 =_= 젠장
    • ?
      사무엘 2013.10.07 18:57
      아아 이건 좀 내가 아는 C++과는 다른 사기적인 기능 같습니다... ㅎㅎ
      인텔리센스용 컴파일러, 코드 생성용 컴파일러, 신택스 하이라이팅용 파서 만드는 사람들.. 다 바빠지겠네요.
    • profile
      Lyn 2013.10.12 09:10
      간단하게 쓸수있는 문법이니 쓸모가 많을거 같습니다 ㅎㅎ.

    Lyn
    조회 수 21749 추천 수 0 댓글 0
    Atachment
    첨부 '5'
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    이 코드는 Visual Studio 2012 Update3에서 테스트 되었습니다.


    boost 1.46 부터 추가된 icl 컨테이너들은 범위를 가지는 데이터들을 넣을 수 있게 되어 있는 컨테이너다


    이것들은 주로 일정 등의 데이터를 관리하기에 아주 좋게 되어 있고(boost 예제도 대부분 그런쪽입니다) 매우 확장성이 좋게 설계 되어 있어 다양한 용도로 활용 가능하지만 개인적으론 map의 기본형인 boost::icl::intervap_map<T1, T2> 의 형태가 매우 비상식적이라고 생각한다.


    그래서 interval_map 을 원하는 형태로 만들어서 쓰고 싶었고 그 과정에서 알게된 것을 정리 해 본다.

    내가 최종적으로 원하는 형태는 프로그램이 처음 시작할때 로딩되는 범위를 가지는 데이터를 관리 하고, 중복되는 경우가 있을경우 데이터 오류로 취급하여 예외를 발생 하는 형태입니다.


    하나씩 알아보자면 interval_map 의 기본 개념은 기존의 std::map 과 같지만, 몇가지 추가적인 개념이 존재한다.

    1. 구간.

    구간이란 0~1, 2~3, 12:00~13:00 처럼 범위를 가지는 데이터. 수학시간에 배웠던것처럼 개구간, 폐구간, 반폐구간(좌/우) 가 존재합니다.
    뭐 이건 문제될 것 없는 당연한 경우라고 보입니다.


    2. Combine

    입력한 구간이 서로 겹칠 경우 그 구간에 대해 Combine 동작을 시행합니다.
    이것에 대해선 아래에서 마저 합니다.


    3. 기본값

    웃기게도 왠지 모르겠지만 (...) 기본값에 대한 처리를 변경할 수 있습니다.

    여기에서 기본값이란 static T; 로 선언 했을때의 초기값입니다. int라면 0, float 라면 0.0f, std::string 이라면 길이0인 문자열인 식입니다.


    1번 구간에 대해서는 별로 문제가 될 경우가 없을겁니다. 상식적으로 사용 할 수 있지요.
    계속 울궈먹을 기본 코드 하나 넣겠습니다


    #include <boost/icl/interval_map.hpp>
    #include <string>
    #include <utility>
    #include <cstdio>
    
    void wmain()
    {
    	using namespace std;
    	using namespace boost;
    	using namespace boost::icl;
    
    	interval_map<int, wstring> ivm;
    
    	ivm.add(make_pair(interval<int>::right_open(0, 2000), L"Right"));
    	ivm.add(make_pair(interval<int>::left_open(2000, 3000), L"Left"));
    	ivm.add(make_pair(interval<int>::closed(3000, 4000), L"closed"));
    	ivm.add(make_pair(interval<int>::open(4000, 5000), L"Open"));
    
    	auto it = ivm.find(1000);
    	if(it != ivm.end())
    	{
    		wprintf(L"Find %s\n", it->second.c_str());
    	}
    	else
    	{
    		wprintf(L"Not Found");
    	}
    }


    기본적인 사용 방법은 위와 같습니다. 구간을 지정해서 데이터를 넣고 find로 찾아오는형식으로 map과 완전히 동일합니다.

    결과도 별다를게 없구요


    1.png


    위 코드를 자세히 보시면 3000 이라는 값은 left, closed 두개의 데이터가 겹치는 구간이라는 것을 알 수 있습니다.
    이때 3000을 찾아서 출력해 보면 어떤 결과가 일어 나냐면....


    2.png


    이런 상황이 벌어집니다 (...) 
    왜냐면 interval_map은 구간이 겹칠 경우 Combine 작업을 진행 하는데 이 단순히 interval_map<T1,T2> 로 선언한 Combine의  동작이 + 연산입니다.

    즉 겹치는 구간의 데이터를 몽땅 더해서 출력해 버립니다. 숫자라면 값이 더해질것이고 문자열이라면 위처럼 문자열일 경우 두 문자열이 붙어서 나타납니다.

    이 Combine 연산을 미리 몇가지 제공 하고 있는데, 이렇게 더한다던지, stl 컨테이너에 insert 를 한다던지 최대값/최소값으로 교체한던지 하는 기본적으로 많이 쓸만한 연산을 제공 하고 있습니다....만 내가 필요한 문제있을시 예외던지는 Combine은 제공 하지 않습니다.


    두번째로 기본값 문제입니다. 위 코드를 수정해서 아래처럼 바꾼 후 실행해보겠습니다.


    3.png



    [0~2000) 구간에 빈 문자열을 넣었지만 찾을수 없다고 하고 있습니다.

    왜냐면 그 Type의 기본값이 들어갈 경우 데이터를 넣지 않는것이 interval_map 기본형의 동작입니다


    하지만 이 상황에서 내가 원한건 데이터 없음과, 빈 문자열을 데이터로 가지는것을 확실하게 구별 해야 하는겁니다.

    값이 빈값인건 빈값이고,  없는건 없다고 할수 있어야되는 상황이더 많다고 생각되는데.... 왜 이런지는 모르겠습니다.


    이 동작을 바꾸는 기능을 icl 은 제공 하고 있는데 두가지 옵션을 하나의 구조체로 만들어 총 4종류의 형태를 제공합니다.
    partial_absorber, partial_enricher, total_absorber, total_enricher 의 4가지입니다.

    여기서 뒤에 붙은 absorber, enricher 이 기본값을 어떻게 처리할지 결정합니다.
    absorber는 날려버리고(초기형태) enricher 는 보존합니다. partial_enricher 로 바꾼 뒤 다시 한번 실행해 보겠습니다.


    4.png


    보다시피 찾아오는것에 성공하는 것을 볼 수 있습니다.

    다음으로 앞에 붙은 partial, total 은.... 당췌 이게 어떻게 돌아가는지 모르겠습니다 =_=;;;;; 뭘로 하던 내가 원하는 동작에 지장이 없었기에 넘어갔습니다.
    차후 추가적인 확인이 필요할듯 합니다. 아마 사용자 정의 type 에서만 영향을 미치는지도 모르겠습니다. 기본형은 이미 다 되어있고 ...


    그리고 이제 combine 을 방지해야 하는데... combine functor 를 새로 만듬으로서 가능했습니다. 
    겸사겸사 실수로 substract 를 호출하는것을 방지하기 위해 inverse functor 도 만들었습니다. 어차피 오류낼거니 궂이 따로 만들 필요는 없어서 그냥 같이 쓰도록 되었네요.


    5.png


    그 결과가 이 코드입니다. 중간에 less는 std::less 입니다. 비교 functor 가 템플릿에서 앞에 있는 바람에 저렇게 들어가버렷네요.

    어쨋든 멋지게 assert 를 내 주네요. 원하는 동작이 만들어 졋습니다. 구간별 데이터를 안전하게 저장 할때 사용할수 있겠습니다.
    궂이 사용 예를 추가해 보자면 ip 별 지역 정보를 넣어놓고 빠르게 검색할때 사용할 수 있겠네요.

    TAG •
    ?

    Lyn
    조회 수 25158 추천 수 0 댓글 0
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    두번째 방법으로는 CRT의 예외 핸들러에서 UnhandledExceptionFilter로 데이터를 넘겨서 생성하는 방법인데요...
    이는 안정적이고 정석적이지만 치명적인 단점으로 CRT에서 던지는 예외가 하나가 아니라는겁니다... 


    어쨋든 코드를 보면


    #include <Windows.h>
    #include <DbgHelp.h>
    #include <cstdio>
    #include <string>
    
    using namespace std;
    
    #pragma comment(lib, "dbghelp.lib")
    
    LONG __stdcall TopLvFilter(PEXCEPTION_POINTERS pExceptionPointer)
    {
    	MINIDUMP_EXCEPTION_INFORMATION MinidumpExceptionInformation;
    	std::wstring DumpFileName = L"dmpfile.dmp";
    
    	MinidumpExceptionInformation.ThreadId = ::GetCurrentThreadId();
    	MinidumpExceptionInformation.ExceptionPointers = pExceptionPointer;
    	MinidumpExceptionInformation.ClientPointers = FALSE;
    
    	if (DumpFileName.empty() == true)
    	{
    		::TerminateProcess(::GetCurrentProcess(), 0);
    	}
    
    	HANDLE hDumpFile = ::CreateFile(DumpFileName.c_str(),
    		GENERIC_WRITE,
    		FILE_SHARE_WRITE,
    		nullptr,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL, nullptr);
    
    	MiniDumpWriteDump(GetCurrentProcess(),
    		GetCurrentProcessId(),
    		hDumpFile,
    		MiniDumpNormal,
    		&MinidumpExceptionInformation,
    		nullptr,
    		nullptr);
    
    	::TerminateProcess(::GetCurrentProcess(), 0);
    
    	return 0;
    }
    void InvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved)
    {
    	EXCEPTION_POINTERS ExceptionPointer;
    	EXCEPTION_RECORD ExceptionRecord;
    	_CONTEXT ContextRecord;
    
    	ZeroMemory(&ContextRecord, sizeof(ContextRecord));
    	RtlCaptureContext(&ContextRecord);
    
    	ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
    	ExceptionRecord.ExceptionCode = STATUS_INVALID_CRUNTIME_PARAMETER;	
    	ExceptionRecord.ExceptionAddress = (PVOID)ContextRecord.Eip;
    		
    	ExceptionPointer.ExceptionRecord = &ExceptionRecord;
    	ExceptionPointer.ContextRecord = &ContextRecord;
    
    	TopLvFilter(&ExceptionPointer);
    }
    void main()
    {
    	SetUnhandledExceptionFilter(TopLvFilter);
    	_set_invalid_parameter_handler(InvalidParameterHandler);
    	atoi(nullptr);	 
    }



    구조는 간단합니다. RtlCaptureContext 를 이용해서 현재 쓰래드의 Context를 캡쳐 해 오고, 익셉션 코드와 현재 코드가 실행중인 위치를 넘겨 주면 되는데요..
    x86 시스템에서 현재 실행중인 위치는 Eip 레지스터에 있기 때문에 캡쳐해온 Context에서 Eip를 
    ExceptionAddress로 넘겨 주면 됩니다.


    이 방식은 안정적이고 백신이 오진을 할 가능성도 없고 Windows 8 에서도 아무 문제 없이 작동 하지만 ... 위에서 말한것처럼 예외가 이거 하나가 아니라는게 문제가 됩니다.
    예를들어 메모리 할당 예외는 _set_invalid_parameter_handler 로 처리 할 수 없습니다. 하지만 어차피 몇개 안되기도 하고 (...) 그냥 몇개 더 만들어주면 될 문제지요.


    정말 골때려 지는건 3rd party 에서 SetUnhandledExceptionFilter 를 호출해서 자기껄로 만들어버리는 라이브러리가 있다는겁니다 =_=; 적은 내안에만 있는게 아닌거죠...그래서 첫번째 방법과 두번째 방법을 모두 적용해 두는게 좋다고 생각 합니다


    TAG •
    ?

    Lyn
    조회 수 32459 추천 수 0 댓글 0
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄

    사후 디버깅을 위해 가장 많이 쓰이는 방법중 하나가 메모리 덤프를 남기는 방법입니다.

    보통 아래와 같은 형태로 구현 하게 되는데요. 과연 이 프로그램은 덤프가 남을까요?


    #include <Windows.h>
    #include <DbgHelp.h>
    #include <cstdio>
    #include <string>
    
    
    using namespace std;
    
    #pragma comment(lib, "dbghelp.lib")
    
    LONG __stdcall TopLvFilter(PEXCEPTION_POINTERS pExceptionPointer)
    {
    	MINIDUMP_EXCEPTION_INFORMATION MinidumpExceptionInformation;
    	std::wstring DumpFileName = L"dmpfile.dmp";
    
    	MinidumpExceptionInformation.ThreadId = ::GetCurrentThreadId();
    	MinidumpExceptionInformation.ExceptionPointers = pExceptionPointer;
    	MinidumpExceptionInformation.ClientPointers = FALSE;
    
    	if (DumpFileName.empty() == true)
    	{
    		::TerminateProcess(::GetCurrentProcess(), 0);
    	}
    
    	HANDLE hDumpFile = ::CreateFile(DumpFileName.c_str(),
    		GENERIC_WRITE,
    		FILE_SHARE_WRITE,
    		nullptr,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL, nullptr);
    
    	MiniDumpWriteDump(GetCurrentProcess(),
    		GetCurrentProcessId(),
    		hDumpFile,
    		MiniDumpNormal,
    		&MinidumpExceptionInformation,
    		nullptr,
    		nullptr);
    
    	::TerminateProcess(::GetCurrentProcess(), 0);
    
    	return 0;
    }
    
    void main()
    {
    	SetUnhandledExceptionFilter(TopLvFilter);	
    	atoi(nullptr);
    }
    


    네. 결론부터 말하자면 위 코드에서는 덤프가 남지 않습니다.
    왜냐면 CRT 에서 에러가 발생하면 UnhandledExceptionFilter 를 CRT가 자신의 예외 핸들러로 다시 세팅 하기 때문입니다.

    그러나 우리가 바라는건 별로 친절하지도 않은 (...) CRT의 에러메세지가 아니라 메모리 덤프지요.

    우리가 원하는대로 덤프를 남기는 방법은 여러 가지가 있지만 그 중 한가지로 해결 해 보겠습니다.

    이번에 사용 할 방법은 코드를 패치해서 CRT가 감히 내가 만든 예외처리기를 바꾸지 못하도록 (...) 하는 방법입니다



    #include <Windows.h>
    #include <DbgHelp.h>
    #include <cstdio>
    #include <string>
    
    using namespace std;
    
    #pragma comment(lib, "dbghelp.lib")
    
    BYTE HotPatchingBackup[5];
    
    LPTOP_LEVEL_EXCEPTION_FILTER WINAPI NullSetUnhandledExceptionFilter(
      _In_  LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
    )
    {
        return nullptr;
    }
    
    void InstallAPIHook_SetUnhandledExceptionFilter()
    {
        BYTE *pSetUnhandledExceptionFilter;
    
        DWORD ProtectOption = 0;
        DWORD Temp = 0;
    
        pSetUnhandledExceptionFilter = (BYTE*)&SetUnhandledExceptionFilter;
    
    	HANDLE hSelfProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE, ::GetCurrentProcessId());
    	VirtualProtectEx(hSelfProcess, (void *)pSetUnhandledExceptionFilter, 5, PAGE_EXECUTE_WRITECOPY, &ProtectOption);     	
    
        memcpy(HotPatchingBackup, pSetUnhandledExceptionFilter, 5);
    
        *pSetUnhandledExceptionFilter = 0xE9;
    	pSetUnhandledExceptionFilter++;
        *((DWORD *)(pSetUnhandledExceptionFilter)) = ((DWORD)NullSetUnhandledExceptionFilter) - ((DWORD)pSetUnhandledExceptionFilter + 4);	
    	pSetUnhandledExceptionFilter--;
    
    	VirtualProtectEx(hSelfProcess, (void *)pSetUnhandledExceptionFilter, 5, ProtectOption, &Temp);
    
    	CloseHandle(hSelfProcess);
    }
    
    void UnInstallAPIHook_SetUnhandledExceptionFilter()
    {
        BYTE *pSetUnhandledExceptionFilter;
    
        DWORD ProtectOption;
        DWORD Temp;
    
        pSetUnhandledExceptionFilter = (BYTE*)&SetUnhandledExceptionFilter;
    	
    	HANDLE hSelfProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE, ::GetCurrentProcessId());
    	VirtualProtectEx(hSelfProcess, (void *)pSetUnhandledExceptionFilter, 5, PAGE_EXECUTE_READWRITE, &ProtectOption);    
    
        memcpy(pSetUnhandledExceptionFilter, HotPatchingBackup, 5);
    
    	VirtualProtectEx(hSelfProcess, (void *)pSetUnhandledExceptionFilter, 5, ProtectOption, &Temp);     
    
    	CloseHandle(hSelfProcess);
    }
    
    LONG __stdcall TopLvFilter(PEXCEPTION_POINTERS pExceptionPointer)
    {
        MINIDUMP_EXCEPTION_INFORMATION MinidumpExceptionInformation;
        std::wstring DumpFileName = L"dmpfile.dmp";
    
        MinidumpExceptionInformation.ThreadId = ::GetCurrentThreadId();
        MinidumpExceptionInformation.ExceptionPointers = pExceptionPointer;
        MinidumpExceptionInformation.ClientPointers = FALSE;
    
        if (DumpFileName.empty() == true)
        {
            ::TerminateProcess(::GetCurrentProcess(), 0);
        }
    
        HANDLE hDumpFile = ::CreateFile(DumpFileName.c_str(),
            GENERIC_WRITE,
            FILE_SHARE_WRITE,
            nullptr,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL, nullptr);
    
        MiniDumpWriteDump(GetCurrentProcess(),
            GetCurrentProcessId(),
            hDumpFile,
            MiniDumpNormal,
            &MinidumpExceptionInformation,
            nullptr,
            nullptr);
    
        ::TerminateProcess(::GetCurrentProcess(), 0);
    
        return 0;
    }
    
    void main()
    {
        SetUnhandledExceptionFilter(TopLvFilter);
        InstallAPIHook_SetUnhandledExceptionFilter();
        atoi(nullptr);
        UnInstallAPIHook_SetUnhandledExceptionFilter();
    }


    API 훅을 해서 필터를 바꾸려 하면 아무 일도 하지 않는 NullSetUnhandledExceptionFilter 이 호출되도록 하여 막는 방법입니다...
    멋있어 보이기는 하지만 이 코드는 64bit/32bit 를 따로 만들어야 하고, 심지어 윈도우 8 에서는 이 코드가 정상적으로 동작 하지 않습니다 (....) 아마도 강화된 보안때문에 그런게 아닌가 싶은데...

    어쨋든 윈도우7 까지는 정상적으로 덤프가 남습니다.


    그럼 이런 편법 말고 좀 정석적인 방법이 필요한데... 그건 다음글로...

    TAG •
    ?

    Lyn
    조회 수 22964 추천 수 0 댓글 1
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    ?

    단축키

    Prev이전 문서

    Next다음 문서

    크게 작게 위로 아래로 댓글로 가기 인쇄
    http://www.microsoft.com/ko-KR/download/details.aspx?id=38188
     

    사실 온라인 다운로드 버전 받을 때 스크롤 쭉 내려서 읽어 보면 나와 있는데 ...
    온라인 업데이트 프로그램을 받은 후 VS2012.2.exe /layout 처럼 /layout 옵션을 주어 시작하면 설치하지 않고 다운로드만 받습니다.

    다운로드 후 인터넷이 안되는 PC로 복사해 가서 설치 하면 됩니다.
    용량이 한 2G쯤 되니 시간은 좀 걸립니다(본격 원본보다 더 큰 패치)

    ?
    • ?
      아리수 2013.04.15 05:21
      VS2012 설치 하면서 미리 업데이트를 받아 두려고 했더니 설치 중이라는 메시지와 함께 실행이 되지 않더군요...꼼꼼하더군요..ㅋㅋ..
      작업하시는데 참고하세요...^^

    Board Pagination Prev 1 2 Next
    / 2