너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!
이번이 SafeInt 라이브러리에 대한 4번째 글이면서 마지막 글입니다. 이전 회의 제 글을 보셨다면 SafeInt가 어떤 것인지, 어떻게 사용하는지 대부분 알게 되셨습니다. 이번에는 SafeInt의 함수 버전에 대해서 설명합니다.
SafeInt 함수
SafeInt 라이브러리에는
SafeInt 클래스의 인스턴스를 만들지 않고 사용할 수 있도록 몇 개의 함수를 지원하고 있습니다.
SafeInt 함수는 정수 오버플로우가 발생하지 않도록 단일 수치 연산을 보호하고 싶을 때 사용합니다. 그리고 복수의 수치 연산을 보호하고 싶을 때는 SafeInt 클래스를
사용하고 함수 버전들을 반복하여 사용하는 것보다는 SafeInt 클래스를 사용하는 것이 더 효율적이라고
합니다.
<코드 1>의
결과를 보면 예외 처리에 의해서 프로그램이 크래쉬 되지 않음을 알 수 있습니다. 그런데 SafeInt를 사용할 때마다 <코드1> 처럼 매번 예외처리를 구현한다는 것은 너무 불편합니다. 불편하면 SafeInt를 기피하게 되겠죠. -_-
다행히 SafeInt는 이런 것도 다 감안해서 만들어져 있습니다. 사전에 예외처리를 미리 정의 해 놓으면 매번 예외처리를 정의할 필요가 없습니다.
SafeInt의 예외처리 정의는 두 가지 방법이 있습니다. 이번 회는 두 가지 방법 중 첫 번째 방법을 소개하고 두 번째 것은 다음 회에 소개 하겠습니다.
SafeInt의 예외처리 – 방법 1
SafeInt의 기본 예외 처리 클래스를 상속 받아서 우리가
원하는 방식으로 정의한 후 그것을 SafeInt의 생성자에 인자로 넘겨주면 SafeInt로 연산 작업을 할 때 예외가 발생하면 우리가 정의한 예외처리를 호출합니다. 백문이불여일견이라고 바로 다음의 코드를 봐 주세요. 아주 간단합니다.
< 코드 2.
SafeInt의 예외처리 방법 1 >
#include <iostream>
#include <safeint.h>
using namespace msl::utilities;
class MySafeIntException : public
SafeIntException
<코드 2>의
결과를 보면 SafeInt로 연산 작업을 할 때 예외처리를 같이 정의하지 않아도 오버플로우로가 발생하면
우리가 정의한 클래스의 멤버 함수를 호출 합니다.
SafeInt의 생성자에 인자로 넘기는 예외처리 클래스는 꼭
SafeIntException클래스를 상속 받고 static
void SafeIntOnOverflow()와 static void
SafeIntOnDivZero()를 재정의해야 합니다.
이것으로 <코드 1>의 예외처리 방식보다는 좀 편리해졌습니다. 그런데 아마 지금도
마음에 들지 않는 분이 있을 것 같습니다. SafeInt를 생성 할 때마다 매번 예외처리 클래스를 인자로
넘기는 것도 귀찮은 분이 있을 것 같네요. 이런 분들을 위해서
SafeInt는 또 하나더 예외처리 방법을 지원합니다. 그것은 다음 회에서 설명하겠습니다.^^
<코드1>를
실행하면 변수 X3와 BigX3의 값이 서로 같을까요? 혹시 같다고 생각하시는 분들은 unsigned int의 최대 값이
얼마인지 MSDN에서 검색해 보세요... 네 결과는 서로 다릅니다. 둘 다 계산에 사용하는 값은 같지만 결과가 다르게 나오는 이유는 unsigned
int로는 X1과 X2를 곱해서 나온 값을 보관할 수
없기 때문입니다. X3 = X1 * X2에서 X3는 오버플로우가
발생하여 상위 비트가 삭제되어 올바른 계산 값이 저장되지 않습니다.
<코드 1의
결과>
<코드 1>의
코드는 사실 별로 길지 않은 코드이기 때문에 실행하기 전에 오버플로우가 발생하리라는 것을 충분히 예상할 수 있고,
혹은 실행 후에 X3의 값이 예상하지 못한 값이 들어가 있어도 문제를 쉽게 파악할 수 있을
것입니다. 그러나 우리가 만드는 애플리케이션은 복잡하고 긴 코드를 가지고 있습니다. <코드 1>과 같은 오버플로우에 의해서 버그가 발생하면
쉽게 버그를 찾기 힘들고 특히 오버플로우에 의해 애플리케이션이 오 동작하여 크래쉬가 발생할 수도 있습니다.
안전한 애플리케이션을 만들기 위해서는 안전한 코드를 만들어야 합니다. 보통
안전한 코드를 생각하면 포인터 조작과 문자열 조작을 주로 중요하게 다루고 VC++에서도 안전한 문자열
조작 위해 ‘_s’가 붙은 문자열 조작함수를 사용하도록 VC++에서
종용하고 있어서 요즘은 대 부분 이것을 사용하고 있습니다. 그러나 정수 계산에 대해서는 안전한 코드를
위해 지원해 주는 것이 없었습니다.
SafeInt 란?
VC++ 10에서는 안전한 정수 계산을 위해서 새로운 라이브러리를
지원해 줍니다. 이 라이브러리의 이름은 SafeInt입니다. SafeInt는 C++의 템플릿으로 만들어서 char 형에서 __int64 형까지
8비트에서 64비트 사이의 크기를 가진 모든 정수 형을 사용할 수 있습니다.
SafeInt 라이브러리를 사용하면 결과를 담을 변수의 형 보다
큰 정수 값 연산을 하거나 0으로 나누기 연산을 할 때 발생하는 오버플로우를 감지 할 수 있습니다.
SafeInt 사용
SafeInt를 사용하기 위해서는 헤더 파일 safeint.h 를 포함하고 msl::utilities 이름 공간을
선언해야 합니다.
< 코드 2 >
#include <iostream>
#include <safeint.h>
using namespace msl::utilities;
int main()
{
SafeInt<unsigned
int> X1(1234567);
unsigned
int x2 = 1234567;
SafeInt<unsigned
int> X2(x2);
SafeInt<unsigned
int> X3 = X1 * X2;
getchar();
return
0;
}
<코드 2>를
디버그 모드에서 실행하면 아래와 같은 ASSERT 메시지가 발생합니다.
이유는 오버플로우가 발생했기 때문입니다. 그러나 릴리즈 모드에서는 ASSERT 메시지가 발생하지
않습니다. 다만 크래쉬가 발생합니다. -_-;;
오버플로우에 의해서 디버그 모드에서는 ASSERT 메시지, 릴리즈 모드에서는 크래쉬가 발생하는 이유는
SafeInt<unsigned int> X3 = X1 *
X2;
에서 예외가 발생하기 됩니다. SafeInt를 사용하는 경우
오버플로우가 발생하면 예외를 발생시키기 때문에 try{} catch{}로 예외를 처리해 주지 않으면
안됩니다. 예외 처리가 올바르게 하면 오버플로우가 발생했을 때 발생하는 문제를 올바르게 대처하던가 어디에서 어떻게 오버플로우가 발생했는지 쉽게 알 수 있습니다.
그리고 SafeInt는 일반 정수형과 같이 연산을 할 수도 있다.
<코드 3>
#include <iostream>
#include <safeint.h>
using namespace msl::utilities;
int main()
{
SafeInt<unsigned
int> X1(1234567);
unsigned
int X2 = 123;
SafeInt<unsigned
int> X3 = X1 * X2;
getchar();
return
0;
}
이번 회에는 간단하게 정수 연산 시의 오버플로우 문제와 SafeInt가
무엇인지, SafeInt의 간단한 사용 방법만 설명하였습니다.
그리고 이 라이브러리는 서버에서 사용하는 것을 생각했기 때문에 서버-클라이언트 연결과 서버-서버 연결을 가정하여 클라이언트 접속을 위한 Port와 서버 접속을 위한 Port 두 개를 생성합니다.
서버-서버 :: 서버 접속을 위한 Port
UINT16 ServerPort = configReader.GetValue<UINT16>(ACE_TEXT("server_port");
서버-클라이언트 :: 클라이언트 접속을 위한 Port
UINT16 ClientPort = configReader.GetValue<UINT16>(ACE_TEXT("client_port");
저는 제 컴퓨터의 E:\Projects\HalfNetwork 에
압축을 풀어서 안에 ExternalLib 폴더와 HalfNetwork
폴더가 만들어졌습니다.
HalfNetwork 빌더하기
E:\Projects\HalfNetwork\HalfNetwork 폴더에
가면 VS 솔루션 파일이 있습니다. 그 중 VS2010 솔루션인 HalfNetwork_vc10.sln 을 선택합니다.
프로젝트를 열면 ‘HalfNetwork’ 프로젝트를 ‘시작 프로젝트’로 선택합니다.
C++에서만 사용하는 경우라면 그대로 빌드를 하면 됩니다. 그러나 (방법 1)C++/CLI에서
사용하기 위해서는 프로젝트 속성에서 [C/C++] – [코드 생성]에서
‘런타임 라이브러리’ 값을 ‘다중 스레드 DLL’로 바꾸어야 합니다.
C++/CLI에서만 사용하지 않을 생각이라면 구성을 하나 더
만들기를 추천합니다.
(방법 2)저는 기존의
구성에 ‘DebugMDd’(디버그 모드의 경우)라는 것을
만들어서 ‘런타임 라이브러리’ 값을 바꾸었습니다.
그리고 기존 구성에서 만들어진 출력 파일과 이름이 겹치지 않도록 이름도 변경하도록 합니다.
(Debug 모드에서의 대상 이름 속성의 값은 ‘$(ProjectName).x86.debug’였습니다)
그럼 이제 빌드를 합니다. VS2010만 제대로 설치되어 있다면
아무런 문제 없이 빌드가 될 것입니다. C++/CLI에서 사용하기 위해서 약간 수정을 했지만 만약 C++에서만 사용한다면 HalfNetwork는 솔루션 파일을 연 후
‘빌드’를 선택하는 것으로 준비는 끝납니다.
빌드가 끝나면 E:\Projects\HalfNetwork\HalfNetwork\Bin32
폴더에 lib 파일이 만들어져 있습니다.
저는 (방법 2)로 빌드를 했기 때문에 HalfNetwork.x86.debugMDd.lib 라는 파일이 만들어졌습니다.
이것으로 HalfNetwork lib 파일을 만드는 것은 끝났습니다.
다음 회에서는 이번에 만든 lib 파일을 사용하여 닷넷용 클래스 라이브러리를 만들어 보겠습니다.
저도 글을 따라 구성을 만들고 기본 설정은 디버깅을 기준으로 복사해와서 debugMDd로 이름을 바꾸고 라이브러리를 /MDd로 바꿔줬지만, Bin32에는 HalfNetwork.x86.debug.lib뿐이 안 생깁니다. 대신 Debug로 빌드하면 그림처럼 44MB, DubugMDd 빌드하면 40MB입니다. 영문판 VS2010 SP1에서 발생
저랑 같은 문제가 발생하시는 분은 위에 그림이랑 비교해서 용량이 같으면 파일이름만 HalfNetwork.x86.debugMDd.lib로 바꿔서 사용하세요.
위 코드 중 #pragma unmanaged 지시어 이하는 컴파일러에서
비관리코드로 취급합니다. 그리고 #pragma managed 이하는
관리코드로 취급합니다. 내용이 간단하고 어려운 부분이 없기 때문에 따로 자세한 설명은 생략하겠습니다.
C++/CLI는 단순하게 닷넷 플랫폼에서 사용할 수 있는 C++ 언어라기 보다는 C++ 언어의 부족한 부분을 진화 시킨 언어라고도
생각할 수 있는 부분이 꽤 있습니다. 그러나 C++/CLI는 C++과 C#의 중간의 애매한
위치에 있어서 양쪽 프로그래머 모두에게 별로 호응을 받지 못하는 것 같습니다. 그래서 C++/CLI 관련 글을 제가 처음에 생각했던 것보다는 조금 일찍 끝낼려고 합니다.
C++/CLI를 사용하는 대부분의 프로그래머들은 아마 기존의
비관리 코드를 관리코드에서 사용하고 싶을 때라고 생각합니다. C++/CLI의 기능 소개는 이번으로 일단 끝내고 앞으로는 비관리코드와의 연계에 대해서 실제 사례 보여주면서 설명하려고 합니다.
사례는 오픈 소스 네트워크 라이브러리인 HalfNetwork를
C++/CLI를 사용하여 관리코드에서 사용할 수 있도록 wrapping한
후 이것을 관리코드에서 사용할 예정입니다.
HalfNetwork는 온라인 게임 서버 프로그래머인 임영기님이
만든 것으로 ACE 라는 오픈 소스 네트워크 라이브러리를 사용하기 편하게 만든 라이브러리입니다.
C++/CLI은 네이티브
C++과 다르게 자료구조 배열을 사용하기 위해서는 array 컨테이너를 사용합니다.
array 컨테이너는 기본적으로
non-CLI 오브젝트는 사용할 수가 없습니다. 그러나non-CLI 오브젝트가 포인터라면 사용할 수 있습니다.
아래는 array 컨테이너에 non-CLI 오브젝트를 사용한 경우입니다.
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative>^
arr = gcnew array<CNative>(2);
return 0;
}
빌드하면 위에 이야기 했듯이 아래와 같은 빌드 에러가 발생합니다.
그럼 이번에는 non-CLI 오브젝트의 포인터를 사용해 보겠습니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative*>^
arr = gcnew array<CNative*>(2);
for(int
i=0; i<arr->Length; i++)
{
arr[i]
= new CNative();
}
getchar();
return 0;
}
이번에는 빌드에 문제가 없어서 아래와 같이 실행결과가 나옵니다.
그러나 위의 실행 결과를 보면 이상한 점을 발견하실 수 있을 것입니다. 그것은 CNative 오브젝트의 생성자는 호출하지만 파괴자는 호출되지 않은 것입니다. 이것은 array 컨테이너는 CLI 객체이므로 GC에서 관리하지만 non-CLI 오브젝트를 포인터 타입으로 사용한 것은 GC에서 관리하지 않으므로 만약 array가 GC에서
소멸 되기 전에 array에 담겨있는 non-CLI 오브젝트를
메모리(new로 할당한)를 해제하지 않으면 메모리 해제가 되지 않아서 메모리 릭이 발생합니다.
그래서 아래와 같이 array가 GC에서 소멸되기 전에 메모리를 해제하도록 해야합니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
class CNative
{
public:
CNative()
{
Console::WriteLine(__FUNCTION__);
}
~CNative()
{
Console::WriteLine(__FUNCTION__);
}
};
int main(array<System::String ^>
^args)
{
array<CNative*>^
arr = gcnew array<CNative*>(2);
for(int
i=0; i<arr->Length; i++)
{
arr[i]
= new CNative();
}
for(int
i=0; i<arr->Length; i++)
{
delete
arr[i];
}
getchar();
return 0;
}
실행 결과
이번에는 CNative의 파괴자가 제대로 호출되고 있습니다.
출처
도서 "C++/CLI In Action"
C++/CLI를 공부하시는 분들은 "C++/CLI In Action" 책을 꼭 한번 보시기를 추천합니다.
static 생성자는 클래스의 생성자에서 static 멤버를
초기화 하고 싶을 때 사용합니다.
ref
class, value class, interface에서 사용할
수 있습니다.
#include
"stdafx.h"
#include
<iostream>
using
namespace System;
ref class
A {
public:
static int a_;
static A()
{
a_ += 10;
}
};
ref class
B {
public:
static int b_;
static B()
{
//a_ += 10; // error
b_ += 10;
}
};
ref class
C {
public:
static int c_ = 100;
static C()
{
c_ = 10;
}
};
int
main()
{
Console::WriteLine(A::a_);
A::A();
Console::WriteLine(A::a_);
Console::WriteLine(B::b_);
Console::WriteLine(C::c_);
getchar();
return 0;
}
< 결과 >
static 생성자는 런타임에서 호출하기 때문에 클래스 A의
멤버 a_는 이미 10으로 설정되어 있습니다. 그리고 이미 런타임에서 호출하였기 때문에 명시적으로 A::A()를
호출해도 실제로는 호출되지 않습니다.
클래스
B의 경우 static 생성자에서 비 static 멤버를
호출하면 에러가 발생합니다.
클래스
C의 경우 static 멤버 c_를 선언과 동시에
초기화 했지만 런타임에서 static 생성자를 호출하여 값이 10으로
설정되었습니다.
initonly
initonly로 선언된 멤버는 생성자에서만 값을 설정할 수 있습니다. 그리고 initonly static로 선언된 멤버는 static 생성자에서만 값을 설정할 수 있습니다.
ref class C
{
public:
initonly staticint x;
initonly staticint y;
initonly int z;
static C()
{
x =1;
y =2;
// z = 3; // Error
}
C()
{
// A = 2; // Error
z =3;
}
void sfunc()
{
// x = 5; // Error
// z = 5; // Error
}
};
int main()
{
System::Console::WriteLine(C::x);
System::Console::WriteLine(C::y);
C c;
System::Console::WriteLine(c.z);
return0;
}
literal
literal로 선언된 멤버는 선언과 동시에 값을 설정하고 이후 쓰기는 불가능합니다. 오직 읽기만 가능합니다.
usingnamespaceSystem;
ref class C
{
public:
literal String^ S ="Hello";
literal int I =100;
};
int main()
{
Console::WriteLine(C::S);
Console::WriteLine(C::I);
return0;
}
참고 http://cppcli.shacknet.nu/cli:static%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF http://cppcli.shacknet.nu/cli:initonly http://cppcli.shacknet.nu/cli:literal
위의 예의 델리게이트들은 모두 void를 반환하고 파라미터가 없는 것인데 당연하듯이 반환 값이나 파라미터를 가질 수 있습니다.
델리게이트의 비동기 실행
델리게이트는 비동기 실행을 지원합니다. 비동기 실행은 처리를
요청한 후 종료를 기다리지 않은 호출한 곳으로 제어를 넘겨줍니다. 델리게이트 함수가 긴 시간을 필요로
하는 작업인 경우 비동기 실행을 이용하면 프로그램의 응답성을 높일 수 있습니다.
델리게이트의 비동기 실행은 스레드를 사용합니다. 이런 경우 비동기
실행을 할 때마다 스레드의 생성과 소멸에 부담을 느낄 수도 있지만 델리게이트는 닷넷의 기능을 잘 활용하여 스레드를 생성/삭제하지 않고 스레드 풀에 있는 스레드를 사용하므로 스레드 사용에 대한 부담이 작습니다.
비동기 실행을 할 때는 주의해야 할 점이 있습니다. 비동기 실행을
하는 경우 델리게이트에는 꼭 하나의 함수만 등록해야 합니다. 만약 2개
이상 등록하였다면 예외가 발생합니다.
비동기 실행은 BeginInvoke()를 사용하고, 만약 종료를 기다리고 싶다면 EndInvoke()를 사용합니다.
#include "stdafx.h"
#include <iostream>
using namespace System;
delegate void MyDele(void);
void myfunc(void)
{
System::Threading::Thread::Sleep(3000);
}
int main(array<System::String ^>
^args)
{
MyDele^
dele = gcnew MyDele(&myfunc);
Console::WriteLine(L"1");
IAsyncResult^
result = dele->BeginInvoke(nullptr,nullptr);
Console::WriteLine(L"2");
dele->EndInvoke(result);
Console::WriteLine(L"3");
getchar();
return 0;
}
위 코드를 실행하면 '2'가 찍힌 이후 3초가 지난 이후에 3이 찍힙니다.
참고
http://cppcli.shacknet.nu/cli:delegate
http://cppcli.shacknet.nu/cli:delegate%E3%81%9D%E3%81%AE2
댓글을 달아 주세요
너무 멋져요을 개봉된! 나는 필자 전에 이런 걸 배우는 가정 없다. 그래서이 주제에 대한 몇 가지 참신한 아이디어가있는 모든 사람을 찾을 수 좋네요. 정말이 일을 시작 주셔서 감사합니다. 이 웹 사이트는 약간 독창성과 웹, 누군가에 원한의 한 가지입니다. 웹에 새로운 것을 가져다 유용 직업!