블로그 이미지
대갈장군

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

'프로그래밍'에 해당되는 글 102

  1. 2011.03.11 윈도우 메모리 - 3 (VirtualAlloc)
  2. 2011.03.11 윈도우 메모리 - 2 (malloc 함수)2
  3. 2011.03.10 윈도우 메모리 - 1
  4. 2010.09.25 Visual C++ 컴파일시 링크 되는 정적 혹은 동적 라이브러리들
  5. 2010.09.25 C Standard Library
  6. 2010.09.04 윈도우 7 및 윈도우 비스타 USB로 설치하기
  7. 2010.09.01 Application Domain
  8. 2010.07.01 USB로 윈도우 XP 설치하기4
  9. 2010.06.25 C# 이벤트 - 객체 지향적 메시지
  10. 2010.06.18 C# - 제너릭 (Generic) 에 대한 질문
  11. 2010.06.18 Covariance and Contravariance
  12. 2010.06.17 델리게이트 - Delegate
  13. 2010.03.18 The Boost.Threads Library2
  14. 2010.03.11 프로그램의 원리 (컴파일, 링크, DLL)
  15. 2010.03.11 싱글 스레드에서 멀티 스레드로... 왜 /MD 인가?
  16. 2010.03.06 유니코드 (Updated!)4
  17. 2010.03.05 Managed 코드, Unmanaged 코드 그리고 Native 코드에 대한 이야기3
  18. 2010.02.18 Finding Convex Hull (Graham's Scan) - 외곽선 찾기, 임의의 사각형 내부에 임의의 점 존재 확인8
  19. 2009.12.19 객체 지향의 이해 1 - 클래스
  20. 2009.11.17 윈도우 - 차일드, 팝업
  21. 2009.11.17 윈도우 - SetClassLongPtr(), SetWindowLongPtr()
  22. 2009.11.17 윈도우 - WNDCLASS 구조체
  23. 2009.11.17 윈도우 - CreateWindow()
  24. 2009.09.10 x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.4053_x-ww_e69679896
  25. 2009.07.30 C/C++ 메모리 오류에 대하여1
  26. 2009.07.28 STL (Standard Template Library)
  27. 2009.07.28 호출 규약
  28. 2009.05.22 메모리, Win32 vs. Win162
  29. 2009.05.16 DirectShow 필터로 동영상 추출하여 OpenGL로 렌더링 하기
  30. 2009.03.17 필터 렌더링 (1) - 자동 & 수동 소스 필터 렌더링
2011. 3. 11. 23:54 프로그래밍/Windows API
윈도우에서 메모리를 할당하는 다음 방법은 VirtualAlloc() 함수다. 이 함수는 malloc 함수에서 보다 발전된 형태로 사용자에게 여러 가지 추가 기능을 제공한다.

가장 큰 차이점은 '예약'과 '확정' 상태가 존재한다는 것인데 malloc의 경우 '확정' 상태만 있었다. 여기에 예약이 추가 되었는데 이로써 보다 효율적인 메모리 사용이 가능해 졌다. 예약만 한 경우 물리적 메모리는 전혀 소모되지 않는다. 오직 확정에만 가상 메모리가 실제로 할당된다.

함수의 원형은 다음과 같다.
LPVOID WINAPI VirtualAlloc( __in_opt  LPVOID lpAddress, __in      SIZE_T dwSize, __in      DWORD flAllocationType, __in      DWORD flProtect );

첫번째 인자가 할당하고자 하는 주소의 위치 (4GB의 프로세스 주소 공간에서), 두번째 인자는 할당할 크기, 세번째 인자는 할당 타입인데 바로 여기서 확정, 예약, 높은 번지 할당등을 설정할 수 있다. 자세한 내용은 다음 표 참조. 마지막 인자는 할당할 페이지의 엑세스 타입 설정을 하는데 이것도 malloc과의 다른점 중에 하나다. 읽기 쓰기 접근 제한 설정이 가능하다.

ValueMeaning
MEM_COMMIT
0x1000

Allocates physical storage in memory or in the paging file on disk for the specified reserved memory pages. The function initializes the memory to zero.

To reserve and commit pages in one step, call VirtualAlloc with MEM_COMMIT | MEM_RESERVE.

The function fails if you attempt to commit a page that has not been reserved. The resulting error code is ERROR_INVALID_ADDRESS.

An attempt to commit a page that is already committed does not cause the function to fail. This means that you can commit pages without first determining the current commitment state of each page.

MEM_RESERVE
0x2000

Reserves a range of the process's virtual address space without allocating any actual physical storage in memory or in the paging file on disk.

You can commit reserved pages in subsequent calls to the VirtualAlloc function. To reserve and commit pages in one step, call VirtualAlloc with MEM_COMMIT | MEM_RESERVE.

Other memory allocation functions, such as malloc and LocalAlloc, cannot use a reserved range of memory until it is released.

MEM_RESET
0x80000

Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages should not be read from or written to the paging file. However, the memory block will be used again later, so it should not be decommitted. This value cannot be used with any other value.

Using this value does not guarantee that the range operated on with MEM_RESET will contain zeroes. If you want the range to contain zeroes, decommit the memory and then recommit it.

When you specify MEM_RESET, the VirtualAlloc function ignores the value offlProtect. However, you must still set flProtect to a valid protection value, such as PAGE_NOACCESS.

VirtualAlloc returns an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable if it is mapped to a paging file.

간단한 예를 보면, 


ptr = (int *)VirtualAlloc(NULL, sizeof(int)*10, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
VirtualFree(ptr, sizeof(int)*10,MEM_DECOMMIT);
VirtualFree(ptr, 0, MEM_RELEASE);
VirtualFree에 대한 설명이 빠졌는데 메모리 해제 함수다. MEM_DECOMMIT은 확정 해제, MEM_RELEASE는 예약 해제다. 메모리 할당도 MEM_RESERVE와 MEM_COMMIT를 같이 불러 호출하거나 따로 각각 두번 호출해야 한다. malloc보다 좀 번거롭긴 하다.

예약을 할 수 있다는 차이점 이외에도 할당 단위의 차이가 있다. 일단 일반적인 컴퓨터를 기준으로 봤을때 4K 사이즈로 할당이 이루어 진다. 즉, 내가 10K 할당을 요구하면 4의 배수인 12K로 할당한다는 말. 그리고 할당을 시작하는 할당 단위가 있다. 보통의 경우 64K 단위로써 매우 큰 편이다. 즉, 작은 사이즈의 메모리를 여러번 반복적으로 할당하게 되면 할당을 시작하는 단위를 64K로 끊기 때문에 메모리의 조각화가 심해진다. 

그래서 작은 메모리의 할당에는 malloc이 훨씬 더 경제적이다. 그런 경우에는 다음에 알아볼 힙을 사용하는 것도 좋은 방법이다. 

VirtualAlloc의 다른 재미있는 점은 접근 권한 설정이 가능하다는 점인데 너무 보안에 신경을 쓴게 아닌가 싶다.. ㅡ.ㅡ 또 다른 한가지 흥미로운 점은 메모리 잠금 기능인데 할당된 메모리를 물리적 RAM에 상주 하도록 잠그는 함수다. 이로써 보다 빠른 접근 속도를 제공하는데 잘못 사용하면 사용가능한 물리적 RAM을 제한함으로써 운영체제 전체에 속도저하를 유발할 수 있다. 

내가 봤을때 마이크로 소프트에서 야심차게 내놓은 메모리 할당 / 해제 함수인 것 같은데 소규모 프로그램 개발자에게는 그닥 매력적이지 않다. 물론 가능한 기능들은 malloc에 비해서 많고 예약과 확정이라는 효율적인 메커니즘을 제공하기는 하지만 작은 용량을 가지는 다수의 메모리를 반복적으로 할당해야 하는 경우 VirtualAlloc은 메모리 낭비가 너무 심히다.



'프로그래밍 > Windows API' 카테고리의 다른 글

윈도우 메모리 파이널!  (0) 2011.03.12
윈도우 메모리 - 4 (힙과 메모리 맵)  (0) 2011.03.12
윈도우 메모리 - 2 (malloc 함수)  (2) 2011.03.11
윈도우 메모리 - 1  (0) 2011.03.10
윈도우 - 차일드, 팝업  (0) 2009.11.17
posted by 대갈장군
2011. 3. 11. 03:20 프로그래밍/Windows API
앞서 언급했던 윈도우 메모리에 관한 이야기 2탄으로 바로 malloc() 함수에 대한 이야기다.

malloc은 뭐 워낙 유명한 함수라서 굳이 이야기 안해도 사용하는 방법쯤은 다 알거라 생각한다. 바로 코드로 들어가 보자.

   
   1:  #include <stdio.h>
   2:  #include <stdlib.h>
   3:  #include <string.h>
   4:   
   5:  #ifdef _DEBUG
   6:  #include <vld.h>
   7:  #include <vldapi.h>
   8:  #endif
   9:   
  10:  void main()
  11:  {
  12:      // Test int
  13:      int *ptr;
  14:      ptr = (int*)malloc(sizeof(int)*10);
  15:   
  16:      for(int i = 0; i < 10; i++)
  17:      {
  18:          ptr[i] = i * 10;
  19:          printf("ptr[%d] = %d\n", i, ptr[i]);
  20:      }
  21:   
  22:      free(ptr);
  23:   
  24:      // Test char
  25:      char *ptr_char;
  26:      ptr_char = (char *)malloc(sizeof(char)*20);
  27:   
  28:      char *str1 = "MEMORY LEAK";
  29:      strcpy(ptr_char, str1);
  30:   
  31:      printf("ptr_char = %s\n", ptr_char);
  32:   
  33:      free(ptr_char);
  34:   
  35:      // Test array of pointers
  36:      int **double_ptr;
  37:   
  38:      double_ptr = (int **)malloc(sizeof(int *)*10);
  39:   
  40:      for(int i = 0; i < 10; i++)
  41:      {
  42:          double_ptr[i] = (int *)malloc(sizeof(int)*10);
  43:          for(int j = 0; j < 10; j++)
  44:          {
  45:              double_ptr[i][j] = (i * 10) + j;
  46:              printf("double_ptr[%d][%d] = %d\n", i, j, double_ptr[i][j]);
  47:          }
  48:      }
  49:   
  50:      for(int i = 0; i < 10; i++)
  51:      {
  52:          free(double_ptr[i]);
  53:      }
  54:   
  55:      free(double_ptr);
  56:   
  57:  }

위의 코드의 제일 윗부분에 라인 6번과 7번을 보면 vld.h 라는 녀석이 있는데 이 녀석은 이른바 visual leak detector라고 불리는 오픈 소스로서 메모리 누수가 발생하면 콘솔 출력창에 어디서 뭐가 왜 어떻게 유출되었는지 상세 정보를 보여주는 아주 착한 놈이다.

이 오픈 소스는 http://www.codeproject.com/KB/applications/visualleakdetector.aspx 에 가면 다운 받을 수 있다.

코드는 세 파트로 나뉘어져 있는데 첫번째 파트에서는 간단하게 10개의 int 메모리를 할당하고 0 에서 9까지 넣어 출력했고 두번째 파트는 char 메모리 공간을 생성하여 MEMORY LEAK라는 섬뜩한 문구를 출력해 봤고 마지막은 이중 포인터를 이용해서 2차원 int 배열을 생성하여 0부터 99까지 각 배열에 대입 후 출력해 봤다.

암튼, 22번 라인, 33번 라인 그리고 50번에서 55번까지 free()로 할당된 메모리를 해제하는 명령들이 나와있는데 메모리는 할당보다 해제가 더 중요하다. 화장은 하는 것보다 지우는 것이 더 중요한 것 처럼... 

예를 들어 두번째 테스트는 Test char 파트에서 free()를 호출 안하면 (라인 33번 지우면), 다음과 같은 에러가 프로그램 종료 후 출력창에 뜬다.


너무나도 친절하게 얼마만큼의 메모리가 누수되었는지 그리고 들어있던 내용은 무엇인지 그리고 게다가 코드의 어느 부분에서 누수가 있었는지 알려준다... 너무 친절해서 눈물이 날 지경이다..

라인 33번에서 55번까지는 이중 포인터의 개념인데 integer 값을 10개 저장하는 배열을 10개 만들었다. 즉, 10 x 10 = 100 개의 int 배열을 만든 셈이다. 중요한 것은 해제인데 해제 할때는 10개의 개별 배열을 모두 하나씩 해제한 후 마지막에 이중 포인터를 해제 한다. 만약 개별 배열들을 해제하지 않고 이중 포인터만 해제해 버리면 10개의 개별 배열은 부모 잃어버린 자식들이 되므로 메모리 누수가 일어난다. 

개별 배열(자식)을 해제 하기 전에 이중 포인터 (부모)를 날려 버리면 자식들을 찾을 수가 없다. 왜냐면 부모 연락처가 없기 때문에... 

malloc을 이용한 할당은 가장 경제적인 메모리 할당이다. 왜냐면 바이트 단위로 사용자가 원하는 만큼 할당하기 때문이다. 좀 있다 살펴볼 다른 메모리 할당 함수는 malloc 처럼 바이트 단위로 할당을 하지 않는다. 경제적인 것은 그만큼 손이 많이 간다. 고로 해제하는 과정을 사용자가 반드시 꼼꼼히 해야 한다는 불편함이 있다. 


posted by 대갈장군
2011. 3. 10. 05:56 프로그래밍/Windows API
보다 나은 개발을 위해서는 윈도우 메모리에 대해서 전반적인 이해가 필요로 된다. 내가 김상형님의 책을 좋아하는 이유는 김상형님은 책을 쓰실때 항상 '왜' 라는 질문에 대답을 해주시기 때문이다.

과거 16비트 시절의 메모리 구조는 한 마디로 요약하자면 '위험천만'이었다. 가장 큰 문제는 사용자가 잘못 작성한 프로그램이 건드려서는 안되는 중요한 메모리 영역을 건드릴 수 있었다는 점이다. 메모리가 '전역 변수' 처럼 모든 프로그램 및 운영체제에게 통일된 표기 방식으로 공개 되어 있었기 때문이다.

그래서 실수로 운영체제의 영역을 지우거나 바꿔버리면 심지어 윈도우를 다시 깔아야 하는 말도 안되는 불상사가 종종 있었다고 한다. 안타깝게도 나는 그런 일을 겪어볼 행운(?)이 없었다. 

그러면서 32비트 운영체제가 도입되고 윈도우 95/98로 넘어오면서 드디어 새로운 메모리 체계가 완성된다. 바로 '가상 메모리' 시스템이다. 

가상 메모리는 간단히 말해서 물리적 메모리 + 페이징 파일이다. 여기서 물리적 메모리는 우리가 너무나 너무나 잘 알고 있는 바로 RAM이고 페이징 파일은 RAM을 흉내내고 있는 하드 디스크의 일부를 말한다.

RAM은 속도가 빠르지만 용량이 충분하지 않고 페이징 파일은 하드디스크를 이용하므로 용량은 방대하나 RAM에 비해 속도가 떨어진다.

하지만 이것이 전부가 아니다. 가상 메모리를 이용하는 운영체제는 각 프로그램이 운영체제가 제공하는 최대 주소 공간을 각 프로그램에게 할당해 준다. 즉, 32비트 운영체제라면 4GB의 주소 공간을 각 프로그램 다시 말하자면 하나의 프로세스에 할당해 준다.

이 4GB의 주소 공간은 사실 껍데기에 불과하며 물리적 메모리가 아니다. 예를 들어 malloc 함수를 통해 메모리 할당을 요청하면 프로그램이 가지고 있는 4GB 주소 공간의 어딘가에 요청한 메모리가 할당되었다고 알려주지만 실제로는 가상 메모리에 실질적인 메모리가 할당된 후 할당된 주소가 '페이지 테이블'이라고 불리는 맵에 저장되고 4GB에 할당 된 것 처럼 보이는 메모리는 실제로는 가상 메모리로 자동 연결되게 되어 있다.

이런 식의 이중 연결을 구현하는 이유는 이렇게 함으로써 이 프로그램이 다른 프로그램의 주소 영역, 혹은 운영체제의 고유 메모리 영역을 아예 터치 할 수 없도록 차단해 주기 때문이다. 고로 프로세스 A가 망해도 A만 망하지 B,C,D 기타 운영 체제에게는 눈꼽만큼의 영향도 안준다.

이는 16비트 운영체제의 단점을 한방에 커버 시켜주는 그야 말로 최대 강점이다. 최대 강점이면서 동시에 최대 단점이다. 왜냐면 프로세스간에 통신이 겁나 복잡해 졌기 때문이다. 보안성의 강화는 접근성을 약화 시킬수 밖에 없다.

다음 글타래에서는 가상 메모리 시스템에서 메모리를 할당하는 다양한 방법에 대해서 알아보자. 


posted by 대갈장군
2010. 9. 25. 01:58 프로그래밍/Visual C++ 2008
/MT, /MD, /MTd, /MDd 와 같은 컴파일 옵션에 따라 링크 되는 라이브러리가 다르다.
일단 정적으로 링크하게 되면 libcmt.lib 또는 libcmtd.lib가 선택이 되며 동적으로 링크할 경우 msvcrt.lib와 msvcrtd.lib가 선택된다. 이들의 차이는 정적 / 동적 링크이며 d가 붙으면 디버그 모드, 없으면 릴리즈 모드다.

C run-time library

Associated DLL

Characteristics

Option

Preprocessor directives

libcmt.lib

None, static link.

Multithreaded, static link

/MT

_MT

msvcrt.lib

msvcr80.dll

Multithreaded, dynamic link (import library for MSVCR80.DLL). Be aware that if you use the Standard C++ Library, your program will need MSVCP80.DLL to run.

/MD

_MT, _DLL

libcmtd.lib

None, static link

Multithreaded, static link (debug)

/MTd

_DEBUG, _MT

msvcrtd.lib

msvcr80d.dll

Multithreaded, dynamic link (import library for MSVCR80D.DLL) (debug).

/MDd

_DEBUG, _MT, _DLL

msvcmrt.lib

msvcm80.dll

C Runtime import library. Used for mixed managed/native code.

/clr

 

msvcurt.lib

msvcm80.dll

C Runtime import library compiled as 100% pure MSIL code. All code complies with the ECMA URT spec for MSIL.

/clr:pure

 

헌데 재미 있는 것은 Visual C++는 추가적으로 헤더의 선언 유무에 따라 Standard C++ library도 링크한다는 것이다. 자, 여기서 조금 헷갈리는데 위의 테이블은 분명 Visual C++의 프로젝트 옵션의 컴파일 설정에 따라 링크하는 라이브러리가 결정되었다. 헌데 아래 테이블에 나오는 라이브러리는 단순히 프로젝트 옵션의 컴파일 설정에 의해 결정되는 것이 아니라 Standard C++ library header가 프로그램에 포함되었는지를 보고 링크가 이루어 진다.  

Standard C++ Library

Characteristics

Option

Preprocessor directives

LIBCPMT.LIB

Multithreaded, static link

/MT

_MT

MSVCPRT.LIB

Multithreaded, dynamic link (import library for MSVCP80.dll)

/MD

_MT, _DLL

LIBCPMTD.LIB

Multithreaded, static link

/MTd

_DEBUG, _MT

MSVCPRTD.LIB

Multithreaded, dynamic link (import library for MSVCP80D.DLL)

/MDd

_DEBUG, _MT, _DLL

바로 요 아래에 있는 것이 Standard C++ library header 파일들의 목록이다. 바로 요놈들중에 한놈이라도 프로그램에 선언되어 사용되는 놈이 있다면 위의 테이블에 나온놈들중에서 현재 프로젝트의 컴파일 옵션에 따라 적당한 라이브러리가 추가로 링크가된다. 

algorithm>

<bitset>

<complex>

<deque>

<exception>

<fstream>

<functional>

<hash_map>

<hash_set>

<iomanip>

<ios>

<iosfwd>

<iostream>

<iso646.h>

<istream>

<iterator>

<limits>

<list>

<locale>

<map>

<memory>

<new>

<numeric>

<ostream>

<queue>

<set>

<sstream>

<stack>

<stdexcept>

<streambuf>

<string>

<strstream>

<utility>

<valarray>

<vector>

 

In addition, the following C++ wrappers are documented:

<cassert>

<cctype>

<cerrno>

<cfloat>

<ciso646>

<climits>

<clocale>

<cmath>

<csetjmp>

<csignal>

<cstdarg>

<cstddef>

<cstdio>

<cstdlib>

<cstring>

<ctime>

<cwchar>

<cwctype>

 

이렇게 표만 잔뜩 늘어 놓으면 재미가 없으니, 예를 보자.

우선 아래 그림을 보면 빈 코드가 컴파일 되었는데 포함된 헤더파일은 오직 stdio.h 밖에 없다. 그리고 아래 Output을 살펴보면 MSVCRTD.lib를 볼수 있는데 이것은 현재 이 프로그램을 /MDd 옵션으로 컴파일 되었기 때문이다.


그렇다면 만약 ctime 부분을 포함시키면 어떻게 될까? ctime은 Standard C++ library header 중의 하나이기 때문에 현재 컴파일 설정인 /MDd에 해당하는 라이브러리인 MSVCPRTD.lib가 포함될까? 정답은 아래 그림속에...


앞서본 그림과 달리 아래 부분에 msvcprtd.lib가 포함되어 있다! 호~이!

posted by 대갈장군
2010. 9. 25. 01:40 프로그래밍/C
우선 역사를 보자. C 언어는 기준이 완성되기 전까지는 Input/Output 기능과 같은 기본 함수를 언어자체적으로 지원하지 않았다. 그것에 불편을 느낀 다수의 사용자들이 모여 지금의 C Standard Library를 작성하게 되었다. 

Unix와 C는 둘 다 AT&T's Bell Laboratories에서 1960년대 그리고 1970년대에 개발되었는데 C 언어가 인기가 높아지고 많은 사람들이 사용하기 시작하면서 호환성 문제가 발생한다. 고로 American National Standards Institute(ANSI)에서 표준을 세우고 규격화하여 내놓은 것이 바로 ANSI C이다. 그리고 이것은 1989년에 완성된어 C89라고 명명되어 졌다. 

그렇다면, C Standard Library는 뭐냐 하면, C Programming 언어에서 사용되는 기본 명령들, 예를 들자면 input/output 및 string 핸들링 함수와 같은 것들을 구현하기 위한 headers와 library routines를 모아놓은 ISO C 기준의 일부분이다. 

즉, 다시 말해서 C Standard Library는 문서에 의해 규정된 '인터페이스 기준'이다. 고로 걍 설명서일 뿐이라는 것. 사실 C Standard Library는 24개의 헤더 파일인데 요 헤더 파일들에는 함수들과 데이터 타입 그리고 매크로등이 들어가 있다. 

이것은 다른 언어 (예를 들자면 자바)에 비교해서 아주 간단한 함수들만 선언된 헤더파일들이다. C Standard Library는 기껏해야 수학 함수, 문자열 함수, 타입 변환 함수 및 파일 입출력, 콘솔 화면 입출력 함수 정도만 명시되어 있다. 고로 C Standard Library는 C++ Standard Template Library와 같은 고급 컨테이너 타입이나 GUI, 네트워킹 툴등의 고급 기능은 전혀 없다.

아래의 목록이 바로 ISO가 명시하는 C Standard Library의 헤더 파일들이다.

ISO C library headers

NameFromDescription
<assert.h> Contains the assert macro, used to assist with detecting logical errors and other types of bug in debugging versions of a program.
<complex.h> C99 A set of functions for manipulating complex numbers.
<ctype.h> Contains functions used to classify characters by their types or to convert between upper and lower case in a way that is independent of the used character set (typically ASCII or one of its extensions, although implementations utilizing EBCDIC are also known).
<errno.h> For testing error codes reported by library functions.
<fenv.h> C99 For controlling floating-point environment.
<float.h> Contains defined constants specifying the implementation-specific properties of the floating-point library, such as the minimum difference between two different floating-point numbers (_EPSILON), the maximum number of digits of accuracy (_DIG) and the range of numbers which can be represented (_MIN_MAX).
<inttypes.h> C99 For precise conversion between integer types.
<iso646.h> NA1 For programming in ISO 646 variant character sets.
<limits.h> Contains defined constants specifying the implementation-specific properties of the integer types, such as the range of numbers which can be represented (_MIN_MAX).
<locale.h> For setlocale and related constants. This is used to choose an appropriate locale.
<math.h> For computing common mathematical functions.
<setjmp.h> Declares the macros setjmp and longjmp, which are used for non-local exits.
<signal.h> For controlling various exceptional conditions.
<stdarg.h> For accessing a varying number of arguments passed to functions.
<stdbool.h> C99 For a boolean data type.
<stdint.h> C99 For defining various integer types.
<stddef.h> For defining several useful types and macros.
<stdio.h> Provides the core input and output capabilities of the C language. This file includes the venerable printffunction.
<stdlib.h> For performing a variety of operations, including conversion, pseudo-random numbers, memory allocation, process control, environment, signalling, searching, and sorting.
<string.h> For manipulating several kinds of strings.
<tgmath.h> C99 For type-generic mathematical functions.
<time.h> For converting between various time and date formats.
<wchar.h> NA1 For manipulating wide streams and several kinds of strings using wide characters - key to supporting a range of languages.
<wctype.h> NA1 For classifying wide characters.



posted by 대갈장군
2010. 9. 4. 00:00 프로그래밍
이 내용은 http://www.intowindows.com/how-to-install-windows-7vista-from-usb-drive-detailed-100-working-guide/ 를 참조해서 작성했습니다.

XP에서 XP설치 USB를 만드는 방법은 이미 전에 소개한 바 있으나 Windows 7과 비스타를 설치하는 방법은 조금 다르다. 

우선 작업을 시작하기 전에 필요한 것들을 살펴보면,
  • 최소 4GB 용량을 가지는 USB 
  • 윈도우 7 또는 윈도우 비스타 설치 파일 (DVD를 가지고 있다면 OK)
  • 메인보드가 USB 부팅을 지원해야 함
위 조건을 충족한다면 다음 순서대로 명령수행 해 봅시다~

1. USB 키를 멀쩡한 컴퓨터에 꼽고 안에 들어있는 내용을 비운다.

2. Command Prompt를 열기 (단, 열때 Administrator 권한을 열어야 함)
    Start menu -> All Programs -> Accessories 에 보면 Command Prompt가 있고 우클릭하면 Run as administrator 옵션 있음.

3. 열린 Command Prompt에 DISKPART 라고 치고 엔터 날리기


   그러면 위 그림 아래 부분처럼 DISKPART> 프롬프트가 뜰것임. 그런 다음 LIST DISK 라고 치고 엔터 날리면 현재 이 컴퓨터에 
   연결되어 있는 모든 드라이브 정보가 나오는데 그것들 중에 아까 꼳은 USB 드라이브 정보도 있다.
   그 녀석의 DISK 번호가 무엇인지 잘 기억해 두자.

4. 만약 꼳은 USB의 번호가 Disk 1 이라고 가정하고 아래 명령을 차례대로 수행하자
   SELECT DISK 1
   CLEAN
   CREATE PARTITION PRIMARY
   SELECT PARTITION 1
   ACTIVE
   FORMAT FS=NTFS
   ASSIGN
   EXIT

   위의 명령중 FORMAT FS=NTFS를 실행하면 포멧한다고 시간이 좀 걸린다. 


   위 그림을 보면서 따라하면 더 쉽겠죵

5. 준비해둔 윈도우 7 혹은 비스타 DVD를 CD 드라이브에 넣고 CD 드라이브의 이름을 확인한다. 일반적으로 CD 드라이브는 D 드라이브죠.
    그리고 USB의 드라이브 이름도 확인한다. 내 컴퓨터를 열면 USB의 드라이브 이름을 확인할 수 있다. 여기서는 H라고 가정하자.

6. 아까 열었던 Command Prompt에서 다음 명령을 넣자

    D: CD BOOT
   CD BOOT
    여기서 D:가 바로 윈도우 설치 시디가 들어가 있는 CD 드라이브를 의미한다. 만약 자신의 컴퓨터의 CD 드라이브가 D가 아니라면 바꿔주세용.

7. 마지막으로 다음 명령을 날려준다. 다만 마지막에 들어간 H: 는 현재 자신의 USB 드라이브 이름이므로 자신에게 맞게 바꿔주시길.

    BOOTSECT.EXE /NT60 H:
   

8. 이제 CD 드라이브에 있는 윈도우 7이나 비스타를 모조리 USB로 복사하자. Ctrl+A, Ctrl+C, Ctrl+V! 붙여넣기 콤보 발사

9. 마지막으로 윈도우를 설치할 컴퓨터의 BIOS 셋업에 들어가서 (컴퓨터 시작시 Del 키나 F2 또는 F10, F11 키를 눌러서 들어감) 부팅 순서에서 USB를 최상위로 맞춰놓고 컴퓨터에 USB를 꼽아준 다음 재부팅 시키면 알아서 설치를 시작할 것이다.

종종 일부 노트북은 UBS가 컴퓨터에 연결되어 있지 않은 경우 BIOS의 부팅 순서 설정에 UBS를 보여주지 않는 경우가 있다. 그런 경우를 방지하려면 USB를 꼳아 놓은 상태에서 BIOS를 들어가면 될것이다. Good Luck!
  
posted by 대갈장군
2010. 9. 1. 00:10 프로그래밍/70-536
Application Domain, 줄여서 AD는 뭣하는 녀석인가? 가장 빠르고 쉽게 답하자면 '프로세스'와 비슷한 놈이다. 프로세스는 운영체제가 제공하는 일종의 '독립된 프로그램 운영 공간'인데 바로 이 AD는 .NET Framework에 의해 제공되며 운영체제가 제공하는 프로세스보다 성능 효율 그리고 보안적인 면에서 우위를 점하고 있는 놈이다.

그렇다면 이 AD가 가지는 장점을 나열해 보자.
  • 다른 어셈블리로의 직접적인 엑세스 방지 (보안성 증가)
  • 보안설정이 각각의 AD에 적용 가능 (보안성 증가)
  • 하나의 AD에서 발생한 unhandled exception은 다른 AD에 영향을 미치지 않음 (안정성 증가)
  • 프로세스 생성에 비해 오버헤드가 적다 (효율성 증가)
위에서 언급된 장점들을 이용하면 아주 효율적인 프로그램 작성이 가능한데, 두 가지의 예를 들자면, 우선 하나의 AD가 오류로 인해 정지하더라도 다른 AD 영역은 안정적으로 돌아가므로 프로그램 전체의 안정성이 증가한다. 이것을 우습게 보면 안되는 것이 인터넷 환경에서 예측할 수 없는 오류로 에러가 발생했을 경우 이 오류로 인해 저장되지 않은 모든 작업이 한번에 날아가 버린다면 얼마나 허무하겠는가? 고로 이런면에서 AD는 매우 중요한 '프로그램 안정성'을 제공할 수 있다는 것이다.

다른 예로는 효율성인데, 일반적으로 하나의 프로세스에서 돌아가는 프로그램은 모든 라이브러리 (어셈블리)가 실행시에 정적 혹은 동적으로 연결된다. 대표적인 동적 라이브러리가 DLL인데 일반적으로 이 DLL들은 사이즈가 무지막지하게 큰 경우가 있다. AD를 사용하면 이런 무거운 DLL들을 실행시에 별도의 AD 영역으로 불러들인 다음 사용하고 다시 unload 해 주면 효율성이 증대 된다. 물론 현재의 컴퓨터 환경에서 남아도는 메모리가 많은데 이렇게 까지 할 필요가 있나라는 생각이 들기도 하지만, 어쨌든, 그렇게 할 수 있다는 것은 프로세스에 비해 효율적일수 있다는 것.

Application Domain의 생성 및 어셈블리 로드, 그리고 해제에 대한 코드는 아래와 같다.

AppDomain AD = AppDomain.CreateDomain("MyDomain"); // AD 생성

AD.ExecuteAssembly("Assembly.exe"); // AD에 어셈블리 로드

AppDomain.Unload(AD); // AD 해제

위에서 말한 각각의 AD에 대한 보안 설정에 대해 자세히 알아보자. 일단 왜 개별 보안 설정이 위력적인지 알아야 한다. 예를 들어 인터넷에서 Third-party(대형 기업이 만든것이 아닌 제 삼자)가 만든 어셈블리를 사용한다고 했을때, 권한 설정이 되어 있지 않다면 이 어셈블리는 하고싶은 것을 다 할수 있게된다. 만약 이 어셈블리에 보안 취약점이 있고 해커가 그것을 이용해 사용자의 컴퓨터를 악용하려 한다고 해도 막을 방법이 없다. 사용자는 자신의 코드는 안전하다고 믿었으나 아무것도 모른체 눈뜨고 당할 수 밖에 없다. 

만약 이런 시나리오에서 '권한 설정'을 추가하게 되면 이야기가 달라진다. 새로운 AD를 설정하여 보안 취약성을 가지고 있는 어셈블리를 불러온 다음 권한 설정을 통해 하드디스크 읽고 쓰기를 금지시켜 버리면 비록 그 어셈블리가 취약성을 가지고는 있지만 악용할 수는 없게 된다.

바로 이것이 권한제한 -> 보안 취약성 제거 -> 피해 최소화 로 이어지는 방어법이다. 이것을 영어로 표현하면 Defense-in-depth 라고 하는데 위키로 찾아보니 군대 용어라고 하는데 겹겹이 방어하여 피해를 최소화하는 방어 전술이래라 뭐래라... -_- 틀린말은 아니네..

아무튼, 이런 권한 설정을 위한 방법이 바로 Evidence 인데... 책에 보니까 이 Evidence 한국말로 '증거' 에 대해서 쏼라쏼라 뭐라뭐라 많이 써놨는데 주욱 읽어보니 다 잡소리고 걍 줄여서 한마디로 '보안 등급' 정도로 생각하면 되겠다. 단계별로 생성과정을 설명하자면 우선, object[] 타입으로 생성되는 변수가 하나 있어야 하는데 이 변수는 System.Security.SecurityZone 열거자와 System.Security.Policy.Zone 객체를 사용해서 사용자가 원하는 '보안 등급 영역'을 설정할 수 있다. 대표적인 예가 SecurityZone.Internet인데 인터넷의 보안 등급 영역을 사용하라는 말이다. 인터넷 영역의 보안 등급은 당연히 매우 높음이다. 왜냐면 보안 취약성이 높기 때문이지. 아무튼, 이렇게 생성한 object[] 변수를 Evidence 객체 생성자에 때려 넣으면 비로서 Evidence 객체가 생성된다.

이제 이 생성된 Evidence 객체를 AD를 만들때 붙여 넣으면 Application Domain 전체에 그 보안 등급을 적용하는게 되고 만약 특정 어셈블리를 로드할 때만 붙여 넣으면 해당 어셈블리에 대해서만 보안 등급을 적용하는 것이다.

뭐 구질 구질하게 설명하는 것보다 간단하게 코드로 살펴보자.

우선 특정 어셈블리에만 보안 등급을 설정하려면, 
object[] MyZone = { new Zone(SecurityZone.Internet)};
Evidence MyEvidence = new Evidence(MyZone, null);
AppDomain MyDomain = AppDomain.CreateDomain("MyDomain");
MyDomain.ExcuteAssembly("AnotherAssembly.exe", MyEvidence);

그리고 전체 Application Domain에 적용하고 싶다면,
object[] MyZone = { new Zone(SecurityZone.Internet)};
Evidence MyEvidence = new Evidence(MyZone, null);
AppDomain MyDomain = AppDomain.CreateDomain("MyDomain", MyEvidence);
MyDomain.ExcuteAssembly("AnotherAssembly.exe");

여기서 잘 보면 Evidence 객체를 생성할때 생성자에 두 개의 변수를 입력 받도록 되어 있고 두번째 녀석은 null로 되어 있는데 첫번째 인자는 host evidence이고 두 번째 인자는 assembly evidence이다. 첫번째 인자를 설정하면 두번째 녀석은 설정안해도 자동으로 첫번째 인자를 이용한다. 다만 프로그램에서 어셈블리 생성시 적용되는 보안 등급을 다르게 해야 한다면 두번째 인자도 설정해주면 된다. 뭐, 별로 그럴일은 없어 보이지만..



posted by 대갈장군
2010. 7. 1. 00:29 프로그래밍
아는 형한테서 받은 노트북이 너무 오래 되서 그런지 자꾸 CD를 못읽는 것이 문제의 시작이었다. XP 설치 시디를 어쩌다 인식하면 설치도중에 CD 내놓으라고 하면서 멈춰 버리기를 무한 반복하던중 구세주를 만났으니... 바로 다음 웹사이트 였다.http://www.boot-land.net/forums/index.php?showtopic=4900

여기 들어가면 USB_MultiBoot_10.zip 파일을 다운로드 받을 수 있다. 이 프로그램은 USB를 포멧하고 Windows XP나 기타 운영체제를 복사하도록 해주고 거기다 추가로 컴퓨터가 이 USB를 부팅시 제대로 인식할 수 있도록 각종 설정 파일을 설치하도록 도와 준다.

간단하게 말해서 윈도우 XP USB를 만들어준다. 다만 이것을 사용하기 위해서는 컴퓨터가 USB 부팅을 지원해야 한다. 다행히도 내가 받은 노트북은 BIOS 셋업에 보니 Harddisk 우선 순위에서 USB를 상위에 설정할 수 있게 되어 있었따.

우선 XP를 C 드라이브에 전체 복사 해 놓고 USB_MultiBoot_10.cmd 프로그램을 돌리면 cmd 창이 뜨면서 수동으로 각종 셋팅을 지정할 수 있는 창이 뜬다.


음, 마치 내가 해커가 된 기분... 캬캬캬

우선 0을 선택하고 엔터 때리면 USB-stick 옵션이 USB-Harddisk 옵션으로 바뀐다. 그리고 1번을 누르고 엔터 날리면 XP 복사본이 어디 있냐고 친절하게 물어온다. 간단하게 복사했던 XP 설치 시디 위치를 찾아주면 OK. 그리고 2번을 누르고 엔터 날려서 USB가 어느 드라이브에 꼳혀 있는지 알려준다.

그 외에 건드려야 할 것은 없다. 아, 그리고 위 과정에서 한번은 윈도우 설치에 대해 각종 정보를 입력하고 작은 윈도우가 뜨는데 걍 Unattended로 선택하고 필요한 정보, 뭐 사용자 이름, 시리얼 넘버 등등 같은거 설정해주면 된다. 걍 빈칸으로 넘어가도 상관없다.

다만 시리얼 넘버를 넣어주면 설치 중에 그것을 물어오지 않아서 편리하다.

그리고 나서 3번 누르고 엔터~ 그러면 마지막에 15분 걸리는데 XP 소스 파일 복사할래 말래 물어보는데 그냥 yes 날리면 뭐 잔뜩 복사한다. 끄읏! 그리고 USB 넣고 컴터 켜고 부팅한 다음 1번 선택하고 엔터.. 그러면 끄웃!
posted by 대갈장군
2010. 6. 25. 02:37 프로그래밍/C#
이벤트, 이벤트 핸들러. 이 두 단어는 API와 C++을 공부한 나에게는 아주 익숙하게 들린다. C#이 아닌 다른 언어에서는 이벤트란 API에서 비동기적으로 특정한 호출을 일으키는 명령을 의미하고 이벤트 헨들러는 그것을 받는 핸들러를 의미한다라고 아주 자연스럽게 이해가 된다.

하지만 C#에서 이벤트와 이벤트 핸들러는 좀 다른 의미다. 겹쳐지는 의미가 있기는 한데 바로 '비동기적 호출'이라는 점이다. C#에서 이벤트는 호출되어야 하는 함수의 목록을 의미한다. 중요한 것은 '목록'이라는 건데 이건 다수가 될 수 있다는 점이다. 즉, 멀티캐스트라는 것...

즉, 이벤트는 멀티캐스트 델리게이트로 구현되며 호출되어야 하는 함수 목록을 순차적으로 가진다... 그리고 이벤트에 의해 호출되는 함수를 '이벤트 핸들러'라고 한다. 이벤트를 핸들 하는 놈이라는 의미에서 이벤트 핸들러라 하는 갑다.

사실 C에 보면 메시지를 통해 이런 것을 구현했었다. 메시지를 특정 윈도우에 보내면 해당 윈도우의 메시지 처리 함수에서 메시지에 해당하는 함수를 구현하는 그런 방식이었다. 하지만 이것은 일단 복잡하고 비-객체 지향적이란다. 뭐, 객체 지향이네 아니네는 코딩을 어떻게 하느냐의 차이지만 C에서는 아무래도 언어 자체적인 객체 지향성이 C#에 비해 떨어진다는 의미다.

아무튼 이런 C의 메시지를 객체 지향적으로 포장한 것이 C#의 이벤트라는 것이다. 이벤트는 다음과 같이 선언한다.

엑세스지정자 event 델리게이트타입 이름;

이 위에 선언식을 보아하니 델리게이트와 좀 헷갈리지 않는가? 델리게이트의 선언은 다음과 같다.

엑세스지정자 delegate 리턴타입 이름(인수목록);

event를 선언하기 위해 사용된 델리게이트타입이라는 놈은 바로 델리게이트 타입 선언에 의해 선언된 놈이다. 즉, 예를 들면,

public delegate void MyDelegate(int a, int b);
public event MyDelegate
PrintOutEverything
MyEvent;

위와 같이 사용된다는 것... 델리게이트와 델리게이트 타입. 이 두 놈이 조금 복잡한듯 하나 위에 놈은 델리게이트의 타입 선언문이고 아래는 선언된 델리게이트 타입을 사용한다는 차이만 알면 될듯.

또 한가지 중요한 것은 이벤트 핸들러 즉, 호출 되는 함수는 리턴값을 가지지 않는다는 것. 원래 이벤트라는 것이 알려주기 위함이지 얻어오기 위함이 아니기 때문이다. 더군다나 이벤트 핸들러는 비동기적으로 호출되므로 리턴한다고 해도 받아 줄 놈이 없어졌을 수도 있다. 

그런데 자세히 보면 이벤트라는 놈은 사실 델리게이트타입을 재정의 한 것일뿐 특별한 차이점이 델리게이트와 비교해서 없다. 즉, 이벤트라는 지시자가 없더라도 델리게이트만으로도 모든 기능을 구현할 수 있다는 말이다. 사실 나도 이 부분을 MSDN 책을 보면서 '왜 두번 거쳐서 복잡하게 처리할까?' 라고 생각했던 부분인데 김상형님의 책에서 시원하게 설명해 주고 있다. 

이벤트는 델리게이트와 다르게 인수 정보를 사용자에게 보여주지 않고 감추며 멀티스레드에 안전하다는 장점이 있다. 즉, 사용자에게 보다 쉽고 안전한 방식으로 델리게이트를 사용하도록 했다는 것. 지금 보기에는 이것이 더 복잡해 보일지 모르나 비쥬얼 스튜디오의 마법사를 이용하면 이 과정이 사용자에게는 모두 숨겨지고 단 한번의 더블클릭으로 이루어 진다. 

그렇다면 실제로 이벤트를 폼에 적용하는 예를 살펴보자.

C#의 프로젝트를 폼 형태로 생성하면 메인 클래스는 반드시 Form을 상속받게 되어 있다. 이 Form 클래스에는 Paint라는 이벤트가 멤버로 들어가 있는데 바로 다음과 같이 선언되어 있다.

public event PaintEventHandler Paint;

여기서 PaintEventHandler가 바로 델리게이트 타입이다. 이 델리게이트 타입의 선언을 따라가보면 다음과 같이 선언 되어 있다.

public delegate void PaintEventHandler(Object sender, PaintEventArgs e);

보시다시피 리턴값이 없는 델리게이트 선언문이다. 고로 사용자는 위의 인수를 가지는 함수를 생성하여 Paint 이벤트에 대입만 해주면 화면 그리기 명령을 손쉽게 수행할 수 있는 것이다. 

이 방법 외에 윈도우에 그림을 그리는 방법이 한가지 더 있는데 바로 OnPaint 가상 함수를 재정의 하는 것이다. 이 경우에는 Control (부모)에 정의 되어 있는 OnPaint() 함수를 오버라이드 하는 것이다. 이렇게 하면 이벤트 핸들러를 사용할 필요가 없지. 

그렇다면 이 둘의 차이는? 만약 이 둘다 정의해 놓고 호출해 보면 오버라이딩 된 함수가 먼저 호출된다. 즉, OnPaint() 가상함수가 우선순위가 높다는 것이다.

헌데 막상 비주얼 스튜디오에서 더블클릭해서 이벤트 추가를 해보면 오버라이딩을 하지 않고 이벤트 핸들러를 사용하는 것을 알수 있다. 그 이유는 상속을 안받아도 되기 때문. 가상 함수를 오버라이딩 시킬려면 해당 함수를 가진 부모를 상속받아야 하는 불편함이 있다. 그에 비해 이벤트 핸들러는 델리게이트를 이용하므로 타입과 인수목록만 맞으면 자식 부모 관계 없이 호출 가능하다는 장점이 있다. 고로 이벤트 핸들러의 승리!

1. 이벤트의 정의는?
2. 이벤트 핸들러는 리턴값을 가지지 않는데 왜 그럴까?
3. 델리게이트와 이벤트는 어떻게 다른가?
4. 왜 Visual Studio에서 마법사로 함수를 추가하면 가상함수 오버라이딩 대신 이벤트 핸들러를 사용하는가?


'프로그래밍 > C#' 카테고리의 다른 글

Asynchronous programming  (0) 2017.05.31
C# - 제너릭 (Generic) 에 대한 질문  (0) 2010.06.18
Covariance and Contravariance  (0) 2010.06.18
델리게이트 - Delegate  (0) 2010.06.17
posted by 대갈장군
2010. 6. 18. 10:04 프로그래밍/C#
1. 제네릭이나 템플릿을 사용하는 이유? 역효과는?


2. 제네릭 타입 구체화 (Generic Type Instantiation) 이란?


3. 제네릭의 타입을 호출시 정확히 알려줘야 할 경우는 어떤 경우인가?


4. 제약 조건 (where)에서 가장 유용하다고 생각되는 것은 base, 즉, base를 상속받은 클래스이어야 한다는 조건인데 왜 그럴까?


5. 제네릭은 C++의 템플릿과 유사한데 C++과 무엇이 다른가?


6. 일반 컬렉션 (ArrayList)를 사용하게되면 예상되는 문제점은 없는가?


7. 일반 컬렉션 대신 제네릭 컬렉션 (List<T>)를 사용하게되면 왜 더 안전한 코딩인가?


'프로그래밍 > C#' 카테고리의 다른 글

Asynchronous programming  (0) 2017.05.31
C# 이벤트 - 객체 지향적 메시지  (0) 2010.06.25
Covariance and Contravariance  (0) 2010.06.18
델리게이트 - Delegate  (0) 2010.06.17
posted by 대갈장군
2010. 6. 18. 00:43 프로그래밍/C#
델리게이트에 대해서 공부하다 보니 공변성 (Covariance)반공변성 (Contravariance) 에 대한 이야기가 나왔다.

공변성과 반공변성... 뭔 소리인가... 라고 생각한다면 당신은 정상인...

수학 쫌 했다는 분들은 알아들을 수도 있겠다만 나는 처음 이 두 단어를 접하고 언어의 한계성을 느꼈다...

아무튼, 공변성과 반공변성은 사실 같은 원리에 근거한 같은 법칙을 말한다. 여기서 같은 원리란, 부모는 자식을 받아들일수 있으나 자식은 부모를 받아들일수 없다는 원칙이다. 쓰다보니 이런 패륜적인 원리일 줄이야... 헐.

일반적으로 자식 클래스는 부모 클래스로 부터 상속을 받아 여러가지 함수 및 멤버 변수를 추가하게 된다. 자, 그렇다면 자식은 양적으로 부모 보다 더 '많이' 가지고 있다. 반면 부모 클래스는 여러 자식들에게 공통으로 나누어 주어야 하므로 '적게' 가지고 있다.

부모/자식 클래스라는 헷갈리는 말 대신, 많다/적다로 표현하면 훨씬 더 이해가 빠르게 될 것이다. 그렇다면 부모 클래스에 자식 클래스를 대입하면 어떻게 되나? 다른 말로 적은 것 (부모)에 많은 것 (자식)을 넣게되면 자식이 가진 몇몇 함수 및 변수는 잘려 나가겠지만 그래도 잘라내면 그만이므로 문제가 없다.

헌데 반대로 자식에 부모를 대입한다면, 즉, 많은 것 (자식)에게 적은 것 (부모)를 대입한다면 문제가 생기는데 왜냐면 부모는 자식이 가지고 있는 것들을 가지고 있지 않기 때문에 '비어있는 공간'이 생기게 되고 이는 호출 오류를 일으킨다. 왜? 없는 것을 호출하니까...

그래서 부모(적은 것)는 자식(많은 것)을 받아들이지만 그 반대는 성립하지 않는다는 것이다.

함수를 살펴보면 "리턴타입 함수이름(인수)" 이런 형태다. 여기서 리턴 타입과 인수가 바로 '대입'이 일어나는 두 곳이다. 자 여기서 바로 공변성과 반공변성이 등장한다... 

일단 리턴타입의 경우 공변성을 가지는데 다른말로 풀어 쓰자면 위에서 설명한 기본 원리인 '부모는 자식을 받지만 그 반대는 성립하지 않는다'는 원칙을 눈으로 볼때 그대로 따른다는 의미다. 

즉, 선언한 델리게이트의 리턴 타입이 부모 클래스라면 자식 클래스 및 부모 클래스 타입을 리턴 타입으로 가지는 모든 함수를 받아준다는 이야기다. 당연히 그 반대는 안되죠오..

이제 남은 것은 '인수'인데, 여기서 이른바 '
반공변성
반공변성'
이 나온다. 말은 참 어려운것 같지만 한번만 풀어서 생각하면 같은 원리다. 이 반공변성은 위에서 말한 리턴 타입의 경우와 정 반대로 동작한다는 의미에서 붙여진 이름이다.

이것은 언제 '인수'가 대입 되는가를 생각해 보면 이해가 된다. 리턴타입의 경우 델리게이트 객체에 함수를 대입할 때 바로 '대입'이 일어나지만 '인수'는 델리게이트 객체에 함수를 대입할 때가 아니라 그 후에 대입된 함수를 호출 할 때 '대입'이 일어난다. 

고로 리턴 타입과는 달리 인수는 비교해야 하는 시점이 호출할 때이므로 델리게이트 객체에 함수를 대입할 때의 인수가 호출 시점의 인수보다 '부모'이어야 한다. 이것이 코드에서 보았을 때는 공변성과 정반대로 보이게 되므로 이름을 반공변성이라고 붙였다만 사실 알고보면 똑같은 원리에 근거한 것이다.

정리하자면 다음과 같이 말할 수 있다.

리턴타입 델리게이트객체(인수) = 리턴타입 함수객체(
);

위의 공식을 보면 좌측 리턴 타입이 우측 리턴 타입보다 큰데 이것은 좌측 리턴 타입이 우측 리턴 타입을 받아준다는 의미고 마찬가지로 우측 인수가 좌측 인수보다 크므로 받아준다는 의미다. 

이런 공변성과 반공변성을 잘 알고 있으면 델리게이트의 활용에 큰 도움이 될듯..







'프로그래밍 > C#' 카테고리의 다른 글

Asynchronous programming  (0) 2017.05.31
C# 이벤트 - 객체 지향적 메시지  (0) 2010.06.25
C# - 제너릭 (Generic) 에 대한 질문  (0) 2010.06.18
델리게이트 - Delegate  (0) 2010.06.17
posted by 대갈장군
2010. 6. 17. 23:31 프로그래밍/C#
델리게이트는 C#에 등장하는 새로운 함수 포인터다. 델리게이트의 사전적 의미는 '대표자'라는 의미로 명사일때와 동사일때 발음이 차이가 있으며... 아차차, 주제가 이게 아니지. 이놈의 토플 공부 습관은 사라지지가 않는다. -_-

그렇다면 우선 델리게이트 선언 형식을 살펴보자. 간단하다.

액세스지정자 delegate 리턴타입 이름(인수목록);

이것은 C++에서 사용하는 함수 포인터의 선언 형식보다 훨씬 더 가독성이 좋은 편이다. 

일단, C++과 비교해서 C#의 함수 포인터인 델리게이트의 장점 및 차이점을 나열해 보자.

  • C++의 함수 포인터에 비해 타입 체크가 훨씬 더 엄격하므로 잘못된 대입을 미리 막는다.
  • Delegate는 함수 포인터와 다르게 '클래스 타입'이다. 
  • 인수 목록에 반드시 이름이 필요하다.

  • 사실 위 3가지 차이점 중에 2번이 가장 크고 중요한 차이라고 개인적으로 생각한다. delegate가 클래스 타입이라고 했는데 이는 즉, delegate가 클래스를 선언하는데 사용되는 일종의 명시자라는 말이다. 자, 클래스라면 생성자를 사용해서 객체화를 하여 사용해야 한다. 고로 delegate를 사용하기 위해서는 delegate 클래스 타입으로 임의의 객체의 선언 및 생성자를 이용한 객체화가 요구된다.

    추가로 delegate는 엄연히 독립 타입이므로 어디에나 선언 가능하며 클래스 내부에 선언되면 액세스 지정자 (public, private) 를 줄수가 있다. 만약 외부에 전역으로 선언되면 엑세스 지정자는 굳이 필요하지 않다.

    다음과 두 함수를 보면 인수목록이 같고 리턴 타입이 같으므로 delegate를 쓸수 있다.

    public static void MyFunction1(int input) { Console.WriteLine("MyFunction1" + input); }
    public static void MyFunction2(int input) { Console.WriteLine("MyFunction2" + input); }

    이 두 함수를 가리키기 위해 델리게이트 타입의 클래스를 전역으로 선언한다면 다음과 같이 선언 할 수 있다.

    delegate void MyDele(int a);

    그리고 이제 이 선언된 MyDele 클래스의 객체를 객체화 (생성자를 이용한 구현) 를 하기만 하면 포인터 처럼 바로 사용가능하다.

    MyDele mDele;
    mDele = new MyDele(MyFunction1); //또는 mDele = MyFunction1;
    mDele(50);
    mDele = new MyDele(MyFunction2); //또는 mDele = MyFunction2;
    mDele(100);

    다시 한번 강조하지만 delegate는 클래스 타입이므로 delegate를 사용하기 위해서는 우선 delegate 클래스 타입으로 특정한 리턴 타입과 인수 목록을 가지는 클래스(MyDele)를 생성하고 이 클래스의 객체(mDele)를 생성하여 생성자로 가리킬 함수(MyFunction1 또는 MyFunction2)를 대입하면 만들어진 객체가 바로 그 함수가 된다.

    일단 C++ 방법에 비해 복잡해 보이지만 훨씬 더 '
    세련된
    세련된' 방식이다. 참고로 new로 선언한 후 해제하지 않았는데 우리 뒤에는 항상 Garbage Collector 가 알아서 쓰레기를 수거해 가는 것을 잊지 말자. +.+

    함수 포인터나 델리게이트의 장점은 바로 같은 인수로 리턴 타입을 가지는 여러개의 함수를 돌아가면서 가리킬 수 있다는 것인데 이런 장점은 사실 확 와닿지가 않는다. 이것의 장점을 몸으로 느끼기 위해서는 프로그램이 조금 커야 하며 설계 부터 상속을 염두해두고 만들어야 하며 가상함수들이 즐비해야 비로서 '아~ 이래서 델리게이트가 무려 챕터 하나를 차지하는구나...' 하고 느낄것이다.

    마지막으로 C++의 함수 포인터와 다른 큰 차이점으로 델리게이트는 다른 클래스에 속한 메서드를 가리킬 수 있다는 점이다. C++의 함수 포인터의 경우 임의의 클래스에 선언되어 있을 경우 그 해당 클래스 내부에 있는 메서드 만을 가릴킬 수 있었는데 델리게이트는 그것을 극복했다. 

    이것도 사실 '화악~' 마음에 와 닿지는 않지만 C++의 함수 포인터보다는 훠~얼씬 더 좋다는 것 정도는 알겠다. 

    정리하자면 C++의 함수 포인터는 단순한 변수였다. 말그대로 주소를 저장하는 포인터 였으나 C#에서 등장한 델리게이트라는 놈은 함수 포인터보다 훨씬더 가독성이 좋으며 독립적인 클래스 타입으로써 객체를 생성하여 사용한다는 점이 큰 차이다... 암튼, 당연히 델리게이트가 함수 포인터 보다는 좋다는 이야기지...

    더 많은 이야기를 하고 싶지만 코드가 들어가면 이야기가 너무 길어질 것 같아서 오늘은 여기까지만... 다음엔 공변성과 반공변성에 대해서 토크를 해야 할 듯...



    '프로그래밍 > C#' 카테고리의 다른 글

    Asynchronous programming  (0) 2017.05.31
    C# 이벤트 - 객체 지향적 메시지  (0) 2010.06.25
    C# - 제너릭 (Generic) 에 대한 질문  (0) 2010.06.18
    Covariance and Contravariance  (0) 2010.06.18
    posted by 대갈장군
    2010. 3. 18. 06:25 프로그래밍/C++

    불과 몇년 전만 하더라도 멀티 스레드를 사용하는 프로그램은 드물었다. 오늘날의 인터넷 서버 프로그램은 다수의 클라이언트 연결을 위해 여러개의 스레드를 이용한다. 효율과 생산성을 최대화 하기 위해서 트랜잭션 서버들은 분리된 여러개의 스레드를 이용하곤 한다. GUI 프로그램 또한 오랜 시간이 걸리는 작업은 분리된 스레드를 통해 백그라운드로 작업을 하면서 동시에 사용자의 입력은 실시간을 받아들인다. 이런식으로 나열하면 수도 없이 많다. 그만큼 멀티 스레드는 보편화 되었다는 것.

    기존의 C++ Standard는 멀티 스레드에 대해 언급조차 하지 않는다. 심지어는 프로그래머에게 멀티 스레드 C++ 프로그램이 작성 가능한지 조차 알려주지 않는다. (과거 C나 C++ 은 멀티 스레드 환경에서 설계 되지 않았기 때문) 물론 기본적인 함수를 이용한 멀티 스레드 프로그램이 작성 불가능 한것은 아니지만, 프로그래머는 그럼에도 불구하고 운영체제가 지원하는 스레드 관련 라이브러리를 이용해서 작성한다. 자, 이것은 두가지 문제를 가지고 있다. 우선, 이 운영체제에서 제공하는 라이브러리들은 거의 C 라이브러리들인데다가 C++에서 사용시에는 주의해야 한다. 그리고 각각의 운영체제는 자신만의 멀티 스레드 조작 함수를 사용한다는 것이다. 이렇게 만들어진 코드는 우선 'Non-Standard(비기준)' 인데다가 'Non-Portable(비호환성)' 이다. 운영체제마다 쓰는 함수가 다르니 윈도우에서 돌리던 프로그램 유닉스에서 돌리려면 코드를 다 뜯어 고쳐야 한다는 말... 고로, Boost.Therads 라는 라이브러리를 만들었다는 말이다. 이 두 문제를 한방에 해결하기 위해서...

    Boost는 C++ Standards Committee Library Working Group의 멤버들이 C++의 새로운 라이브러리를 만들이 위해 시작되었다. 현재 대략 2000명이나 된다구. 수많은 boost의 라이브러리를 thread-safe 하게 사용하기 위해서 바로 이 Boost.Threads가 만들어 졌다...

    많은 C++ 전문가들이 Boost 디자인을 위해 생각들을 내어놓았고 인터페이스는 바닥부터 차근 차근 설립한 기초공사부터 시작했단다. 걍 원래 있는 C 스레드 API 함수를 가져다 쓰는게 아니라 이말이다. 많은 C++의 특징들이 흡수되었고 (예로 생성자, 파괴자, 함수 객체, 템플릿등) 인터페이스를 보다 유연하게 만들었다. 현재 Boost가 돌아가는 환경은 POSIX, Win32, Mac 등이래요. 뭐 거의 크로스 플랫폼을 달성했다고 봐도...

    Thread Creation

    boost::thread는 std::fstream 클래스가 파일을 표현하는 것과 같은 방식으로 스레드의 실행을 표현한다. Default 생성자가 현재의 실행 스레드를 나타내는 객체를 생성한다. 오버로딩된 생성자는 입력 변수가 없고 리턴하는 것이 없는 함수 객체를 받는다. 이 생성자는 새로운 실행 스레드를 시작하는데 이는 결국 함수 객체의 호출이며 해당 함수의 호출이다.

    첫눈에 보기에는 이 방식의 디자인이 기존 C의 방식보다 후져 보인다. 왜냐면 기존 C는 void 포인터를 새 스레드 생성시 데이터를 넘겨주기위해 사용할 수 있기 때문이다. 하지만 Boost.Threads는 함수 포인터 대신 함수 객체를 사용하기 때문에 이 함수 객체가 스레드에 필요한 데이터를 소유 할 수 있다. 이 접근법은 타입 점검을 정확히 하며 보다 유연하다. 다른 함수 라이브러리들, 예를 들어 Boost.Bind와 같은 것과 연동되면 이 방식은 어떤 형태의 데이터라도 (많든 적든) 새로 만들어진 스레드로 전달하기가 매우 쉽다. 

    현재로써 Boost.Threads로 할수 있는건 많지는 않다. (이 글이 작성된게 2002년인걸 감안하면 이렇게 말할만도 하다.) 사실 딱 두개의 연산자만 사용할 수있다. (지금은 바뀌었겠지?) == 과 != 연산자를 이용하면 간단하게 같은 스레드인지 아닌지 확인 가능하다. 그리고 boost::thread::join을 이용하면 스레드가 작업을 다 할때까지 기다릴수 있다. 다른 스레드 라이브러리들은 스레드와 관련된 다른 작동을 할 수 있게 도와 준다. (예를 들면 우선순위 설정) 

    아래 리스트 1은 아주 간단한 boost::thread 클래스의 사용을 보여준다. 새로운 스레드가 생성되고 이 스레드는 아주 간단한 hello() 함수를 호출한다. 이걸 하는 동안 main 스레드는 대기한다. 
    #include <boost/thread/thread.hpp>
    #include <iostream>
    

    void hello() { std::cout << "Hello world, I'm a thread!" << std::endl; } int main(int argc, char* argv[]) { boost::thread thrd(&hello); thrd.join(); return 0; }

    VS 2008으로 위 코드를 돌려봤더니 잘 돌아간다. 그리고 실행중에 디버깅을 해보면 아래 그림에 나온 것 처럼 hello() 함수 호출후 join 함수로 스레드 실행의 완료를 기다린후 return 0; 바로 직전에 브레이크 포인트를 자세히 보면 친절하게도 '이 프로세스나 스레드는 바로 직전에 변경되었습니다.'라고 알려준다. 참 친절하신 VS 2008. :)

     

    Mutexes

    멀티 스레드의 최대 약점은 자원 경쟁에 의한 오작동이다. 프로그램 짜본 사람이라면 누구나 알것이다. 이것은 두개 이상의 스레드가 동시에 공유된 자원에 접근하여 데이터를 변경할때 발생하는 데이터의 부정확성과 불일치를 말한다. 이것을 방지하기 위한 여러방법중 하나가 바로 Mutex 다른말로 하면 Mutual Exclusion (상호 배타)이다. 이 뮤텍스는 오직 하나의 스레드만이 공유자원에 지정된 시간에 접근하도록 만든다. 고로, 스레드가 공유 자원에 접근하기 위해서는 우선 뮤텍스를 'lock' 해야 한다. 즉, 공유자원이라는 집안에 들어가서 문을 걸어 잠그는 것이다. (다른 놈 못들어오게) 

    뮤텍스의 컨셉은 여러가지가 있는데 Boost.Threads가 지원하는 가장 큰 두개의 카테고리는 Simple Mutex와 Recursive Mutex다. Simple Mutex는 말그대로 단순한 뮤텍스로써 오직 한번만 잠글수 있다. 만약 자신이 잠근 뮤텍스를 또 다시 잠글려고 하면 바로 데드락이 걸리고 이것은 '무한 대기(데드락)' 상태를 유발한다. 반면 Recursive Mutex (한국어로 말하면 재귀 뮤텍스이군)의 경우는 하나의 스레드가 뮤텍스를 여러번 잠글수 있으나 대신 반드시 잠근 횟수만큼 풀어야 다른 스레드가 공유자원을 사용할 수 있다는 것이다. 집에 들어와서 자물쇠를 현관문에 100개 달았으면 100개 다 풀어야 남이 들어온다는 이야기.

    이 두가지의 카테고리 내부에서 또 여러가지 형태로 나뉘는데 이것은 어떤 식으로 뮤텍스를 잠그느냐에 따라 나뉜다. 세가지 방법으로 뮤텍스를 잠글수 있는데,
    1. 다른 스레드가 뮤텍스를 잠그지 않을때까지 대기
    2. 만약 다른 스레드가 뮤텍스를 잠궈놓은 상태라면 바로 리턴하기
    3. 다른 스레드가 뮤텍스를 잠그지 않을때까지 대기 하거나 혹은 일정 시간만 대기 하다가 리턴
    최고의 뮤텍스 타입은 아무래도 recursive 타입인데 이 것은 위의 세가지 잠그기 방식을 모두 지원하기 때문이라는데... 반면 좋은 대신 오버헤드가 크다는 단점이 있다. 아무튼, 총 여섯 가지의 뮤텍스를 사용자가 선택가능하며 다음과 같다. 

    boost::mutex
    boost::try_mutex
    boost::timed_mutex
    boost::recursive_mutex
    boost::recursive_try_mutex
    boost::recursive_timed_mutex.

    데드락은 매번 뮤텍스를 잠글때마다 잠근 횟수만큼 풀지 않았을때 발생한다. 이것이 가장 흔한 데드락의 한 종류인데 Boost.Threads는 이 데드락을 최대한 최소화 시킨다. (발생 할 수는 있다는 이야기) 어떤 뮤텍스이건 간에 직접적인 뮤텍스 잠금 및 해제 함수는 없다. 즉, 간접적인 방식으로 잠그고 푸는데 이것이 안전성을 높인다는 이야기. 이걸 Scoped Lock 이라고 한다는데 Douglas Schmidt, Michael Stal, Hans Rohnert, and Frank Buschmann. Pattern-Oriented Software Architecture Volume 2 Patterns for Concurrent and Networked Objects (Wiley, 2000) 를 참조하라고 해놨다. 헤헤, 거의 논문 수준이다. :)

    아무튼, C++이 기본적으로 예외 발생시나 객체 파괴시 항상 파괴자를 호출하므로 파괴자에 뮤텍스를 풀어주는 장치를 둠으로써 이런 데드락을 줄인다는 이야기 란다. 다만 주의 할 것은 이 Scoped Lock 패턴이 비록 풀어주는 건 확실히 풀어주지만 예외가 발생해서 풀어졌을 경우 공유자원이 변경되었을 가능성이 있단다. 
     
    아래의 코드는 간단한 뮤텍스 사용법을 보여준다.
    #include <boost/thread/thread.hpp>
    #include <boost/thread/mutex.hpp>
    #include <iostream>
    

    boost::mutex io_mutex;

    struct count { count(int id) : id(id) { }

    void operator()() { for (int i = 0; i < 10; ++i) { boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << i << std::endl; } }

    int id; }; int main(int argc, char* argv[]) { boost::thread thrd1(count(1)); boost::thread thrd2(count(2)); thrd1.join(); thrd2.join(); return 0; }

    위의 코드를 보면 thrd1과 thrd2라는 스레드를 생성하여 메인 스레드는 이 두 스레드가 끝날때까지 대기한다. 이 두 스레드는 count라는 구조체를 호출하는데 이 구조체 내부에 std::cout 함수가 호출되어 0에서 9까지의 숫자를 출력한다. 재미있는 것은 이 std::cout은 이른바 공유 자원이다. 화면에 글자를 출력해내는 함수는 한순간에 오직 하나의 스레드에 의해서만 점유되어야 함이 마땅하다. 고로 이 프로그램을 돌려보면 다음과 같이 각각의 스레드에 대해 0에서 9까지의 출력이 차례대로 나온다. 


    헌데 만약 위 코드에서 뮤텍스를 잠그는 명령을 쏙 빼버리면 다음과 같은 결과가 나온다. 


    잘 보면 thrd1의 "1"을 출력한후 스레드가 변경되어 thrd2의 "2: "를 출력한 후 다시 thrd1로 변경되어 ": 0"를 출력한 후 또 다시 thrd2로 변경되어 "0"을 출력한다. 이렇게 규칙성있게 출력되는 이유는 분명히 CPU가 공정하게 스레드에게 실행 시간을 나눠주기 때문일 것이다. 두 스레드가 같은 우선순위를 가지므로 둘이 똑같이 CPU 시간을 나눠 받는 것이다. 

    그리고 위 코드를 다시 보면 함수 객체를 작성하는데 이거 매번 하려면 굉장히 불편하다. 이럴때 사용할 수 있는 것이 Boost.Bind란다. 다음 코드를 보면 새 함수 객체를 바인딩을 통해 값을 전달하여 생성하는 예를 보이고 있다. 

    Listing 3: Using the Boost.Bind library to simplify the code in Listing 2

    // This program is identical to
    // listing2.cpp except that it
    // uses Boost.Bind to simplify
    // the creation of a thread that
    // takes data.
    

    #include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/bind.hpp> #include <iostream>

    boost::mutex io_mutex;

    void count(int id) { for (int i = 0; i < 10; ++i) { boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << i << std::endl; } } int main(int argc, char* argv[]) { boost::thread thrd1(boost::bind(&count, 1)); boost::thread thrd2(boost::bind(&count, 2)); thrd1.join(); thrd2.join(); return 0; }


    Condition Variables

    종종 걸어 잠그기 만으로는 충분하지 않은 경우가 있다. 즉 뮤텍스만으로는 해결 안되는 상황을 말한다. 어떤 자원이 있는데 그 자원을 사용하려면 반드시 특정 상태가 되어야 한다고 치자. 예를 들어 임의의 데이터가 네트워크를 통해서 들어오기를 기다려야 하는 상황이 있다면 뮤텍스는 이런 형태의 점검을 지원하기에는 적합하지 않다는 말이다. (아래를 보면 이해가 될것, 왜 뮤텍스로는 부족한지) 그래서 다른 형태의 동기화가 필요한데 이것이 바로 '조건 변수 (Condition Variable)'를 이용하는 것이란다.

    하나의 조건 변수는 항상 뮤텍스와 공유 자원 사이에 접합 지점에서 사용이 된다. 하나의 스레드가 먼저 뮤텍스를 잠그고 난 다음 공유자원이 적합한 상태인지를 체크하게 된다. 만약 해당 상태가 아직 원하는 상태가 아니라면 스레드는 대기한다. 이 대기 상태에서 스레드는 뮤텍스를 풀어줌으로써 다른 스레드가 공유자원에 대해 남은 일을 처리하여 적합한 상태 (Ready 상태)로 변경할 수 있게 된다. 이것이 바로 조건 변수가 필요한 이유. 무한 대기가 아닌 조건 대기. 또한 이것은 스레드가 대기 상태에서 복귀할때 바로 뮤텍스를 걸어 잠그는 것까지 한다. 다른 스레드가 공유자원에 대한 작업을 마쳐서 조건 변수의 상태를 변경하게 되면 이것을 즉각적으로 대기하던 스레드에게 알려준다. 그로써 대기중인 스레드는 바로 복귀함과 동시에 완료된 공유자원에 뮤텍스를 이용해 잠근다. 

    생각보다 현명한데? :) 조건 변수라고 하길레 그냥 정적 변수 하나 선언해 놓고 true / false 값 판단인줄 알았는데 제법 인공지능을 가지고 있다. 
    #include <boost/thread/thread.hpp>
    #include <boost/thread/mutex.hpp>
    #include <boost/thread/condition.hpp>
    #include <iostream>
    

    const int BUF_SIZE = 10; const int ITERS = 100;

    boost::mutex io_mutex;

    class buffer { public: typedef boost::mutex::scoped_lock scoped_lock; buffer() : p(0), c(0), full(0) { } void put(int m) { scoped_lock lock(mutex); if (full == BUF_SIZE) { { boost::mutex::scoped_lock lock(io_mutex); std::cout << "Buffer is full. Waiting..." << std::endl; } while (full == BUF_SIZE) cond.wait(lock); } buf[p] = m; p = (p+1) % BUF_SIZE; ++full; cond.notify_one(); }

    int get() { scoped_lock lk(mutex); if (full == 0) { { boost::mutex::scoped_lock lock(io_mutex); std::cout << "Buffer is empty. Waiting..." << std::endl; } while (full == 0) cond.wait(lk); } int i = buf[c]; c = (c+1) % BUF_SIZE; --full; cond.notify_one(); return i; } private: boost::mutex mutex; boost::condition cond; unsigned int p, c, full; int buf[BUF_SIZE]; };

    buffer buf;

    void writer() { for (int n = 0; n < ITERS; ++n) { { boost::mutex::scoped_lock lock(io_mutex); std::cout << "sending: " << n << std::endl; } buf.put(n); } } void reader() { for (int x = 0; x < ITERS; ++x) { int n = buf.get(); { boost::mutex::scoped_lock lock(io_mutex); std::cout << "received: " << n << std::endl; } } } int main(int argc, char* argv[]) { boost::thread thrd1(&reader); boost::thread thrd2(&writer); thrd1.join(); thrd2.join(); return 0; 

    }위 코드의 클래스는 FIFO 버퍼를 추상화했는데 내부 private 멤버 변수인 mutex를 사용해서 스레드에 안전하게 설계되었다. put과 get 함수는 condition variable을 사용하여 스레드가 적합한 상태로 공유자원이 변경되도록 기다리게 했다. 두 스레드가 만들어지면 하나는 100개의 정수를 이 버퍼에 넣고 다른 하나는 하나씩 빼낸다. 허나 이 버퍼는 최대 10개까지만 대기할 수 있으므로 두 스레드스 서로 기다려 준다. 잘보면 전역변수로 io_mutex를 선언하여 std::out에 대해서 뮤텍스를 사용함을 알수 있다. 

    Thread Local Storage

     이 놈은 예전에 내가 다른 글에서 언급한 놈이다. http://diehard98.tistory.com/entry/프로그램-프로세스-스레드 를 보면 왜 TLS가 필요한지 설명하고 있다. 기본적으로 함수들은 재진입을 염두하지 않는다. 고로 하나의 스레드가 호출중인 함수를 다른 스레드가 또 호출하면 '불안정한' 상태가 될 수 있다는 의미. 재진입을 고려하지 않은 함수는 정적 데이터를 유지하는데 대표적인 예로 std::strtok가 있다. 이 놈은 재진입이 불가한데 왜냐면 함수가 호출되면 실행 동안에 정적 변수에 값을 저장해 놓기 때문이다. (고로 다른 스레드가 실행 와중에 다시 그 함수를 호출하여 사용하면 같은 정적 변수에 또 다른 값을 덮어 씌워 버릴것이다.)

    이런 재진입이 불가한 함수들은 두가지 방법으로 재진입이 가능한 함수로 바꿀수 있다. 우선 첫번째는 포인터나 레퍼런스를 받아서 정적 변수를 사용하던 방식을 바꾸는 것이다. 예를 들어 POSIX의 strtok_r 함수는 std::strtok의 재진입을 가능하게 만든 함수로 이 함수는 입력 변수로 정적 변수를 사용하는 대신 char** 을 받는다. 이 해결법은 쉽고 좋다. 그러나 이렇게 하면 모든 공개된 인터페이스 (호출 방식)을 바꿔야 한다. 즉, 많은 코드의 변화를 가져온다는 이야기. 이 방법 말고 다른 방법인 두번째 방법이 바로 스레드에 독립적인 저장 공간을 주는 방식이다. 스레드의 독립적인 저장공간을 영어로 Thread Local Storage 혹은 Thread-Specific Storage라고 한다.

    TLS는 각 스레드 별로 할당되는 고유 공간이다. 멀티스레드 라이브러리는 이 독립공간을 임의의 스레드가 접근할수 있도록 인터페이스를 제공한다. 고로 각각의 스레드는 스레드 객체의 고유 데이터를 소유하게 된다. 이 말은 다른 스레드가 접근 할 수 없다는 말이고, 즉, 자원 경쟁으로부터 자유롭다는 말. 문제는, 이 TLS가 일반 데이터 엑세스보다 느리다는 점. 하지만 첫번째 방법보다는 편한데 왜냐면 인터페이스를 안바꿔도 되니까 코드 변경이 필요없기 때문이다.

    Boost.Threads는 boost::thread_specific_ptr이라는 스마트 포인터를 이용해서 TLS에 각 스레드가 접근 할 수 있도록 도와준다. 스마트 포인터란 참조된 횟수를 스스로 계산하여 자폭하는 영리한 포인터를 말한다. 각 스레드는 우선 이 스마트 포인터의 객체에 접근하는데 이때 이 값이 NULL인지 먼저 확인해야 한다. NULL이면 초기화가 필요하다는 말. 그리고 Boost.Threads 라이브러리는 스레드 종료시 이 TLS를 알아서 청소해준다. 왜냐면 스마트 포인터니까~
    #include <boost/thread/thread.hpp>
    #include <boost/thread/mutex.hpp>
    #include <boost/thread/tss.hpp>
    #include <iostream>
    

    boost::mutex io_mutex; boost::thread_specific_ptr<int> ptr;

    struct count { count(int id) : id(id) { }

    void operator()() { if (ptr.get() == 0) ptr.reset(new int(0));

    for (int i = 0; i < 10; ++i) { (*ptr)++; boost::mutex::scoped_lock lock(io_mutex); std::cout << id << ": " << *ptr << std::endl; } }

    int id; }; int main(int argc, char* argv[]) { boost::thread thrd1(count(1)); boost::thread thrd2(count(2)); thrd1.join(); thrd2.join(); return 0; }

    위 코드를 실행해 보면 각 스레드 별로 1에서 10까지 출력한다. 그리고 당근 std::out은 뮤텍스로 보호!

    Once Routines

    이제 남은 것은 딱 하나. 어떻게 생성자를 멀티스레드에 안전하게 만드는가다. 예를 들어 전역으로 사용되는 객체가 있다면 멀티스레드 환경에서 주의해야 할 것은 이 객체가 오직 한번만 생성자를 호출해야 하며 스레드들이 동시에 생성자를 호출하는 것을 막아야 한다.

    이 것을 해결하는 방법은 이른바 "Once routine" 이라는 방식의 사용이다. 이 것을 이용하면 해당 함수나 변수는 프로그램에 의해 오직 한번만 호출된다. 만약 다수의 스레드가 이 함수를 동시에 호출한다면 그들중 오직 하나만이 이 함수에 접근할 수 있다. 이것을 가능하게 해주는 방법은 바로 랩핑함수를 이용해서 한번 더 점검을 하는 거란다. 고로 멀티 스레드 환경에서 '오직 한번' 초기화하기 문제는 해결되었다. boost::call_once를 이용하면 되고 플레그 타입인 boost::once_flag를 BOOST_ONCE_INIT 매크로를 이용해 초기화 하면 된다네.

    Listing 6: A very simple use of boost::call_once

    #include <boost/thread/thread.hpp>
    #include <boost/thread/once.hpp>
    #include <iostream>
    

    int i = 0; boost::once_flag flag = BOOST_ONCE_INIT;

    void init() { ++i; }

    void thread() { boost::call_once(&init, flag); } int main(int argc, char* argv[]) { boost::thread thrd1(&thread); boost::thread thrd2(&thread); thrd1.join(); thrd2.join(); std::cout << i << std::endl; return 0; }

    뭐 위 코드를 보면 flag를 선언할때 BOOST_ONCE_INIT 매크로 사용했고 전역 변수 i를 두개의 스레드가 동시에 접근하는데 둘 다 call_once로 호출하므로 두 스레드중 오직 하나만 접근 하여 초기화 한후 값을 하나 증가 시킨다. 고로 다른 스레드는 해당 함수를 더 이상 호출 할 수 없으므로 출력 값으로는 1이 출력된다. 만약 두 스레드가 모두 변수에 호출가능 했다면 i 값은 2가 되어야 한다.

    The Future of Boost.Threads

    Boost.Threads를 위한 몇가지 계획이 있는데 추가될 놈으로 boost::read_write_mutex가 있단다. 이 것은 멀티 스레드들이 공유자원을 동시에 읽기 가능하게 하고 단, 쓸때는 한놈만 쓰게 한다. 그리고 추가로 boost::thread_barrier 라는 놈을 만들건데 이 놈은 특정 스레드들이 모두 특정한 위치까지 들어오도록 기다린단다. 흠... 용이하겠는데? 그리고 또 boost::thread_pool을 계획중인데 이 녀석은 스레드를 매번 생성 파괴하지 않고 짧은 실행을 비동기로 할 수 있게 해주는 놈이란다. 찾아보니 이 놈들 벌써 구현됬다... ㅋㅋㅋ

    그리고 이 Boost.Threads는 차기 C++ 표준에 들어갈 수도 있다는 말과 함께 이 긴 글을 마무리한다...

    추가로 참조된 책 및 웹사이트, 논문등

    Notes

    [1] The POSIX standard defines multithreaded support in what’s commonly known as the pthread library. This provides multithreaded support for a wide range of operating systems, including Win32 through the pthreads-win32 port. However, this is a C library that fails to address some C++ concepts and is not available on all platforms.

    [2] Visit the Boost website at <http://www.boost.org>.

    [3] See Bjorn Karlsson’s article, “Smart Pointers in Boost,” in C/C++ Users Journal, April 2002.

    [4] Douglas Schmidt, Michael Stal, Hans Rohnert, and Frank Buschmann. Pattern-Oriented Software Architecture Volume 2 Patterns for Concurrent and Networked Objects (Wiley, 2000).


    이제 내가 이야기를 할 차례다. 일단 이 글쓴이에게 감사하는 바이다. 2002년에 작성된 글이라 오래된 느낌이 있지만 매우 영리하고 정확한 표현으로 이해를 돕는다. 그리고 중간중간 효과적인 소스코드로 더더욱 이해가 쉽게 해주었다.

    Boost 라는 그 자체에 대해서 경이로움을 느낀다. C++이 멀티스레드에 약하다는 것은 잘 알고 있었지만 그것을 이렇게 잘 커버해주는 이런 대형 라이브러리가 있는지는 정확히는 몰랐다. 대단하다...

    스레드의 생성과 선언 및 관리가 운영체제마다 다르기 때문에 윈도우에서 작성한 코드는 유닉스에서 안돌아 가지만 이 Boost 라이브러리를 이용하면 코드 변경 없이 가능하겠다. 그리고 스레드마저 객체화하여 클래스로 표현하므로 객체 지향에도 걸맞는다. 

    오버헤드가 좀 있겠지만 객체로 표현한다는 장점에 비하면 적지 않나 싶다. 이 글이 너무 길어지면 곤란하므로 여기까지만 쓰겠다. 

    posted by 대갈장군
    2010. 3. 11. 00:56 프로그래밍
    사용자 삽입 이미지
    프로그램의 작성과 동작 원리는 위 그림처럼 간단한 편이다.

    우선 소스 코드들 (.cpp, .c) 이 컴파일러에 의해서 컴파일 되면 바이너리 (기계어) 코드로 변경되어 .obj (오브젝트 코드)들로 저장이 된다.

    그 후에 링커 (Linker)에 의해서 링크를 하게 되는데 이때 두가지 라이브러리를 사용할 수 있다. Static Library는 정적 라이브러리로써 실행 파일 (.exe)에 아예 포함되어 버리는 라이브러리다.

    고로 .exe 자체가 소유하게 되므로 실행시에는 이 파일이 필요가 없다.

    반대로 Import Library는 프로그램이 사용하는 특정 함수를 동적으로 가져다 쓰는 것으로써 실행을 위한 코드를 .exe 파일이 소유하는 것이 아니라 '어느 파일(DLL 파일들)에 니가 필요한 함수 뭐뭐가 있다더라' 라고 알려만 준다.

    고로, .exe 파일을 실행할 때, 만약 내가 특정 함수를 DLL에서 불러와 사용했다면 반드시 해당 DLL과 .exe 파일을 연결 시켜 주어야 한다. 그렇지 않으면 .exe 는 실행 할 수가 없다는 에러 메세지를 띄운다.

    .NET Framework는 이와는 조금 다르게 동작하기는 하지만 기본적으로 프로그램이 동작하는 원리는 비슷하다.

    참조할 만한 관련글들
    posted by 대갈장군
    2010. 3. 11. 00:29 프로그래밍/C
    예전에 다른 글에서 Visual Studio의 Code Generation 옵션에 대해서 이야기 한 적이 있다. 

    위 글에서는 간단하게 옵션에 대한 설명만 했지만 왜 싱글 스레드에서 멀티 스레드로 바뀌었는지에 대해서는 충분히 설명하지 못했다. 그래서 이 글을 추가로 작성한다.

    C 언어는 멀티 스레드 개념이 생기기 이전의 언어이므로 멀티 스레드 환경에서 발생하는 자원 경쟁 상태 (Race Condition)을 염두해 두지 않았다. 즉, 여러개의 스레드가 하나의 전역 변수를 공유하여 서로 바꾸려고 할때 발생하는 경쟁 상태를 말하는데 이것의 대표적인 예로 C 언어의 errno 전역 변수이다.

    이 전역 변수는 호출한 함수의 결과를 저장하는 전역 변수인데 싱글 스레드의 경우에는 아무런 문제가 없으나 멀티 스레드의 경우 임의의 함수 호출 후 다른 스레드가 또 다른 함수를 호출하여 그 결과를 errno에 저장해 버릴 수 있기 때문에 100% 신뢰할 수 있는 결과를 가지지 않는다. 

    그래서 Microsoft사에서 내놓은 해결책이 멀티 스레드를 위한 새로운 런타임 라이브러리 사용이었다. 멀티 스레드를 위한 런타임 라이브러리는 멀티 스레드에서도 안정적인 C 함수들을 호출하므로 100% 신뢰할 수 있다. 


    내가 늘 불만인 것은 Visual Studio는 이렇게 중요한 옵션에 대한 설명이 너무나 부족하다는 것이다. 초보 프로그래머는 절대로 "왜 이걸 써야 하지?" 라는 궁극적인 질문에 대답을 할 수 없다.

    MSDN에 설명이 되어 있다고 하지만 그 마저도 C 언어를 안다는 가정하에 설명하고 있어서 읽다가 짜증만 난다. 쉽게 쉽게 풀어서 설명해 주면 안되겠니? 응?

    아무튼, 배포를 위한 프로그램을 개발한다면 거의 /MD를 사용하면 된다. D가 의미하는 것이 다이나믹 링크이므로 프로그램을 설치할 컴퓨터에 Visual Studio C++ 런타임 라이브러리만 잘 설치되어 있으면 프로그램은 무리 없이 돌아갈 것이다.

    하지만 한가지 더 중요한 것은 이렇게 /MD 설정으로만 모든 것이 해결되는 것은 아니며 스레드 생성시 반드시 _beginthreadex() 함수를 사용해라는 것인데, 이 함수는 스레드 생성 전에 스레드를 위한 독립 저장 공간을 생성하여 자원 경쟁을 예방한다. 이런 독립 공간을 스레드 지역 저장소라하며 원어로를 Thread Local Storage 라 부른다. 

    그리고 생성을 저 함수로 했다면 스레드를 죽일때에는 _endthreadex()를 사용해야 한다. 

    결론적으로,
    • C 언어는 싱글 스레드를 근간으로 작성되었으므로 멀티 스레드의 자원 경쟁 문제를 유발할 수 있다.
    • 그런 발생 가능한 문제 제거를 위해서는 멀티 스레드 런타임 라이브러리 옵션 (/MD)를 사용하면 된다.
    • 완벽한 문제 발생 제거를 위해서는 _beginthreadex()_endthreadex() 함수로 스레드를 생성하고 해제하라.


    '프로그래밍 > C' 카테고리의 다른 글

    C Language Constructors and Destructors with GCC  (0) 2015.02.12
    C Standard Library  (0) 2010.09.25
    호출 규약  (0) 2009.07.28
    posted by 대갈장군
    2010. 3. 6. 03:53 프로그래밍
    왜 언어코드 체계가 이렇게 짜증이 나는지 원... 에휴...

    1. SBCS (Single Byte Character Set) : ASCII 문자셋과 ANSI 문자셋 처럼 한바이트로 문자를 표현하는 코드. 고로 최대 256개 까지 가능

    문제는 꼴랑 256개 밖에 지원이 안되는 문제점. => 우리나라나 중국은 어쩌라고?
    그래서 나온것이 DBCS

    2. DBCS (Double Byte Character Set) : 원래 이 DBCS의 의미는 2바이트로 문자를 표현한다는 의미. 하지만 영문과 기호는 8비트로 표현하고 한글은 16비트로 표현하기 때문에 2바이트의 한글과 1바이트의 영문자가 공존 (지 맘대로 섞여 있음). 그래서 다른 말로 MBCS (Multi Byte Character Set) 이라고도 한다.

    MBCS의 문제는 글자가 몇개인지 셀때 혹은 바로 앞문자가 뭔지 찾을때 일일이 앞에서 부터 차례대로 검색해야만 알수 있다는 문제점 있음. 왜냐고? 같은 길이를 가지는 문자열이라도 1바이트 영문, 기호랑 2바이트 한글이 몇개씩 들어가 있는지 알수 없으니까.

    그래서 등장 한 것이 바로
    유니코드다....

    3. 유니코드 : 전세계 문자를 표현하는 코드 체계. 16비트로 표현하여 최대 65536개 문자 표현.

    이 유니코드는 각 문자가 2바이트씩 딱딱 차지하므로 MBCS에서와 같은 문제는 없다. 하지만 영문자의 경우 공간 낭비가 발생한다.


    유니코드의 장점
    모든 글자가 같은 크기를 가지므로 글자 수나 검색이 한결 용이함

    유니코드의 단점
    알파벳은 1바이트만 있어도 충분히 표현가능한데 2바이트를 사용하여 공간 낭비
    한글의 고어나 중국의 고어를 넣을 만한 충분한 여유 공간이 없다

    고로 위의 유니코드의 문제점 해결이 필요해 졌으니, 그 대안으로 등장한 것이 UTF 다.

    4. UTF (UCS Transformation format) : UTF-1, UTF-7, UTF-8, UTF-16, UTF-32등 유니코드 바이트 스트림에 따른 서로 다른 인코딩 방식을 총칭.

    UTF는 유니코드를 확장하기 위한 인코딩 방식인데
    UTF-16의 경우는, 우선 일반적으로 유니코드 2바이트 형식으로 표현가능한 글자는 그냥 2바이트로 표기하고 만약 한자 고어, 한국 고어 같은 것이 필요하면 4바이트로 확장하여 사용하는 것인데 한마디로 가변 코드 체계다.

    이것도 MBCS처럼 2바이트 유니코드와 4바이트 코드가 공존하므로 약간 다루기 까다롭다는 문제가 있구만..

    UTF-8의 경우는 또 조금 다른데 이것은 미국이나 유럽등 알파벳을 사용하는 나라를 위해서 사용하는 인코딩 기법이다.

    알다시피 알파벳은 1바이트면 충분하다. 그래서 UTF-8의 경우 가변 길이를 가지는 코드체계로써 만약 알파벳이면 1바이트 한글, 중국어면 3, 4바이트를 이용하는 효율적인 방법이다.

    고로 만약
    문서에 한글(고어 말고)이 많으면 UTF-16이 유리할 것이고 만약 알파벳이 많으면 UTF-8이 유리하다.


    헌데 UTF-8이 훨씬도 똑똑한데 왜 그러냐면,
    1. 문자 코드 길이 계산이 쉽다. 상위 첫 비트가 중복되는 경우가 없으므로 (고유 태그) 길이 계산이 용이하다. (길이 계산 용이)
    2. 가변 길이의 코드도 앞과 뒤가 고유 태그를 가지므로 현재 바이트의 헤더 부분만 보고도 이 바이트가 문자의 앞인지 아니면 중간인지 알수 있다. (글자 검색 용이)
    3. 문자열 검색이 용이하다. 왜? 마찬가지로 중복 코드가 없으므로.

    추가로 중요한 것 한가지는 윈도우 95랑 98에서는 유니코드 지원하지 않는다 (기본적으로) 물론 업뎃하면 된다고는 하지만 완벽 지원은 안된단다. (지금도 그런지는 모름)

    프로그램짜면서 사용해야 하는 실제 상황을 보자꾸나...


    #define UNICODE
    #include <windows.h>

    UNICODE를 windows.h 전에 선언해주면 그 프로젝트는 유니코드로 컴파일 된다.

    wchar_t *lpszClass = L"UniTest1";

    유니코드로 문자열을 선언하기 위해서는 위 명령처럼 wchar_t 타입을 사용해야 하고 L을 " " 문자열 앞에 붙여야 한다. 

    여기서 wchar_t는 사실 unsigned short (16 비트)이고 L은 뒤에 있는 문자열을 2바이트 유니코드로 변환하여 저장하라는 매크로다. 이 L이 없으면 컴파일러는 기본적으로 ANSI (1바이트 문자열 형식)로 저장하려 한다. (에러 뜰거임)

    그러면 프로그램짤때 유니코드 쓰냐 안쓰냐에 따라서 선언문이 바뀌어야 하느냐? 만약 YES라면 얼마나 불편하겠는가. 그래서 등장한 놈이 TCHAR인데...

    typedef wchar_t WCHAR;
    typedef char CHAR;

    #ifdef UNICODE
    typedef WCHAR TCHAR;
    #else
    typedef char TCHAR;
    #endif

    위의 전처리를 통해서 ANSI든 유니코드든간에 TCHAR을 사용하면 UNICODE의 선언 여부에 따라 자동으로 컴파일러가 골라쓰게 된다. 즉, UNICODE가 선언되어 있으면 TCHAR이 WCHAR이고 결국 이건 wchar_t이며 이건 결국 unsigned short이다. 만약 UNICODE 선언이 없다면 TCHAR은 걍 char이다. 

    마찬가지로 문자열에 대해서는

    typedef WCHAR *PWCHAR, *LPWSTR, *PWSTR;                   // 유니코드 사용시 문자열 포인터 타입
    typedef CONST WCHAR *LPCWCH, *LPCWSTR, *PCWSTR;   // 유니코드 사용시 상수형 문자열 포인터 타입
    typedef CHAR *PCHAR, *LPSTR, *PSTR;                             // 유니코드 사용 안할때 문자열 포인터 타입
    typedef CONST CHAR *LPCCH, *LPCSTR, *PCSTR;             // 유니코드 사용 안할때 상수형 문자열 포인터 타입

    #ifdef UNICODE                                                 // 유니코드 사용시는 PTSTR = LPWSTR, LPCTSTR = LPCWSTR
    typedef LPWSTR 
    PTSTR, LPTSTR;
    typedef LPCWSTR 
    LPCTSTR;
    #else                                                               // 유니코드 사용안할때는 PTSTR = LPSTR, LPCTSTR = LPCSTR
    typedef LPSTR 
    PTSTR, LPTSTR;
    typedef LPCSTR 
    LPCTSTR;
    #endif

    인데 졸라 복잡해 보이지만 간단하다. 문자열 포인터나 상수형 문자열 포인터를 써야 한다면 간단하게 걍 PTSTR, LPTSTR, LPCTSTR등을 쓰면 된다. 만약 내가 #define UNICODE를 시작전에 선언했다면 프로그램은 알아서 WCHAR 을 사용할 것이고 만약 유니코드 매크로를 선언하지 않았다면 알아서 CHAR을 사용한다. 위의 선언에서 잘 보면 W가 있고 없고의 차이인데 이것이로 WCHAR이냐 아니면 CHAR이냐의 차이다.

    고로 결론적으로 유니코드도 되고 ANSI도 되는 코드를 짜려면 문자 선언시는 TCHAR, 문자열 선언시는 LPTSTR을 써라는 말이다.

    문자열 상수에 대한 매크로가 존재하는데 바로 TEXT("     ") 이다. 이놈을 사용하면 마찬가지로 매크로에서 정의된 방법대로 유니코드냐 아니냐에 따라서 알아서 저장해 준다. 만약 UNICODE 매크로가 선언되어 있다면 TEXT()는 알아서 문자열 앞에 L을 붙여주고 (이 L이 유니코드로 문자열을 변경하라는 매크로) 만약 UNICODE 매크로가 선언 되지 않았다면 걍 ANSI 문자열로 문자열을 생성한다. 고로 사용자는 단순하게 #define UNICODE를 프로그램 시작부분에 쓰느냐 안쓰느냐에 따라서 간단하게 유니코드와 ANSI 코드 사이를 왔다 갔다리 할 수 있는 거다

    사실 알고보면 윈도우가 제공하는 각종 API 함수들중 문자열을 받거나 리턴하는 대부분 UNICODE 타입과 ANSI 타입을 지원하는 두 종류를 가진다. 예를 들어 TextOut 함수를 보면 실제로는 TextOutW와 TextOutA라는 두 함수로 구성되어 있고 UNICODE의 선언 여부에 따라 적절한 함수를 호출한다. 여기서 W는 Wide라는 의미로 Unicode를  의미하고 A는 ANSI를 의미한다.


    프로그램을 작성할 때는 윈도우가 제공하는 API 뿐만 아니라 오래된 C 함수 및 C++함수들을 사용해야 하는데 대표적인 것이 문자열의 길이를 구하는 함수인 strlen()이다. 대략 10년 전쯤 처음 컴퓨터 프로그래밍 수업시간에 들었던 기억이 난다.. ㅡ.ㅡ;

    아무튼, 이 strlen() 함수는 ANSI 코드를 위한 함수이지 유니코드를 위한 함수가 아니다. 유니코드를 위한 문자열 길이 구하는 함수는 wcslen() 이라는 놈이다. 하지만 걱정할 필요가 없는것이 위에서 해왔던 것 처럼 이 둘 사이를 쉽게 왔다 갔다 할 수 있는 일반형 함수가 존재한다. 

    tchar.h에 보면 UNICODE의 사용여부에 따라 알아서 strlen() 또는 wcslen() 함수를 사용하도록 해주는 일반형 함수가 정의되어 있는데 대표적인 예로 _tcslen, _tcscpy, _tcscmp, _tcsicmp 등이다.

    #ifdef _UNICODE
    typedef _tcslen wcslen;
    #else
    typedef _tcslen strlen;
    #endif

    위 매크로 선언에 의해서 알아서 wcslen과 strlen 함수를 _UNICODE 매크로 선언 여부에 따라 적절히 선택한다.

    참고로 코드를 보면 _UNICODE와 UNICODE 두가지가 공존하는데 이것은 같은 의미의 매크로로써 하나는 컴파일러가 언어차원에서 정의한 것이고 나머지 하나는 운영체제가 정의하는 거란다. 젠장할...

    아무튼 결론적으로보면 사용자는 프로그램 내부에 TCHAR, _tcslen, TEXT(" ") 와 같은 UNICODE 선언 여부에 따라 적절한 함수를 선택하는 매크로 함수들을 사용하면 손쉽게 UNICODE 용으로 컴파일 할수도 있고 혹은 ANSI 용으로 컴파일 할 수도 있다는 점이다. 


    Visual Studio 의 프로젝터 Properties 를 클릭해 보면 General 속성에서 Character Set에 유니코드와 MBCS 둘 중 하나를 선택할 수 있다. 여기서 유니코드를 선택하면 Visual Studio가 알아서 #define UNICODE를 코드에 삽입한다. (얼마나 편한가..)


    만약 프로그램을 짤 때 일반화 함수들을 안쓰고 char을 써놓고 UNICODE로 컴파일 하라는 명령을 주게 되면 위 그림처럼 무수히 많은 에러들을 보게 될 것이다. 이것이 일반적으로 가장 빈번히 발생하는 유니코드 관련 에러이다. 문제는 다음과 같은 경우다. 

    내가 프로그램을 만드는데 어느 유명 사이트에서 대단한 프로그래머가 만든 아주 유용한 소스 코드를 받았다. 헌데 그 프로그래머는 너무나도 유니코드를 사랑하셔서 모든 함수를 일반화 함수가 아닌 유니코드 함수로 직접 불러다 사용했다. 그런데 내가 만든 프로그램에서는 죄다 MBCS를 사용한다. (char, char * 같은 형태) 

    둘을 하나의 프로젝트에 묶어서 컴파일 하려했다... 에러가 200개 뜬다..... ㅡ.ㅡ; 헐... 

    이런 경험을 해본 사람이 많으리라 믿는다. 아무튼, 말하고자 하는 바는 프로젝트를 만들때 유니코드와 MBCS가 동시에 존재할 수 없다는 것이다. 그래서 일반화 함수를 사용하는 것이 현명한 방법이다.

    TCHAR
    TCHAR과 PTSTR 그리고 TEXT() 매크로 함수를 범용성을 위해서 꼭 사용해라... 이말이다.

    posted by 대갈장군
    2010. 3. 5. 05:25 프로그래밍

    Managed, Unmanaged, Native: What Kind of Code Is This?

    http://www.developer.com/net/cplus/article.php/2197621/Managed-Unmanaged-Native-What-Kind-of-Code-Is-This.htm

    With the release of Visual Studio .NET 2003 (formerly known as Everett) on April 24th, many developers are now willing to consider using the new technology known as managed code. But especially for C++ developers, it can be a bit confusing. That's because C++, as I pointed out in my first column here, is special.

    1. What Is Managed Code? – Managed code?

    Managed Code is what Visual Basic .NET and C# compilers create. It compiles to Intermediate Language (IL), not to machine code that could run directly on your computer. The IL is kept in a file called an assembly, along with metadata that describes the classes, methods, and attributes (such as security requirements) of the code you've created. This assembly is the one-stop-shopping unit of deployment in the .NET world. You copy it to another server to deploy the assembly there—and often that copying is the only step required in the deployment.

    Manage code 무엇인가? Managed code Visual Basic .NET C# 컴파일러가 만들어내는 것을 말한다. 컴파일러들은 우선 IL(Intermediate Language – 중간 언어) 라고 불리는 코드를 만들어 낸다. IL 컴파일을 수행한 당신의 컴퓨터에서 바로 실행되기 위한 machine code (기계 언어) 아니라는 알아야 한다. 이렇게 생성된 IL 어셈블리라고 불리는 파일에 저장되고 이때 메타 데이터도 함께 저장하는데 메타 데이터들은 내가 컴파일한 코드의 클래스, 함수 그리고 변수 속성 (보안 관련 속성도 포함)등을 가진다. 이제 어셈블리 파일은 .NET 세계에서 배달 완료된 모든 내용이 포장된 일종의 상품이다. 당신은 이것을 다른 서버에 복사하여 deploy (배포)하기만 하면 된다.

    Managed code runs in the Common Language Runtime. The runtime offers a wide variety of services to your running code. In the usual course of events, it first loads and verifies the assembly to make sure the IL is okay. Then, just in time, as methods are called, the runtime arranges for them to be compiled to machine code suitable for the machine the assembly is running on, and caches this machine code to be used the next time the method is called. (This is called Just In Time, or JIT compiling, or often just Jitting.)

    Managed code 이른바 Common Language Runtime (줄여서 CLR) 환경에서 동작한다. CLR 내가 작성한 코드가 실행될 다양한 형태의 서비스를 지원한다. 여기서 주의 깊게 봐야 하는 단어는 실행될 이다. 일반적인 경우, CLR 우선 어셈블리 파일을 읽은 다음 IL 괜찮은지부터 확인한다. 그리고 함수가 호출되는 순간 CLR 현재 프로그램이 돌아가는 컴퓨터의 환경에 적합한 기계 언어를 만들어 낸다. 그리고 함수가 다음 번에 호출되면 바로 사용될 있게 하기 위해서 만들어진 기계언어 코드를 cache (캐시) 저장해 둔다. 이것이 바로 Just In Time 혹은 줄여서 JIT 컴파일링 이라 불리는 기법이다. 여기서 중요한 것은 현재 프로그램이 돌아가는 컴퓨터 환경에 적합한 기계 언어 생성이다. 이것이 바로 CLR 강점이다.

    As the assembly runs, the runtime continues to provide services such as security, memory management, threading, and the like. The application is managed by the runtime.

    어셈블리가 실행되는 동안 CLR 보안, 메모리 관리, 스레딩에 관련된 서비스 일체를 제공하고 이런 프로그램을 우리는 ‘Runtime 의해 Managed (관리되는) 프로그램이다 라고 한다.

    Visual Basic .NET and C# can produce only managed code. If you're working with those applications, you are making managed code. Visual C++ .NET can produce managed code if you like: When you create a project, select one of the application types whose name starts with .Managed., such as .Managed C++ application..

    Visual Basic .NET C# 오직 managed 코드만 생성가능 하다. 만약 당신이 프로그램들을 사용한다면 당신은 managed code 생성해 내고 있는 것이다. 반면 Visual C++ .NET 내가 원하면 managed code 생성할 수도 있고 아니면 unmanaged code 생성할 수도 있다. 이것은 Visual Studio 프로젝트 속성에서 변경 가능하다.

    2. What Is Unmanaged Code? – Unmanaged code ?

    Unmanaged code is what you use to make before Visual Studio .NET 2002 was released. Visual Basic 6, Visual C++ 6, heck, even that 15-year old C compiler you may still have kicking around on your hard drive all produced unmanaged code. It compiled directly to machine code that ran on the machine where you compiled it—and on other machines as long as they had the same chip, or nearly the same. It didn't get services such as security or memory management from an invisible runtime; it got them from the operating system. And importantly, it got them from the operating system explicitly, by asking for them, usually by calling an API provided in the Windows SDK. More recent unmanaged applications got operating system services through COM calls.

    Unmanaged code Visual Studio .NET 2002 나오기 전에 만든 코드를 말한다. , Visual Basic 6, Visual C++ 6 그리고 15 이상된 C 컴파일러 또한 Unmanaged code 생성해 낸다. 이것들은 내가 컴파일을 수행하는 바로 컴퓨터 적합한 기계 코드를 생성 낸다. (Managed code 처럼 IL 생성 과정이 없다.) 고로 이렇게 생성해낸 기계 언어는 같은 하드웨어 구성을 가지지 다른 컴퓨터에서는 돌아갈지 모르나 다른 구성을 가지는 컴퓨터에서는 당연히 실행 불가다. 또한 이렇게 생성된 unmanaged code 보안 메모리 관리 서비스를 실행시에 Runtime으로부터 받을 수가 없다. 대신 OS(Windows)로부터 받는다. 여기서 중요한 점은 OS로부터 서비스를 받기는 하지만 이것은 Windows SDK 제공하는 특정 함수 (API) 호출함으로써 가능하다. 근래의 Unmanaged code 운영체제의 서비스를 COM call 의해 제공받는다라고 했다. 글이 2004 글이므로 COM 근래의 언어라고 표현한 것이 이해가 간다. 아무튼, 요약하자면 managed code 경우 runtime 알아서 메모리 관리를 해주는데 반해 (Garbage collection) unmanaged code 경우, 사용자에게 책임이 있다는 점이다. 다만 COM 같이 발전된 형태의 라이브러리들은 스스로 객체의 파괴를 관리하는 능동성을 가지고는 있다.

    Unlike the other Microsoft languages in Visual Studio, Visual C++ can create unmanaged applications. When you create a project and select an application type whose name starts with MFC, ATL, or Win32, you're creating an unmanaged application.

    다른 Microsoft 언어들과는 다르게 Visual C++ unmanaged 프로그램을 만들 있다. MFC, ATL 혹은 Win32 생성되는 프로젝트들은 모두 unmanaged application이다.

    This can lead to some confusion: When you create a .Managed C++ application., the build product is an assembly of IL with an .exe extension. When you create an MFC application, the build product is a Windows executable file of native code, also with an .exe extension. The internal layout of the two files is utterly different. You can use the Intermediate Language Disassembler, ildasm, to look inside an assembly and see the metadata and IL. Try pointing ildasm at an unmanaged exe and you'll be told it has no valid CLR (Common Language Runtime) header and can't be disassembled—Same extension, completely different files.

    당연히 Visual C++ Managed application 만들 수도 있다. 이때 .exe 확장자를 가지는 IL 어셈블리가 만들어진다. 만약 당신이 MFC 프로그램(unmanaged code) 만든다면 이것은 똑같은 확장자인 .exe 가지는 native code 생성해 것이다. 여기서 혼란이 수도 있다. 같은 .exe 확장자를 가지니까 뭐가 뭔지 모를 있다. 둘을 구분하기 위해서는 Intermediate Language Disassembler, 줄여서 ildasm이라 불리는 놈으로 어셈블리와 메타 데이터를 들여다 보면 된다. Unmanaged code ildasm으로 보려고 하면 유효한 CLR 헤더가 없다고 에러 메시지를 출력할 것이란다. , 개의 .exe 파일은 완전히 다른 형태의 파일이다.

    3. What about Native Code? – Native code?

    The phrase native code is used in two contexts. Many people use it as a synonym for unmanaged code: code built with an older tool, or deliberately chosen in Visual C++, that does not run in the runtime, but instead runs natively on the machine. This might be a complete application, or it might be a COM component or DLL that is being called from managed code using COM Interop or PInvoke, two powerful tools that make sure you can use your old code when you move to the new world. I prefer to say .unmanaged code. for this meaning, because it emphasizes that the code does not get the services of the runtime. For example, Code Access Security in managed code prevents code loaded from another server from performing certain destructive actions. If your application calls out to unmanaged code loaded from another server, you won't get that protection.

    Native code 가지의 의미를 가지고 있다. 우선 Native code 함은 Unmanaged code 동의어로 사용된다. 옛날 버전의 컴파일러로 컴파일 되었거나 Visual C++에서 일부러 선택한 컴파일러에 의해 해당 컴퓨터에 적합한 기계언어의 생성을 말하며 이는 런타임시 (실행되는 순간) CLR 의해 아무런 서비스를 받을 없다는 것을 의미한다. 이런 Native code(Unmanaged code) 하나의 완전한 프로그램 수도 있고, COM 컴포넌트 수도 있고, 혹은 Managed code내부에서 호출된 DLL 수도 있다. 하지만 이런 경우 ‘Unmanaged code’라고 하는 것이 합당한데 왜냐면 Unmanaged code라는 것이 실행시에 Runtime으로부터 서비스를 받을수 없다는 것을 분명히 강조하기 때문이란다. 예를 들어 설명한 것이 서로 다른 컴퓨터에서 실행되는 프로그램이 Managed code 경우 보안 관련 서비스를 실행시 (Runtime) 받을수 있으나 Unmanaged code 경우 그럴 없다는 .

    The other use of the phrase native code is to describe the output of the JIT compiler, the machine code that actually runs in the runtime. It's managed, but it's not IL, it's machine code. As a result, don't just assume that native = unmanaged.

    다른 의미의 Native code JIT 컴파일로부터 생성된 코드를 말한단다. JIT Managed code에만 사용되므로 이것은 분명 Managed code이지만 IL 코드가 아니고 해당 컴퓨터에 적합한 기계언어이다. 고로 Native라는 단어를 무조건적으로 Unmanaged라고 생각하지 말라.

    Does Managed Code Mean Managed Data? – Managed code Managed Data 의미하나?

    Again with Visual Basic and C#, life is simple because you get no choice. When you declare a class in those languages, instances of it are created on the managed heap, and the garbage collector takes care of lifetime issues. But in Visual C++, you get a choice. Even when you're creating a managed application, you decide class by class whether it's a managed type or an unmanaged type. This is an unmanaged type:

    Visual Basic이나 C# 사용한다면 모든 것이 Managed code이므로 오히려 간단하다. 고로 내가 만약 언어들을 사용해서 프로그램을 작성한다면, 클래스를 하나 선언하고 객체화를 했을 객체는 Managed Heap 생성되고 Garbage Collector 알아서 메모리를 수거해 간다. 하지만 내가 만약 Visual C++ 사용한다면 나는 각각의 클래스를 선언할 마다 선택을 수가 있게 된다. 이른바 클래스가 Managed type인가 아니면 Unmanaged type인가를 다음과 같이 결정한다. 우선 Unmanaged type 경우 다음과 같이 선언한다.

    class Foo

    {

    private:

       int x;

    public:

        Foo(): x(0){}

        Foo(int xx): x(xx) {}

    };

     

    This is a managed type:

    그리고 Managed type 경우 다음과 같다.

    __gc class Bar

    {

    private:

       int x;

    public:

        Bar(): x(0){}

        Bar(int xx): x(xx) {}

    };

    The only difference is the __gc keyword on the definition of Bar. But it makes a huge difference.

    차이점이라고는 클래스 선언 앞부분에 __gc 붙냐 안붙냐 차이 밖에 없다. 하지만 차이는 엄청나다.

    Managed types are garbage collected. They must be created with new, never on the stack. So this line is fine:

    Foo f;

    Managed type garbage collector 의해 자동으로 메모리가 수거 된다. 고로 놈들은 반드시 new 이용해서 생성해야 하며 절대로 stack 생성될 없다. 고로 위에 Unmanaged type으로 생성한 Foo 클래스의 경우 명령처럼 스택에 선언할 있지만,

    But this line is not allowed:

    Bar b;

    Managed type으로 선언한 Bar 클래스의 경우 Foo 클래스와 같은 방식으로 스택에 선언 수가 없다.

    If I do create an instance of Foo on the heap, I must remember to clean it up:

    Foo* pf = new Foo(2);

    // . . .

    delete pf;

    The C++ compiler actually uses two heaps, a managed an unmanaged one, and uses operator overloading on new to decide where to allocate memory when you create an instance with new.

    만약 내가 Foo 클래스를 힙에 선언한다면 (다른 말로 포인터 생성하여 new 객체화 경우), 반드시 메모리 삭제를 delete 통해서 파괴해야 한다. C++ 컴파일러는 실제로 두개의 힙을 사용하는데 각각은 managed unmanaged 위한 것이다. new 라는 키워드는 오버로딩을 통해서 managed unmanaged 중에 하나를 선택하여 메모리 할당한다.

    If I create an instance of Bar on the heap, I can ignore it. The garbage collector will clean it up some after it becomes clear that no one is using it (no more pointers to it are in scope).

    There are restrictions on managed types: They can't use multiple inheritance or inherit from unmanaged types, they can't allow private access with the friend keyword, and they can't implement a copy constructor, to name a few. So, you might not want your classes to be managed classes. But that doesn't mean you don't want your code to be managed code. In Visual C++, you get the choice.

    반대로 만약 내가 Bar 클래스를 힙에 생성하면 나는 간단히 신경 끄면 그만이다. ? Garbage collector 알아서 메모리를 수거할 테니

    , managed type에는 몇가지 제약이 있으니 바로 다중 상속이 안되며 unmanaged type으로 부터의 상속이 안된다는 제약이다. 또한 private 속성에 대한 접근이 friend 키워드를 통해서도 안되고, 복사 생성자를 구현 수가 없단다. 요런 저런 제약을 보아하니 아마도 당신은 managed class 만들고 싶지 않을 수도 있다. 하지만 그것이 당신의 프로그램 전체가 managed code 아니길 바란다는 것은 아닐 것이다 (managed unmanaged 섞어 써도 된다는 ). 암튼, C++에서는 프로그래머가 선택할 있다.

    결론만 말하자면,

    1. Managed code는 CLR에 의해 제공되는 실행시의 각종 서비스 (보안 및 메모리 관련)를 받을 수 있는 것을 말한다.
    2. Managed code는 CLR에 의해 해당 컴퓨터에 적합한 기계 언어를 능동적으로 생성하여 범용성을 높였다.
    3. Unmanaged code는 실행시의 CLR이 제공하는 서비스를 제공받을 수 없다.
    4. Unmanaged code는 컴파일할때 컴퓨터의 환경에 적합한 기계어만 생성하므로 범용성이 매우 낮다.
    5. Native code는 '기계 언어'라고 보는 것이 적합하며 Native code를 Unmanaged code라고 생각하면 안된다.
    6. Visual C++는 Managed Type과 Unmanaged Type의 클래스를 생성할 수 있다.
    7. Managed Type 클래스는 힙에 생성되면 자동으로 메모리 수거가 이루어 지는 반면 Unmanaged Type 클래스는 사용자가 수거해야 한다. 
    8. __gc를 클래스 선언시 사용하면 Managed Type 클래스를 선언 할 수 있다.

    About the Author

    Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.

     

    posted by 대갈장군
    2010. 2. 18. 07:33 프로그래밍
    Convex Hull 을 한국어로 정확하게 해석하자면 '볼록 껍데기' 정도라 하겠다. 2차원 공간에서 임의의 점들이 x와 y 축에 퍼져 있을때 이 점들의 외곽선을 연결하는 꼭지점들 (Vertex) 만 찾아내는 알고리즘이 바로 Graham's Scan이다. 

    일단 몇가지 정의 해야 할 것들이 있다. Q는 2차원 공간상의 임의의 점들의 집합이고 각각의 점들을 P0, P1, P2 ... 와 같이 표현한다.

    스택 S는 점들을 저장하는 스택인데 이 스택에 모든 점들을 최소한 한번씩은 집어 넣은 다음, 만약 어떤 점이 외곽선을 구성하는 점이 아닐 경우 스택에서 뽑아낸다. 결국 모든 과정을 끝낸후에 스택에 남은 점들은 외곽선을 구성하는 점들이 될것이다. 

    그리고 CH(Q)는 Convex Hull(Q)의 약자로 점의 집합 Q에서 외곽선을 구성하는 점들만의 집합을 말한다. 이 CH(Q)가 우리가 최종적으로 원하는 점들이 될것이다.

    자, 이제 단계별로 Graham's Scan을 살펴보자.

    1. P0 정하기 - 제일 먼저 해야 하는 것은 시작점을 정하는 일이다. P0는 가장 작은 y 값을 가지는 점이다. 만약 가장 작은 y 값을 가지는 점이 한개 이상 있다면 그들중 가장 작은 x 값을 가지는 점을 찾아서 그 놈을 P0로 정한다. 이렇게 정하는 이유는 Graham's Scan 이 기본적으로 cross product를 사용하기 때문이다.

    간단하게 말해서 P0, P1, P2 라는 3개의 점이 있을때, (이 세점은 직선상에 놓인 점이 아니어야 한다는 가정아래) 이 세개의 점의 외적 (cross product)의 부호를 구하면 P2 점이 P0와 P1이 만드는 직선을 기준으로 왼쪽에 놓이는지 혹은 오른쪽에 놓이는지 알수 있다. 이것을 Pseudocode로 표현하면, 다음과 같다.
    # Three points are a counter-clockwise turn if ccw > 0, clockwise if
    # ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
    # gives the signed area of the triangle formed by p1, p2, and p3.
    function ccw(p1, p2, p3):
        return (p2.x - p1.x)*(p3.y - p1.y) - (p2.y - p1.y)*(p3.x - p1.x)
    만약 마지막 세번째 점이 앞의 두점이 이루는 직선의 왼쪽에 있으면 Counter-clockwise가 되어 ccw 함수는 양의 값을 리턴하고 반대로 세번째 점이 앞의 두점이 이루는 직선보다 오른쪽에 있다면 Clockwise가 되어 ccw 함수는 음의 값을 리턴한다. 만약 세점이 직선상에 위치하게 되면 0을 리턴한다.

    2. 점 P0를 기준으로 각 점마다 각도 구하여 작은 순서에서 큰 순서로 정렬하기 - 이 과정이 내가 조금 헷갈렸던 부분인데, 나는 원점을 기준으로 각도를 구했다가 낭패를 봤다. 


    구해야 하는 각도는 위 그림처럼 원점을 기준으로 한 각도가 아닌 P0 점을 기준으로 한 각 점들의 각도를 구해야 한다. 이것을 구하기 위해서는 atan2 함수를 사용하면 된다. 원래 tans는 x의 증가량 분의 y의 증가량인데 이 구해진 기울기 값을 tan의 역함수인 atan2 함수에 대입하면 각도가 나온다. 다만, +, - 기호에 주의해야 하는데 이건 웹 서핑하다보면 각 언어별로 적절한 코드가 존재하니 찾아보면 된다. Google 에서는 Angle between two points 라고 하면 여러개가 뜬다.

    옆의 그림은 Graham scan을 간단하게 설명한 그림이다. (위키에서 퍼왔음) 일단 P, A, B, C, D의 점들이 있는데 가장 y값이 적으면서도 가장 x 값이 적은 P 점을 시작점으로 잡는다. 그리고 P 점을 기준으로 각도를 구하는데 각도가 적은 순으로 나열하면 A, B, C, D가 된다. 이제 알고리즘을 돌리기 위한 준비가 끝났다.

    3. 스택 S에 첫 세점 (시작점 포함) 을 집어 넣는다. 왼쪽 그림에서는 P, A, B가 첫 세점이다. 그러면 스택 S는 |P|A|B| <- 요런 모습을 하고 있을 것이다. (B가 가장 상위 스택)

    4. 이제는 For문을 이용하여 i = 3 부터 남아 있는 점들 모두에 대해 스캔을 시작한다. 각각의 i에 대해서 3개의 점이 이루는 cross vector의 방향성을 체크하여 그 값이 음이면, 즉 앞 두점이 이루는 선보다 오른쪽에 위치하면 그 점은 외곽선 내부에 위치하므로 과감하게 스택 S에서 빼버리고 그 다음 점을 스캔한다. 이것을 수도 코드로 표현 하자면,

    for i = 3 to m (여기서 m은 전체 포인트의 갯수)
       
       do while (스택 S의 최상위 바로 밑의 점과 최상위 점, 그리고 포인트 Pi 가 시                     계방향인지 반 시계 방향인지 검사하여 만약 시계 방향이라면, 즉                       앞 두점이 만드는 직선보다 세번째 점이 오른쪽에 있다면)
               do Pop(S) // 스택 S에서 최상위 점 제거

       Push(Pi, S)    

    이걸 걍 보면 잘 이해가 안되니 하나하나 그림을 보면서 해보자.
    우선 앞서 말한것 처럼 스텝 1,2를 끝내고 나면 스택 S에는 |P|A|B| 가 들어가 있는 상태다. 여기서 for 문을 들어가게 되면 최상위 스택의 바로 아래 점 (A)와 최상위 스택의 점 (B) 그리고 Pi 점인데 i가 3에서 시작하므로 P3인 점은 바로 C이다. (P0 = P, P1 = A, P2 = B, P3 = C, P4 = D)

    이 세점을 이어보면 AB가 만드는 직선의 왼쪽편에 점 C가 존재하므로 조건이 만족하지 않는다. 따라서 while 문을 탈출하여 Push(Pi, S)를 하게 되는데, 그러면 스택에는 |P|A|B|C| 처럼 C가 들어가게 되고 제일 위에 위치한다.

    이때 while 문 내부의 조건을 함수로 표현하면 간단하게 ccw(스택 최상위 바로 아래 점, 스택 최상위 점, Pi) <= 0 로 표현 가능하다. 0보다 적거나 같다는 의미는 '일직선 상에 놓이거나 아니면 오른쪽 편 (시계 방향으로 돌거나)에 위치하거나' 라는 의미다.

    자 그런데, 스택에 C를 넣었다는 의미는 즉, C가 외곽선을 구성하는 점들중 하나라는 의미인데 그림을 보면 그렇지 않다. 이는 바로 다음 for 루프에서 찾아진다.

    i 값이 하나 증가하여 4가 되었고 마찬가지로 while 문에 들어가게 되면 최상위 아래점 (B)와 최상위점 (C) 그리고 P4 (i=4) 점에 대해 방향성 검사를 한다. 이때 점 B와 C가 이루는 직선의 '오른쪽' 편에 점 D가 존재하게 된다. 고로 음의 값을 리턴하며 마침내 Pop(S) 문을 수행한다.

    고로 최상위 스택값이던 C점이 스택에서 제거가 된다. 고로 스택 S는 |P|A|B| 가 되어 원상 복구된다. 즉, C점을 제거한 셈이다. 이 과정은 아주 간단한 논리다. 가장 외곽에 존재하는 점들은 절대로 앞 두점을 기준으로 한 직선의 오른쪽 편에 존재하지 않는다는 원리를 사용한 것이다. 만약 오른편에 존재한다면 그 점은 외곽선에 포함되지 않는다는 간단한 논리인 셈이다.

    그리고 이것이 while 문인 이유는 두 개 이상의 점이 실제로는 외곽선 상의 점이 아님에도 불구하고 외곽선으로 판정되는 경우가 종종 있기 때문에 이런 경우 이런 모든 점들을 제대로 스택에서 제거하기 위해서 while문을 이용하여 스택에서 차례 차례 제거한다. while 문에서 i 값은 늘 고정되어 있는데 비해 스택의 최상위 점만 계속 제거해 나가는 구조를 보면 쉽게 이해할 수 있다.

    마지막으로 while문을 벗어난 후 현재 i = 4의 점인 D를 스택 S에 집어 넣는다. |P|A|B|D| 요렇게. 결국 남은 4개의 점은 위의 그림에서 정확한 외곽선을 이루는 점들이다. 

    이런 알고리즘이 그닥 무슨 효용이 있을까 생각하시는 분들 많을 텐데 생각외로 매우 유용할 수 있다. 내가 이 알고리즘을 사용하려고 했던 목적은 사실 임의의 모양을 가지는 사각형이 있고 임의의 점 x가 있을때 이 x 점이 사각형 내부에 존재하는지 아니면 외부에 존재하는지 체크하는데 이 알고리즘을 적용했다.

    만약 사각형의 네개의 점을 위의 그림에서의 P, A, B, D라고 한다면 임의의 점 x가 사각형 내부에 존재한다면 사각형의 각 직선 (PA, AB, BD, DP) 에 대해 점 x는 반드시 왼쪽에 위치해야 한다. 만약 점 x가 사각형 외부에 존재하거나 직선위에 놓이게 되면 방향성 검사에서 음의 값이나 0을 리턴하게 되어 있다. 

    네이버에서 임의의 사각형 내부에 존재하는 점 찾기를 검색해 보면 각 직선의 방정식을 이용하기 등 좀 까다로운 방법들이 많은데 내가 볼때는 이 방법이 가장 '컴퓨터 프로그램'적이지 않나 싶다. 물론 주의해야 할 점은 numerical error가 존재하므로 좌표계를 적절히 조절할 필요가 있다는 점이지 싶다.

    posted by 대갈장군
    2009. 12. 19. 05:50 프로그래밍
    C++이나 C#은 매우 크고 방대한 언어다. 문법도 많고 외워야 할 것도 많으며 규칙도 정말 많다. 책을 사서 1장 부터 읽다보면 3장쯤 가면 복잡해지기 시작하면서 뭐가 뭔 말인지 모르겠다... @.@ 한숨을 푹쉬고 책을 덮는다... 그리고 속으로 '더 쉬운책이 없을까?' 라고 생각한다.

    내가 많이 겪은 경험이다. 이것은 책이 자습서로서의 역활을 못하고 있다는 말과 같다. 거대한 개념이 있다면 그것의 핵심에 접근하기 위해서는 부분 부분이 아닌 전체의 맥과 흐름을 집어주는 역활을 자습서는 반드시 해야 한다.

    내가 읽어본 많은 책들은 그렇지 못했지만 유일하게 김상형님이 쓰신 모든 저서는 이 흐름을 잡아준다. C++, C#이라는 거대한 것을 하나하나 분리해 서로를 유연하게 연결한 다음 독자가 충분히 어떤것을 이해할 단계에 도착하면 거기서 거대한 전체 지도를 보여주면서 내가 왜 여기에 왔는지 그리고 앞으로 어떤 길을 가야 하는지 정확하게 알려준다.

    김상형님의 책을 보면서 깨우친 것을 나름대로 정리하고자 하는데 그 첫째가 바로 객체 지향의 기본인 '클래스'이다.

    가장 단순하게 클래스를 정의하면 C 구조체의 확장형태라고 생각할 수 있다. 그냥 뭐 이런 저런 변수를 담는 통으로 생각하기 쉽다.

    하지만 C#에 들어오면서 완전 객체 지향성을 강조하게 되면서 구조체와 클래스는 '상속'이라는 결정적 차이를 가지게 된다. 바로 이 상속이 가능해 지면서 클래스는 '계층'이라는 말에 더 가까워 졌다.

    상속인터페이스를 이용한 뚜렷하고 명확한 계층 구조의 클래스 설계로 한눈에 들어오는 구조적 설계가 가능해 졌다. 바로 이것이 객체 지향 프로그래밍의 핵심이다. (인터페이스는 차후 설명하겠다.)

    예전에도 언급했듯이 이제는 프로그램 개발자가 두 부류로 분류가 된다. 첫째는 라이브러리 개발자다. 대형 회사, 즉, microsoft와 같은 회사에서 최종 사용자가 사용할 수많은 라이브러리 함수와 클래스를 만들어내는 사람을 말한다. 둘째는 최종 개발자다. 이 사람은 나와 같은 사람으로 대형 회사 개발자가 만들어 놓은 수많은 라이브러리 함수를 불러와 사용하기만 한다. 

    고로 나와 같은 최종 개발자들은 내가 쓸 함수를 처음부터 일일이 만들어 쓰는게 아니라 '잘 불러다가' 쓰면 그만이다.

    바로 이 '잘 불러다가'가 '구조 설계'이며 이 '구조 설계'를 위해 가장 사람에게 직관적이고 적합한 분류 방식이 계단식 방식, 이른바 'Class (계급 혹은 계층) 구조' 이다. 

    내가 판단하기로는 클래스의 의미가 바로 여기서 나왔다고 생각하지만 물론 아닐 수도 있다. ㅋㅋㅋ (아님 말고)

    상속은 부모 클래스의 모든 것을 재사용하며 동시에 추가적인 것을 곁들여 새로운 자식 클래스를 만들어내는데,  이것은 계층적 구조 설계에 딱 맞아 떨어지는 방식이다. 

    클래스를 문법적으로 설명하자면, 생성자, 파괴자, 가상함수, 추상함수, 멤버 변수, 정적변수, 정적클래스, 봉인클래스, 순수 가상함수, 추상클래스 등등등등 수많은 것들이 존재하지만, 내가 이 글에서 말하고자 하는 것은 클래스라는 단어가 객체 지향 언어에서 선택 되었는가이다. 

    거대한 객체 지향 언어를 한 두발 떨어져서 그 의미와 구조, 근원을 볼 수 있게 되면 세세한 문법과 규칙들이 너무나도 당연하게 받아들여지는 순간이 있다. 내가 원하는 것은 바로 그것이다.





    posted by 대갈장군
    2009. 11. 17. 05:01 프로그래밍/Windows API
    차일드 윈도우는 자식 윈도우다. 차일드 윈도우를 생성할때는 CreatWindow() 함수에서 3번째 인수값으로 WS_CHILD 플래그를 넣고 8번째 인자에 부모 윈도우의 핸들을 주면 된다. 

    고로 상식적으로 부모 윈도우에 딸린 것이 차일드 윈도우이므로 부모 윈도우 내부 영역에서만 차일드 윈도우는 유효하다. 차일드 윈도우도 윈도우 이므로 임의의 WndProc 함수를 가질수 있다. 고로 독립적인 행동이 가능하다. 

    차일드 윈도우는 부모가 파괴될 때 자동으로 같이 파괴되므로 WM_DESTROY에 대한 처리가 필요없다. 행여나 처리를 해서도 안된다. 

    팝업 윈도우는 차일드와는 달리 부모 윈도우의 작업 영역을 벗어날 수 있고 부모 윈도우 이동시에도 자신의 위치를 고수한다. (차일드의 경우는 부모 윈도우와 같이 움직인다.) 

    '프로그래밍 > Windows API' 카테고리의 다른 글

    윈도우 메모리 - 2 (malloc 함수)  (2) 2011.03.11
    윈도우 메모리 - 1  (0) 2011.03.10
    윈도우 - SetClassLongPtr(), SetWindowLongPtr()  (0) 2009.11.17
    윈도우 - WNDCLASS 구조체  (0) 2009.11.17
    윈도우 - CreateWindow()  (0) 2009.11.17
    posted by 대갈장군
    2009. 11. 17. 04:50 프로그래밍/Windows API
    윈도우 클래스 와 윈도우는 실행 중에도 그 속성을 변경할 수 있다. 다시 말해서 윈도우 클래스에서 설정했던 각종 속성을 실행중 변경가능 하다는 말이다. 이것이 대단한 이유는 서브클래싱이라는 기법을 가능하게 해주기 때문이다.

    서브 클래싱이란 윈도우마다 설정되어 있는 동작 함수인 WndProc를 실행중에 교체함으로써 전혀 다른 동작을 할수 있게 만들어주는 기법이다. 

    아무튼, 총 8가지의 윈도우 클래스 / 윈도우 속성 설정 함수가 있으나 64비트 운영체제가 도입된 이후로는 기왕이면 Ptr이 붙은 64 비트용 함수를 사용하는 것이 범용성 면에서 유리하다.

    윈도우 클래스 속성 설정 함수는 (WNDCLASS 변경) GetClassLongPtr()과 SetClassLongPtr() 이고, 윈도우 속성 설정 함수는 GetWindowLongPtr()과 SetWindowLongPtr() 함수다. 여기서 윈도우 프로시져 함수의 주소를 바꾸고 싶다면 GWLP_WNDPROC를 인수로 넣으면 된다. 

    주의 할 것은 스타일이 플래그 형식으로 비트의 On/Off에 의해서 판단하므로 대입연산이 아니라 & 또는 | 연산으로 플래그를 설정해야 한다는 점이다.


    '프로그래밍 > Windows API' 카테고리의 다른 글

    윈도우 메모리 - 1  (0) 2011.03.10
    윈도우 - 차일드, 팝업  (0) 2009.11.17
    윈도우 - WNDCLASS 구조체  (0) 2009.11.17
    윈도우 - CreateWindow()  (0) 2009.11.17
    메모리, Win32 vs. Win16  (2) 2009.05.22
    posted by 대갈장군
    2009. 11. 17. 03:34 프로그래밍/Windows API
    CreateWindow() 함수를 사용하여 응용 프로그램 로컬 윈도우 클래스를 생성하고자 한다면 WNDCLASS 구조체를 채워넣어야 한다.

    typedef struct {
        UINT style;
        WNDPROC lpfnWndProc;
        int cbClsExtra;
        int cbWndExtra;
        HINSTANCE hInstance;
        HICON hIcon;
        HCURSOR hCursor;
        HBRUSH hbrBackground;
        LPCTSTR lpszMenuName;
        LPCTSTR lpszClassName;
    } WNDCLASS, *PWNDCLASS;

    이 구조체 중에서 중요한 3가지는 lpszClassName, lpfnWndProc, 그리고 hInstance 요소로서 이 세놈은 기본값이 없으므로 생략할 수 없다. 

    lpszClassName이 바로 CreatWindow() 함수의 첫번째 인자가 될 것이므로 되도록이면 만들고자 하는 윈도우를 잘 나타내는 이름을 지어주자. 

    hInstance는 이 윈도우 클래스를 등록한 응용 프로그램의 인스턴스 핸들이다. 여기서 지정된 응용프로그램이 종료되면 윈도우 클래스도 같이 파괴된다. 주로 WinMain 으로 전달된 hInstance 인수를 그대로 사용하면 된다.

    마지막으로 lpfnWndProc는 메시지 처리 함수다. 뭐, 이놈이 사실 내가 만드는 윈도우의 모든 동작을 정의한다. 

    이 외에 style 인자가 재미 있는 녀석인데 윈도우의 스타일을 지정하는 인수다. 지정할 수 있는 것들로는 
    StyleAction
    CS_BYTEALIGNCLIENT Aligns the window's client area on a byte boundary (in the x direction). This style affects the width of the window and its horizontal placement on the display.
    CS_BYTEALIGNWINDOW Aligns the window on a byte boundary (in the x direction). This style affects the width of the window and its horizontal placement on the display.
    CS_CLASSDC Allocates one device context to be shared by all windows in the class. Because window classes are process specific, it is possible for multiple threads of an application to create a window of the same class. It is also possible for the threads to attempt to use the device context simultaneously. When this happens, the system allows only one thread to successfully finish its drawing operation.
    CS_DBLCLKS Sends a double-click message to the window procedure when the user double-clicks the mouse while the cursor is within a window belonging to the class.
    CS_DROPSHADOW Windows XP: Enables the drop shadow effect on a window. The effect is turned on and off through SPI_SETDROPSHADOW. Typically, this is enabled for small, short-lived windows such as menus to emphasize their Z order relationship to other windows.
    CS_GLOBALCLASS Specifies that the window class is an application global class. For more information, see Application Global Classes.
    CS_HREDRAW Redraws the entire window if a movement or size adjustment changes the width of the client area.
    CS_NOCLOSE Disables Close on the window menu.
    CS_OWNDC Allocates a unique device context for each window in the class.
    CS_PARENTDC Sets the clipping rectangle of the child window to that of the parent window so that the child can draw on the parent. A window with the CS_PARENTDC style bit receives a regular device context from the system's cache of device contexts. It does not give the child the parent's device context or device context settings. Specifying CS_PARENTDC enhances an application's performance.
    CS_SAVEBITS Saves, as a bitmap, the portion of the screen image obscured by a window of this class. When the window is removed, the system uses the saved bitmap to restore the screen image, including other windows that were obscured. Therefore, the system does not send WM_PAINT messages to windows that were obscured if the memory used by the bitmap has not been discarded and if other screen actions have not invalidated the stored image.

    This style is useful for small windows (for example, menus or dialog boxes) that are displayed briefly and then removed before other screen activity takes place. This style increases the time required to display the window, because the system must first allocate memory to store the bitmap.

    CS_VREDRAW Redraws the entire window if a movement or size adjustment changes the height of the client area.

    여기서 주의해서 볼 필요가 있는 속성 두가지는 CS_DBLCLKS와 CS_NOCLOSE인데 첫번째 놈을 설정하지 않으면 마우스 더블클릭은 무시된다. (WndProc 함수로 전달되지 않는다는 말) 그리고 두번째 놈은 종료 버튼이 없도록 만든다. 심지어 Alt+F4도 안된다. 이 플래그가 설정된 경우 윈도우를 닫기 위해서는 반드시 다른 형태의 종료 방법이 제공되야 한다.


    posted by 대갈장군
    2009. 11. 17. 02:44 프로그래밍/Windows API
    윈도우는 CreateWindow() 함수에 의해 만들어진다. 
    HWND CreateWindow(      
        LPCTSTR lpClassName,     LPCTSTR lpWindowName,     DWORD dwStyle,     int x,     int y,     int nWidth,     int nHeight,     HWND hWndParent,     HMENU hMenu,     HINSTANCE hInstance,     LPVOID lpParam );
    윈도우 역시 클래스로 부터 만들어지는데 이른바 '윈도우 클래스' 중 하나로 부터 만들어진다. 첫번째 인자 lpClassName은 바로 그 '윈도우 클래스'를 지칭하는 인자이다.

    재미 있는 것은 MSDN에도 나와 있듯이, 
    lpClassName
    [in] Pointer to a null-terminated string or a class atom created by a previous call to the RegisterClass or RegisterClassEx function. The atom must be in the low-order word of lpClassName; the high-order word must be zero. IflpClassName is a string, it specifies the window class name. The class name can be any name registered with RegisterClass or RegisterClassEx, provided that the module that registers the class is also the module that creates the window. The class name can also be any of the predefined system class names. For a list of system class names, see the Remarks section.
    이 첫번째 인자에 올수 있는 놈은 Null-Terminated String 혹은 A Class Atom (클래스 부류) 라고 했는데 이 놈들은 RegisterClass 함수나 RegisterClassEx 함수에 의해서 미리 (이전에) 등록되어진 놈이라고 한다.

    윈도우 클래스를 크게 두가지 종류로 나누자면 시스템 (운영체제)에서 제공하는 기본 윈도우 클래스와 사용자가 정의해서 사용하는 사용자 윈도우 클래스가 있다.

    운영체제가 제공하는 윈도우는 우리가 자주 사용하는 "button" "edit" 과 같은 놈들이고 사용자가 정의하는 윈도우는 WNDCLASS 구조체를 사용자가 원하는 윈도우 형태로 채워넣은 다음 RegisterClass 함수를 이용해서 등록한 후 사용하는 것을 말한다. 

    그래서 내가 원하는 형태의 윈도우를 그리고 싶다면,
    1. 우선 WNDCLASS 구조체의 각 요소를 내가 원하는 옵션으로 설정한다.
    2. RegisterClass 함수를 이용해서 생성한 WNDCLASS 구조체를 등록한다.
    3. CreateWindow 함수의 첫번째 인자에 내가 작성한 WNDCLASS 의 이름을 넣어준다. 

    조금 복잡하더라도 이렇게 함으로써 사용자는 같은 형태의 윈도우를 쉽게 재생성 할 수 있다. (그것이 WNDCLASS를 생성하고 등록하는 이유)

    윈도우 클래스는 범위로 분류하자면 시스템 전역 클래스, 응용 프로그램 전역 클래스 그리고 응용 프로그램 로컬 클래스로 나뉜다.

    시스템 전역 클래스는 운영체제가 제공하는 윈도우 클래스들로 부팅하면 자동으로 등록되는 놈이다. 이것들이 바로 기본으로 제공되는 놈들로 button, edit, scrollbar, listbox 등이다.

    응용 프로그램 전역 클래스는 주로 DLL에 의해 등록되며 이 DLL을 불러들인 프로세스에서 자유롭게 사용된다. 이것들의 예로는 프로그래스, 트리뷰, 리스트뷰 등이 있다. 의외로 이런 것들이 운영체제에서 제공되는 시스템 전역 클래스가 아니라는 점이다. 고로 윈도우의 버전과 종류에 따라 저런 부분의 구현이나 기능, 색상등이 달라지는 것이다. (DLL의 버전이 다르니까)

    마지막으로 응용 프로그램 로컬 클래스는 사용자가 WNDCLASS를 정의하고 RegisterClass 함수로 등록하여 사용하는 윈도우 클래스를 말한다. 중요한 점은 클래스 이름이 중복될 경우 가장 먼저 로컬 클래스 부터 선택하고 그다음이 응용 프로그램 전역 클래스 그리고 마지막이 시스템 전역 클래스이다. 뭐, DLL과 검색 순위가 동일하다만. :)


    posted by 대갈장군
    2009. 9. 10. 00:29 프로그래밍
    제목이 자극적이다. ㅋㅋㅋ

    저놈이 바로 요근래에 날 괴롭힌 놈이다. 2009년 7월 31일자로 내 컴퓨터에 업데이트 된 놈인데 이놈은 언뜻봐서는 그 버전이 과거에 설치된 8.0.50727.762 보다 떨어지는 놈 같지만 이 어셈블리 번호는 꼭 크다고 우위를 선점하는 건 아닌가 보다.

    아무튼, 이 업데이트 설치 후 VS 2005에서 컴파일 하는 C++ 프로그램은 이 어셈블리가 없으면 실행되지 않는다. 뭐 이 시스템에서는 실행할 수 없네 어쩌네 하면서 배째라고 한다.

    Manifest 파일을 열어 확인해 본 결과 4053 이놈이 첫번째 줄에 떠억 자리하고 있는 것을 확인했고 이 녀석이 꼭 필요하다는 것을 알아챘다. 문제는 이 녀석이 자동으로 업뎃이 안되는 것 같다는 문제였다.

    VS 2005가 설치된 컴에서는 자동으로 필수 업뎃으로 포함되는 것 같았으나 설치가 안된 컴에서는 일반적인 윈도우 업뎃으로는 설치가 잘 되지 않아 모든 Redistribution Package를 설치해야 했다.

    암튼, 구글에서 Microsoft Visual C++ 2005 Service Pack 1 Redistributable Package ATL Security Update 를 검색하면 바로 다운로드 페이지가 뜬다. 

    아마도 이런 문제를 겪은 사람은 별로 없겠지만... -_- 재수 없다면... 

    암튼, .NET Framework가 어셈블리를 통해 일관성과 통일성을 추구하고자 했지만 간과한 것이 바로 이런 점이 아닌가 싶다. 필수 업데이트와 그렇지 않은 업데이트 간에 차이에서 오는 이런 사소한 실수가 어마어마한 시간의 낭비를 초래한다.

    그리고, 업데이트 안하면 안되라고 들이대는 형식의 마이크로 소프트의 행태 또한 옳지 못하다. 뭐, 안되면 너희가 바보라서 그렇단다라는 식 아냐? 젠장... 


    posted by 대갈장군
    2009. 7. 30. 00:53 프로그래밍
    C에서 가장 많이 발생하는 오류가 아마도 메모리 관련 오류가 아닌가 싶은데 이는 여러가지 원인이 존재한다. 여러개의 클래스가 겹쳐지고 상속되고 난리를 치다보면 이중해제를 하는 실수는 누구나 할 수 있다. 

    이 문서는 http://www.yolinux.com/TUTORIALS/C++MemoryCorruptionAndMemoryLeaks.html을 참조하여 작성되었다.

    1. C/C++에서 메모리 오류의 종류

    일단 메모리 공간에 따라 크게 Heap Memory 에러와 Stack (local variables) Memory 에러가 있다. Heap 메모리 영역에서 발생가능한 에러는,

    1. 이미 해제된 메모리 다시 해제 할때
    2. 할당된적도 없는 메모리 해제 할라고 할때
    3. 이미 해제된 메모리 영역에 뭔가 데이터를 쓰려고 할때
    4. 할당된 적이 없는 메모리에 뭔가 데이터를 쓰려고 할때
    5. 메모리 할당 에러
    6. 동적으로 할당된 메모리 배열에서 초과된 index의 위치를 읽거나 쓰려고 할때 

    1,2 번과 3,4번을 묶어서 볼 수 있는데 해제시에 발생하는 문제 vs 데이터 입력시 발생하는 문제로 볼 수 있다.

    스택 메모리 영역에서 발생가능한 에러들로는

    1. 정적 배열에서 초과된 index의 위치를 읽거나 쓰려고 할때
    2.  함수 포인터 오류인데 잘못된 함수 포인터의 전달로 인한 잘못된 함수 호출


    2. 메모리 누수!

    메모리 누수는 할당한 동적 메모리를 제대로 해제하지 않고 프로그램을 종료하면 발생하는 것으로써 이것이 지속되면 컴퓨터의 RAM (빠른 메모리 공간)을 모두 소모하여 버리고 Page 파일 (하드 디스크에 생성하는 가상 메모리)를 남발하게 되여 컴퓨터의 성능을 저하시키다 결국 넉다운 된다. 

    우선 이 웹사이트에서는 strdup() 함수를 예를 들어 설명하는데 좋은 예인거 같지는 않다. 아무튼 말하고자 하는 점은 malloc() 함수 (C library)로 할당된 메모리는 반드시 free()로 해제하라는 점이다.

    요약하자면, 
       1. new로 할당하면 delete로 해제, malloc으로 할당하면 free로 해제.
       2. 할당 실패하면 해제 하지 말것. (할당되지도 않은 놈 해제하면 당연히 에러)
       3. 상속될 가능성이 있는 클래스의 파괴자는 반드시 가상 함수로 선언하라
       4. 포인터 사이에 대입연산 (주소복사)는 항상 메모리 누수 및 이중 해제의 가능성을 가진다.
       5. 클래스의 대입 연산자 사용시 얕은 복사가 잃어난다면 (주소만 복사) 역시나 누수와 이중해제의 위험 존재.

    3. 메모리 부패 (Memory Corruption)

    참 단어 선택이 오묘한데 부패라고 하니 꼭 이상한 듯 들리지만 오염보다는 나은것 같아서 그렇게 표현했다. 한국어로 뭐라해야 할지 모르겠다.

    Memory Corruption은 사용자가 부주의하여 명확한 할당 없이 주소에 접근하여 데이터를 변경하려 하거나 잘못된 주소 위치로 접근하려 하는 경우를 말한다. 즉, 컴퓨터 입장에서 보면 메모리가 '부패'한 셈이다 :)

    우선 흔히 발생하는 Buffer Overflow의 경우는 할당된 메모리 양보다 더 많은 데이터를 집어 넣을때 발생하는 것이고 또 다른 흔한 Memory Corruption은 할당한 배열을 초과하는 위치에 접근하는 경우다. (인덱스 초과)

    그리고 또 흔한 경우로 포인터 변수로 선언한 변수에 new로 메모리를 할당하지도 않았는데 포인터 주소가 가지는 멤버 변수나 함수로 접근하는 경우다. 당연히 에러다.

    free() 함수로 날려버린 변수에 접근하는 것 역시 '부패한 메모리'로의 접근이다. 

    생각보다 중요한 것이 나오는데 new []로 배열을 할당한 경우 반드시 delete []로 삭제해야 한다. 이거 의외로 실수하는 경우가 많다.

    함수를 호출하여 함수 내에서 선언된 지역 변수의 주소를 넘겨와 사용할 경우 그 지역 변수는 함수 리턴 된 후에 언제든 사라질 수 있는 위험을 안고 있다. 그래서 절대로 이렇게 사용하면 안된다는 점.. 명심하자.











    posted by 대갈장군
    2009. 7. 28. 02:48 프로그래밍/C++
    나는 늘 같은 경험을 한다. 모르는 어떤 것을 알기위해 이것 저것 읽어 보다보면 끊어진 이해의 연결 고리들이 어느순간 한번에 이어지면서 한순간에 전체적인 흐름을 파악한다.

    그 흐름이 파악되면 세부적인 내용은 너무나도 쉽게 쉽게 이해가 된다. 아무리 어려운 문법이라도 '음, 이건 분명 이렇게 될 수 밖에 없지...'라는 마치 알고 있었다는 듯한 느낌마저 받는다.

    STL도 처음 나에게는 '이거 뭐야?' 라는 놈이었지만 지금은 '음, 이놈 복잡한 놈...' 이라는 정도의 느낌만 줄뿐 아예 모르는 놈은 아니다.

    STL을 알기위해서는 템플릿도 알아야 하고 포인터에 대한 이해 및 함수 포인터 그리고 배열 및 객체 지향에 대한 이해도 필요하다.

    하지만 이것은 실제적인 사용을 위해 필요한 것이고 이보다 앞서 필요한 '이해'를 위해서는 아무것도 필요없다. 무엇인가 알려고 할때는 항상 전체적인 흐름을 파악하고 왜 이것을 알아야 하는가에 대해서 먼저 답을 해야 한다.

    그것을 알지 못하고 무작정 세부적인 문법만 파고들면 금새 길을 잃어버린 아이처럼 이곳 저곳을 방황하다가 결국 지쳐 울고 만다.

    그렇다면 왜 사람들이 STL, STL하는지 알아보자. 과연 무엇이 그리도 좋다는 말인가?

    STL은 한마디로 라이브러리다. 라이브러리... 도서관... 즉, 필요한 모든 정보가 있는 곳이다. 사용자는 이 STL을 이용해 수많은 똑똑한 개발자들이 만들어 놓은 아주 편리한 함수들을 손쉽게 꺼내어 사용할 수 있다.

    프로그램을 작성하기 위해 필요한 두가지 필수 요소는 바로 알고리즘자료구조이다. 프로그램은 항상 저장된 데이터를 불러와 어딘가에 저장해 놓고 (자료구조) 그 값들을 필요에 따라 정렬, 삭제하여 (알고리즘) 원하는 것을 하는 것이다.

    바로 이 두 가지 필수 요소인 자료구조와 알고리즘을 모조리 묶어서 정리한 라이브러리가 바로 STL이다. 

    하지만 여기서 그치지 않는다. STL은 이름에서 처럼 Standard (일반화) 특성을 가지고 있다. 즉, 어떠한 자료구조이던 간에 상관없이 동일한 형태의 알고리즘을 제공한다는 점이다. 

    이로써 사용자가 각 자료구조에 대한 각각의 알고리즘 (정렬, 삭제, 추가, 대입등) 을 일일히 정의해 줄 필요가 없어진 것이다.

    이 얼마나 고마운 일인가...

    게다가 STL은 이름에 있듯이 Template를 이용해 사용자가 원하는 임의의 형태로 객체를 생성해 낸다. 즉, int를 원하면 int, float을 원하면 float, 클래스를 원하면 클래스... 말 그대로 지 맘대로 객체를 생성할 수 있다.

    고마워서 눈물이 날 지경이다... 상상을 해보라. 프로그래머가 자신이 사용해야 할 자료구조 (스택, 리스트, 큐) 같은 것을 프로그램 짤때마다 만들어야 한다면? 게다가 알고리즘까지 다 만들어야 한다면??

    이런 복잡한 일을 다 해주는 고마운 친구가 STL인것이다.

    그럼 아마도 이런 생각이 들것이다. 그럼 왜 진작부터 이거 사용안하냐고... 

    좋은 질문이다... STL은 이해하기는 쉽지만 사용하기 위해서는 C++과 C에 대한 전반적인 이해가 필수이며 몇몇 단점들이 존재하기 때문이다.

    우선 실행파일이 거대해 진다. 원래 STL은 속도만을 중요시 하기때문에 컴파일 타임에 링크하는 방법을 쓰지 않는다. 고로 정적 링크를 항상 하게 되므로 생성하는 STL의 객체 수와 사용하는 STL 함수만큼 정적 링크의 갯수가 늘어간다.

    하지만 크기의 이점을 잃어버리는 대신 '편리함'을 얻었으니 불평하기만 할 수는 없지.

    다른 문제점은 '가독성'이 열라 떨어진다는 점이다. STL을 이중 삼중으로 사용하게 되면 작성한 놈조차 '이게 뭔 말이여?' 라는 소리를 하게 되어 있다.

    즉, 템플릿의 문법 자체가 복잡한데다가 일반화 특성을 추가해버렸으니 더더욱 복잡해 보인다. 그래서 제대로 사용하기 위해서는 많은 연습이 필요하다는 점이다.

    많은 사람이 STL을 꺼려하는 이유는 복잡하기 때문인데다가 이를 대체할 좋고 쉽게 이해할 수 있는 MFC가 존재하기 때문이다. 

    하지만 내가 얻은 소스가 STL이라면 어쩌겠는가? 즉, 결국은 알아야 한다는 말이지... 에휴... 만약 템플릿이 무엇인지 모른다면 반드시 그것부터 이해해야 한다는 말은 꼭 하고 싶다...
    posted by 대갈장군
    2009. 7. 28. 01:43 프로그래밍/C
    호출 규약은 함수를 호출하는 방식에 대한 약속을 정의한 것인데 예를 들자면 호출하는 쪽에서 메모리를 정리하는지, 인수는 어떻게 전달 할 것인지, 그리고 리턴 값은 어떻게 반환할 것인지 등을 정의 할 수 있다.

    함수를 호출할 때는 스택(Stack)을 사용한다. 알다시피 스택은 메모리 영역의 아래부분에 위치하여 LIFO (Last in First out)의 원칙대로 동작하며 항상성을 유지한다. 

    함수가 호출될 때 스택에는 함수로 전달되는 인수, 실행을 마치고 복귀할 주소, 그리고 지역 변수의 정보등이 저장되는데 이것을 스택 프레임 (Stack Frame)이라고 한다. 

    이제 C에서 사용하는 각종 호출 규약을 살펴보자. 

    총 다섯 가지가 있다. __cdecl, __stdcall, __fastcall, thiscall, naked 이다.

    아마도 Visual Stuido를 사용해본 사람이라면 __cdecl과 __stdcall 그리고 thiscall을 자주 봤을 것이다. (에러 메세지에서)

    __fastcall과 naked는 C / C++ 에서는 거의 사용하지 않기 때문에 설명이 필요없다. 원래 __fastcall은 레지스터 공간을 이용해 인수를 전달함으로써 좀더 빠른 속력을 내고자 했으나 범용성이 떨어져서 사용이 힘들고 naked는 C나 C++이 아닌 다른 언어로 작성된 함수 호출시 호출 규약을 정하지 않고 사용할 때 붙이는 접미사인데 이 역시도 나에게는 거의 사용 가능성이 0%다.

    그러면 남은 것이 3가지인데 이중에서 thiscall은 멤버 함수이면 자동으로 붙게되는 호출 규약이다. 이 호출 규약이 붙으면 자동으로 객체의 포인터가 인수로 전달된다는 점이 특별하다. 

    이 놈은 사용자가 임의로 가져다 쓸수 있는 선언자가 아니고 컴파일러가 알아서 붙여주는 '호출규약'일 뿐이므로 이 마저도 제외해야 한다. 결국 남는것은 딱 두개. __cdecl과 __stdcall이다.

    이 두놈의 큰 차이점 두가지는,

    1. 스택을 정리하는 주체가 다르다는 점이다. __cdecl은 호출한 쪽에서 정리하고 __stdcall은 호출된 함수측 에서 스택을 정리한다.

    2. 가변 인수 함수라면 무조건 __cdecl을 사용해야 한다. 왜냐면 __stdcall은 함수측에서 스택을 정리해야 하는데 가변인수이므로 인수가 몇개나 들어올지 미리 알 길이 없다. 그래서 가변 인수 함수의 경우는 무조건 __cdecl을 사용해야 한다. 

    호출 규약이 불일치 하는 경우에 발생하는 문제점은 치명적이다. 헌데 이런 오류가 발생하기는 상당히 힘든데 C로 작성해서 C로 호출하는 경우 호출 규약이 불일치 할 경우는 거의 없기 때문이다.

    아무튼 어찌 어찌 해서 호출 규약이 서로 불일치하게 되면 호출한 쪽에서는 함수쪽에서 스택을 해제할 것이라 굳게 믿고, 반대로 함수측에서는 호출한 쪽에서 스택을 해제할 것이라 굳게 믿게되는 이상한 상황이 발생한다.

    그렇게 되면 항상성을 잃게 되고 메모리에 오류가 발생하여 99.99992% 확률로 다운될 것이다. 다운이 안되더라도 엉망징찬이 될 게 뻔하다. 

    아무튼, VS 2005를 사용하여 프로그램을 작성하다 보면 에러 메세지에 __cdecl과 __stdcall등 다양한 호출 규약의 접미어가 붙어 있는 것을 보게 되는데 이것을 보고 당황할 필요가 없다는 것이다. :)
    posted by 대갈장군
    2009. 5. 22. 03:08 프로그래밍/Windows API
    프로그램을 짜려면 메모리가 참 중요한데... 차근 차근 책을 보며 다시 한번 정리해 보았다.

    Win32 는 32비트로 메모리 주소공간을 표현한다는 의미로 Windows 95 부터 사용된 것으로 알고 있다. Win16은 Windows 95가 나오기 전에 사용된 것으로 주소공간을 표현하는 비트의 수가 최대 16 비트로 최대 표현 가능한 주소공간이 2의 16승 밖에 되지 않는다. 2의 16승은 64K 군. 

    고로 상위 컴퓨터들이 등장하면서 확장된 메모리 공간 표현을 위해서는 Offset을 추가하는등 복잡한 계산법이 필요했단다... 그러다 보니 자연스레 Win32로의 확장이 이루어 진것인데, 엄밀히 말해서 Win16의 문제점은 두가지였다.

    1. 증가하는 메모리 용량을 16비트의 레지스터 공간으로는 쉽게 표현이 불가능했다. 
    2. 모든 프로그램이 같은 주소 공간에 존재하였으므로 포인터만 가지고 있으면 수퍼맨 처럼 어디든 액세스 가능.

    바로 2번은 심각한 안정성 문제를 초래한다. Win16은 주소 공간과 실제 물리적 메모리가 서로 1대 1 대응 관계를 가지고 있었다. 즉, A라는 응용 프로그램의 포인터 값은 실제 물리적 메모리의 주소이며 이는 B라는 응용 프로그램에서도 마찬가지로 접근 가능한 고유한 값이었다.

    하지만 이 개념이 Win32에 들어오면서 확 바뀌는데... 어떻게 바뀌냐면...

    1. 가상 메모리의 등장. 가상 메모리 = 물리적 RAM + 페이징 파일 
    2. 독립적인 4G 바이트의 가상 주소 공간이 각 응용 프로그램에 할당. 
    3. 각각의 4G 바이트 공간은 페이지 테이블을 통해 가상 메모리에 연결. 

    일단 가상 메모리가 등장하는데 이놈은 실제 컴퓨터가 가지고 있는 RAM 공간 + 페이징 파일이다. 페이징 파일은 물리적인 하드 디스크 공간을 메모리로 사용하는 것을 말하는데 일반적인 RAM보다 속도가 떨어질다는 것 외에는 RAM과 같은 놈이다.

    그리고 각 응용 프로그램 당 4G 바이트라는 어마어마한 가상 주소 공간이 할당 되는데 이 가상 주소 공간은 실제로 할당된 메모리가 아니라 최대 저만큼 쓸수 있다는 말이다. 응용 프로그램이 실제로 무엇을 할당하거나 운영 체제의 모듈을 불러들일 경우 중간자 역활을 하는 페이지 테이블을 통해서 가상 메모리에 메모리 공간을 실제적으로 할당한다.

    고로 각 응용 프로그램은 각자가 독립적인 4G 바이트의 주소 공간을 가지므로 서로가 서로에게 간섭을 할수가 없다. 즉, 안정성이 확보된것이다. 

    응용 프로그램은 운영 체제가 메모리를 관리 (페이지 테이블에 의한) 하여 줌으로써 그 댓가로 안정성을 획득한 셈이다. 이런 구조의 한가지 단점이라면 프로세스간에 (응용 프로그램 간에) 데이터 교환이 용이 하지 않다는 점이다.

    왜냐면 서로가 가지고 있는 주소 공간의 독립적이므로 A라는 응용 프로그램이 가지고 있는 주소 값은 B에서는 엉뚱한 값을 가지고 있을 수 있기 때문이다. 이것을 해결하기 위해 IPC 혹은 메모리 맵 파일을 사용해야 한다.


    posted by 대갈장군
    2009. 5. 16. 01:23 프로그래밍/DirectShow
    제목부터 아리까리하다. 내가 교수님으로부터 이상한 프로그램 하나를 건네 받고 그 놈을 요리조리 뜯어보다 보니 그 원리가 제목과 같더라...

    잘은 모르지만 OpenGL에서 동영상 파일 재생은 매우 제한적이다. NeHe 홈페이지에 가보면 단순한 Mpeg 파일 재생 OpenGL 프로그램이 있기는 하지만 Mpeg 버전에 따라 되는 파일도 있고 안되는 파일도 있다. 한마디로 불완전하다.

    수많은 형태의 동영상 재생 파일을 손쉽게 읽고 재생하기 위해서는 DirectShow 의 강력한 필터가 필요하다. 다만 OpenGL을 기반으로 프로그램을 작성하게 되면 DirectShow와 어떻게 연동해야 할지가 문제다.

    물론 DirectX 기반으로 작성하면 (안해봤지만) 훠어얼씬 쉽겠지. VMR-9이라는 멎진 놈이 존재하기 때문에 텍스쳐 맵핑을 걱정할 필요가 없다.

    하지만 죽으라는 법은 없다고 하지 않았던가... DirectShow의 필터중에 동영상 샘플 추출 필터가 있으니 바로 ISampleGrabber 를 이용하는 것이다.

    이놈을 이용하게 되면 디코더를 지나자 마자 샘플 추출기 필터로 데이터가 흘러 들아가게 되고 샘플 추출기 필터는 새로운 데이터가 들어오면 SampleCB() or BufferCB() 함수를 콜백으로 호출하게 된다.

    이 두 방식의 차이는 IMediaSample 인터페이스를 직접 넘겨주는가 그렇지 않는가에 있다. 뭐 중요한 차이는 아닌듯 싶다. 직접 넘겨주지 않는다면 클래스 멤버 변수에 따로 변수들을 저장해 두면 그만이고..

    데이터가 넘어오면 그 길이와 주소를 이용해서 적당한 작업을 하면 된다. 하지만 주의할 것은 콜백 함수가 리턴 되기 전까지 필터는 흐름이 멈추게 된다. 고로 메모리 복사 작업 (텍스쳐 복사 작업)은 콜백 함수 내부에서 하면 성능 저하를 가져 올수도 있겠다는 생각이 든다.

    하지만 막무가내로 리턴하고 다음 데이터를 받아버리면 공유된 버퍼 자원에 대한 자원 경쟁이 발생하거나 새로운 이미지 데이터가 업데이트 되는 도중에 텍스쳐 복사를 한다거나 하는 문제가 발생하지 않을까? 

    만약 그렇다면 해결방법은 Critical Section을 이용해야 한다... 라면 성능에는 아무런 문제가 없을까?...

    아무튼 현재 사용중인 프로그램은 이런 형태의 흐름을 가지고 있으며 이를 이용해 다양한 형태의 입력 파일들 (mpg, jpg, avi 등) 을 아주 손쉽게 OpenGL 텍스처로 입혀서 사용중이다.


    posted by 대갈장군
    2009. 3. 17. 23:02 프로그래밍/DirectShow

    앞서본 널 렌더링이나 필터 추가후 렌더링의 경우 RenderFile() 함수를 사용해서 자동으로 필터 그래프가 완성되도록 했으나 필터 렌더링은 필터의 Output 핀을 사용한 렌더링이다.

    즉, 가장 세밀한 조작이 가능한 렌더링 방식이다. 그중에서도 소스 필터 자동 렌더링이 있는데 이는 RenderFile() 함수를 IGraphBuilder::AddSourceFilter() 함수와 IGraphBuilder::RenderFilter() 함수가 대체하는 방식이다.

    1. 필터 그래프 매니저 생성
    JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGB);


    2. AddSourceFilter() 함수로 소스 필터 추가하면서 pSourceFilter에 그 소스필터의 진입점(포인터) 얻어 오기
    IBaseFilter *pSourceFilter;
    JIF(m_pGB->AddSourceFilter(wFileName, wFileName, &pSourceFilter);


    3. 필터 렌더링!
    if(pSourceFilter != NULL)
          LIF(RenderFilter(m_pGB, pSourceFilter));


    4. AddSourceFilter에 의해 추가로 생성된 필터 인터페이스 (리모컨) 삭제
    SAFE_RELEASE(pSourceFilter);

    근디... 자동 소스 필터 렌더링과 수동 소스 필터 렌더링은 그 차이가 크지 않네...

    수동 소스 필터 렌더링은 우선적으로 소스로 사용할 필터를 추가시켜 놓고 자동 필터 렌더링과 마찬가지로 AddSourceFilter와 RenderFilter 함수를 차례로 호출하는 것일뿐 차이가 없다.

    결국 정리하지만 자동 소스 필터 렌더링은 파일이나 URL를 이용해 AddSourceFilter() 를 수행하고 곧바로 RenderFilter를 이용해 Output Pins를 렌더링 한다.

    반면 수동 소스 필터 렌더링은 소스 필터로 사용하고자 하는 필터를 우선 추가한 후, 필터들을 그래프에 추가시키고 그 다음에 AddSourceFilter()와 RenderFilter() 함수를 이용해 Pin 렌더링 수행.
    posted by 대갈장군
    prev 1 2 3 4 next