2010. 7. 8. 22:49

typedef를 활용한 private범위의 struct나 class가져오기


뭐.. private에 선언한 struct나 class를 외부에서 쓰고 싶은 경우엔.. 그냥 public에 선언해버리면 되지만.. 무조건 public보다 외부에 알리는데는 이런 심볼이름을 해야겠다.. 라는 정의가 필요할 수 있다. 집안에선 자랑스런 남편이고 아들일테고 밖에서는 또 역할이 다르지 않은가? 여튼 고민하시고 혹시나 필요하신 분을 위해...
-------- A.h --------
#include "B.h"
class A
{
    B::BST* st;
};

-------- B.h --------
class B
{
public:
 typedef struct ST BST;

private:
 struct ST
 {
 };
};

2010. 7. 8. 22:48

컴파일러에 암시 또는 힌트


컴파일러에게 힌트를 주는 행위
특징 : 중복되어도 에러가 나지 않는다. 현재 시점에서 그것이 존재한다고 가정하고 컴파일한다. 나중에 링크할때 결합하게 된다.

extern변수의 선언(정의 아님)
extern int g;

앞으로 나올 클래스다. 라고 힌트
class Test;

구조체도 마찬가지
struct Test;

단, 클래스(구조체)내부에 들어있는 클래스(구조체)는 위와 같은 방법으로 불가능하다. 현 시점으론 내포된 클래스(구조체)인지 모르기 때문이다.
class Test::ST; X

함수의 원형
void abc();

인라인함수는 힌트는 아니지만 중복된다고 해서 에러는 나지 않는다. 단.. 중복되었을 때 함수가 수정된 경우 영향이 있는 헤더의 인라인 함수를 추가하니 주의

같은 형식의 typedef는 힌트는 아니다. 다만 중복된다고 해서 에러는 나지 않는다. 단, 다른 심벌로 바꾸면 에러가 발생함(typedef int INT; 했다가 typedef int Int;등으로 바꾸면 에러)
typedef int INT;
2010. 5. 31. 19:34

리다이렉션(< or >)을 활용한 파일입출력


리다이렉션은 프로그래밍관련 용어는 아니며, 표준입력과 표준출력을 위한 연결을 다른쪽으로 변경할 수 있는 기능입니다. 이는 UNIX와 MS-DOS등 많은 운영체제들이 지원한다. 여기서 말하는 표준입력과 표준출력은 각각 stdin, stdout을 뜻하며 키보드와 모니터를 뜻합니다.

< file
파일내용을 표준입력으로 읽어들인다.
이는 파일내용을 scanf와 같은 함수(표준 입력으로부터 읽어들이는)로 읽어들인다고 생각하시면 됩니다.

> file
표준출력내용을 파일에 쓴다.(표준출력이 파일로 바뀌는 것이라 생각하면됨)
이는 파일내용을 printf와 같은 함수(표준 출력으로 출력하는)로 출력하신다고 생각하시면 됩니다.

>> file
표준출력내용을 파일로 추가한다.

2> file
표준에러내용을 파일에 쓴다.

2>> file
표준에러내용을 파일에 추가한다.

테스트 소스
#include <stdio.h>
void main()
{
 char ch;
 char res;
 char str[512];
 char str2[512];
 char str3[512];
 char str4[512];
 scanf("%s", str );
 scanf("%s", str2 );
 scanf("%s", str3 );
 scanf("%s", str4 );
 printf("%s\n", str );
 printf("%s\n", str2 );
 printf("%s\n", str3 );
 printf("%s\n", str4 );
}

테스트 파일(test.txt)
aaabbb
dfsfds
eeee rrr

빌드 후 테스트
C:\>test.exe < test.txt > result.txt

결과(result.txt)
aaabbb
dfsfds
eeee
rrr

2010. 4. 29. 21:16

ZeroMemory


선행 학습이 필요합니다.
http://ekessy.tistory.com/10 C/C++에서의 일반변수, 포인터변수, 메모리에 대해
http://ekessy.tistory.com/11 memset, memcpy, memmove, memcmp 멤형제들



ZeroMemory의 원형에 가보면 다음과 같습니다.
#define ZeroMemory RtlZeroMemory

RtlZeroMemory의 원형에 가보면 다음과 같습니다.
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))

즉, 단순히 ZeroMemory는 memset를 활용한 전처리기 매크로일 뿐이라는 점입니다. 비슷한 예로 여러가지
더 있는데,
#define MoveMemory RtlMoveMemory
#define CopyMemory RtlCopyMemory
#define FillMemory RtlFillMemory
#define ZeroMemory RtlZeroMemory

들이 각각

#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))
#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))
#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))

으로 정의되어있습니다.

다시 본론으로 들어가서, 아래와 같은 구조체가 있을 때,
struct Test
{
   int n;
   char c;
};


Test t;
ZeroMemory( &t, sizeof(t) );
//를 하게되면 컴파일시 자동으로 아래처럼 대체된단 얘기입니다.

Test t;
memset( &t, 0, sizeof(t) );



즉, t의 시작주소부터 t가 차지하는 바이트수까지 0으로 채워준다를 뜻합니다.

하지만 주의사항이 있는데요. D3DXMATRIX값이나 D3DXQUATERNION등은 값자체가 0이면 안되는 경우가 많고, STL의 경우 의도치않는 데이터(0으로 초기화시키면 안되는)가 0으로 초기화되기 때문에 항상 주의를 요합니다. 따라서 순수한 데이터만 가지고 있는 구조체의 인스턴스에 취하는 것이 적절하겠습니다.
2010. 4. 29. 20:58

memset, memcpy, memmove, memcmp 멤형제들


선행 학습이 필요합니다.
http://ekessy.tistory.com/10 C/C++에서의 일반변수, 포인터변수, 메모리에 대해


memset, memcpy, memmove, memcmp등은 메모리 관련 함수들이다.

각각 메모리를 특정정수값으로 초기화하고, 메모리를 복사하고, 이동시키고, 비교하는 함수가 되겠다.

memset
void * __cdecl memset(_Out_opt_bytecapcount_(_Size) void * _Dst, _In_ int _Val, _In_ size_t _Size)

void* _Dst // 초기화할 시작 주소
int _Val // 초기화할 값
size_t _Size // 대상의 크기
리턴 //_Dst의 시작주소

memset은 지정된 수를 1바이트단위로 지정된 주소부터 지정된 크기까지 채워주는 함수이다.
예를 들어
int n;
memset( &n, 10, sizeof(int) );

이렇다면 n에 0x0a0a0a0a에 들어가게된다.
1바이트단위라고 했는데, 1바이트가 넘어선 수를 넣으면 어떻게 될까?

int n;
memset( &n, 321, sizeof(int) );

이렇다면 n에 0x41414141이 들어가게 된다. (321이 16진수로 141이다.)


memcpy
void * __cdecl memcpy(_Out_opt_bytecapcount_(_Size) void * _Dst, _In_opt_bytecount_(_Size) const void * _Src, _In_ size_t _Size)

void* _Dst // 복사될 대상의 시작 주소
const void* _Src // 복사할 소스의 시작주소
size_t _Size // 복사되는 총 바이트 수(복사할 소스의 시작주소부터 + 바이트수가 복사됨)
리턴 //_Dst의 시작주소

memcpy는 지정된 주소부터 지정된 크기까지 복사하여 복사될 주소부터 붙여넣기(?)하는 함수이다. 물론 메모리내부의 값을 복사시켜준다.

// dest에 321이 복사된다.
int n = 321;
int dest;
memcpy( &dest, &n, sizeof(int) );

// dest에 0xcccccc41이 복사된다. dest를 먼저 초기화를 안했기 때문이다.
int n = 321;
int dest;
memcpy( &dest, &n, sizeof(char) );

// dest에 0x00000041이 복사된다.(10진수로 65)
int n = 321;
int dest = 0;
memcpy( &dest, &n, sizeof(char) );

// dest에 안녕하세요. 가 복사된다.
char* str = "안녕하세요.";
char dest[128] = "";
memcpy( dest, str, strlen( str )+1 );


물론 구조체도 가능하고, 배열도 가능하다.
int arr[128] = { .......... };
int dest[128];
memcpy( dest, arr, sizeof( arr ) );

int* test[5];
int* dest[5];
memcpy( dest, test, sizeof( test ) );


memmove
void *  __cdecl memmove(_Out_opt_bytecapcount_(_Size) void * _Dst, _In_opt_bytecount_(_Size) const void * _Src, _In_ size_t _Size);

void* _Dst // 이동될 대상의 시작 주소
const void* _Src // 이동할 소스의 시작주소
size_t _Size // 이동되는 총 바이트 수(복사할 소스의 시작주소부터 + 바이트수가 복사됨)
리턴 //_Dst의 시작주소

memcpy와 비슷하기 때문에 생략한다.


memcmp
int __cdecl memcmp(_In_opt_bytecount_(_Size) const void * _Buf1, _In_opt_bytecount_(_Size) const void * _Buf2, _In_ size_t _Size)

const void* _Buf1 // 비교할 대상1의 시작주소
const void* _Buf2 // 비교할 대상2의 시작주소
size_t _Size // 비교 크기
리턴 // _Buf1이 _Buf2보다 작은경우  0보다 작은값이 리턴, 같을경우 0이 리턴, _Buf1이 _Buf2보다 클경우  0보다 큰값이 리턴

memcmp는 단순히 메모리의 값들을 비교해줍니다. 이를 응용하여 아래와 같이 문자열을 비교할 수도 있구요.

char* str1 = "안녕하세요?";
char* str2 = "안녕하세요?";

if( memcmp( str1, str2, strlen(str1)+1 ) == 0 )
{
   printf("두 문자열이 같습니다." );
}

그냥 비교하는 것이 아닌, 당연한 얘기지만 범위를 지정할 수가 있는데요... 다음과 같은 처리가 가능합니다.

int n = 321; // 0x141
int n2 = 65; // 0x41

if( memcmp( &n, &n2, sizeof(char) ) == 0 )
{
   printf("첫번째 바이트 부분의 값이 같습니다." );
}

char* str1 = "hoo ekessy";
char* str2 = "haha ekessy";
if( memcmp( &str1[4], &str2[5], strlen("ekessy")) == 0 )
{
   printf("ekessy");
}
2010. 4. 29. 19:42

c/c++에서의 일반변수, 포인터변수, 메모리에 대해


특정 언어에 국한되는 문제는 아니지만, c/c++ 이하 c에서의 일반 변수와 포인터 변수와 메모리에 대한 얘기를 해볼까 합니다. 일단 메모리는 선형구조를 갖습니다. 그리고 메모리 각각의 주소를 가지고 있어요. 현실세계의 주소의 의미와 같습니다. 메모리라는 나라가 있다면 각 바이트는 그 나라의 국민이라고 하면 될까요?
앞서 살짝 언급했지만, 메모리의 주소는 바이트단위로 나누어집니다. 시작주소는 0이구요. 0부터 쭈욱 뻗어나갑니다. c에서는 이를 16진수로 표현하고 있습니다. 16진수 2자리가 1바이트라는건 아시죠? 그렇다면 0x00000000부터 ...까지 되어있겠네요. 한계는 거의없다고 봐도 과언이 아닙니다. 이게 물리적인 메모리(램)의 크기를 따라가는 것이 아니기 때문입니다. (실례로 어떤 프로그램 2개를 띄웠을때 같은 주소를 참조하고 있는데 값이 다른경우가 있습니다. 이 문제는 관련 책자를 찾아보세요.[각주:1])  하지만 최대값은 0xFFFFFFFF이 되겠습니다. 여기서 잠깐, 왜 주소를 4바이트로 사용할까요? 그 이유는 4바이트만으로도 메모리내부의 주소를 모두 참조할 수 있기 때문입니다. 그래서 적정량을 4바이트로 두는 것이죠. 이것은 포인터 변수의 크기이기도 합니다.

일반변수의 포인터변수 모두는 메모리에 저장됩니다. 단, 저장되는 구조가 조금 다릅니다. 하지만 공통적으로 선언됨과 동시에 메모리의 어딘가[각주:2]에 해당변수의 크기만큼 배정됩니다.(해당변수를 sizeof한 만큼이 배정됩니다.) 하지만 저장됨과 동시에 일반변수는 값이 저장되는데 반해, 포인터변수는 메모리의 주소가 저장됩니다.
예를 들어 아래처럼 두 변수가 각각 0x0015F7FC, 0x0015F7F0위치에 배정되었다고 칩시다.
int n; // 0x0015F7FC
int* p; // 0x0015F7F0

변수가 초기화되어있지 않았기 때문에 쓰레기값이라고 불리는 값 0xCCCCCCCC가 들어있을겁니다. 두 변수 모두 예외는 아닙니다. 일단 n의 주소로 가보면(메모리의 주소 입력부에서 &n입력),

역시 cc cc cc cc로 채워진 걸 볼 수 있습니다. 시작주소 + 변수가 차지하는 바이트수를 읽으시면 되는데, int형이기 때문에 4바이트를 차지하니까 0x0015F7FC부터 +4바이트를 읽으시면 됩니다.

마찬가지로 p의 주소에 가봅시다. (메모리의 주소부터 &p를 입력)

역시 cc cc cc cc로 채워진 걸 볼 수 있습니다. 위와 마찬가지로 시작주소 + 변수가차지하는 바이트수까지 읽어오시면 되는데, 포인터 변수이기 때문에 0x0015F7F0부터 +4바이트를 읽으시면 됩니다.

이번엔 다음과 같이 초기화를 사용해봤습니다.
재컴파일을 했더니 위치가 약간 달라졌네요. 각각 0x002AFE60, 0x002AFE54위치에 배정되었다고 칩시다.
int n = 321; // 0x002AFE60
int* p = &n; // 0x002AFE54

먼저 n의 주소로 가봅시다.(&n)

0x002AFE60부터 4바이트를 읽어옵시다.
41 01 00 00 이 나오게 되는데 이걸 10진수로 바꿔보면 321이 아닌 1090584576이 나오게 됩니다..
이 문제는 리틀인디안 빅인디안(big-endian) 문제로, 인텔에선 리틀인디안(little-endian) 방식을 채택했기 때문에 다소 이상하게 나오는 것처럼 보이는 겁니다. 뒤에서부터 읽어오시면 됩니다. 00 00 01 41 그러면 321이 나오게 됩니다.

이번엔 p의 주소로 가봅시다. (&p)

0x002AFE54부터 4바이트를 읽어옵시다.
위와 마찬가지로 리틀인디안 방식으로 읽어오면,
00 2a fe 60이 됩니다. 바로 이것이 n의 주소가 됩니다. &n = 0x002AFE60

즉, 일반변수의 경우 일반적인 값이 저장되고, 포인터변수의 경우는 메모리의 주소가 저장되는 셈입니다. 변수의 주소라고 쓰려다가 고쳤는데, 그 이유는 다음과 같이 주소를 적당히 대입해도 가능하기 때문입니다. 하지만 이 방법은 다소 위험한 방식으로 거의 사용하진 않습니다만.. 일부 임베디드 프로그래밍에서는 자주 사용되는 듯 합니다.
int* p = (int*)0x002AFE60;



정리해보면 일반변수와 포인터 변수와의 차이는 내부적으로 저장되는 값의 형태 차이입니다.
int n;
int* p;
n = 100;
p = &n;

printf("%d", n ); // n의 값
printf("%d", p ); // p의 값(n의 주소값)


여기서 잠깐, 주소를 표현하는데 있어서 아스트릭 연산자를 써서 해당주소의 값을 가져올 수 있습니다.
단, 가져올 때는 해당 변수의 자료형의 크기만큼 가져옵니다.
/* 321이 나옵니다. 먼저 (&n을 이용해서 잠깐 주소값으로(포인터변수로) 변형되는데, 그후 아스트릭 연산자가 있으므로 포인터 변수의 원형(int)의 크기만큼 값을 읽어옵니다.) */
int n = 321;
printf("%d", *&n );

/* 포인터 변수 자체가 주소값이기 때문에 아스트릭 연산자를 이용해 값을 가져올 수 있습니다.
p가 저장하고 있던 값(n의 주소)부터 p의 원형(int)의 크기만큼 가져옵니다.
321이 나옵니다.*/
int* p = &n;
printf("%d", *p ); 

/* 아래처럼 하면 포인터 변수의 값(n의 주소값을 가져옵니다.)
먼저 p의 주소를 가져오게되는데, 따라서 이중포인터형으로 잠깐 변형됩니다. 그 후 아스트릭 연산자가 있으므로 해당 포인터 변수의 원형(int*)의 크기만큼 값을 읽어옵니다. */
printf("%d", *&p);

// 아래처럼하면 321이 나옵니다.(n의 주소에 있는 값)
printf("%d", **&p);

// 아래처럼 하면 어떤값이 나올까요?
// p의 주소의 값(n의 주소)를 char*형으로 바꾼 후, 그 주소의 값을 가져옵니다.
// 방금 원데이터형의 크기만큼 가져온다고 했는데, 원래 int형이었지만, char형으로 바뀌었기 때문에,
// 1바이트만 가져오게 됩니다.
// 00 00 01 41 을 읽어와야 정상이지만, char형으로 캐스팅했기 때문에 41만 읽습니다.
// 즉 65가 출력됩니다.
printf("%d", *(char*)*&p );


  1. 리버스 엔지니어링 관련 도서나 프로그램 해킹 관련도서를 보시면됩니다. 또는 메모리관리방법과 가상메모리 페이징 등을 참고해보세요. [본문으로]
  2. 여기서도 실제 메모리의 위치가 아닙니다. 프로세스별로 생성되는 가상메모리의 위치라고 보시면 되겠습니다. [본문으로]
2010. 4. 20. 03:45

알수없는 위치에 메모리릭이 발생하는 경우 검출법(vs2008기준)


다음과 같은 구문을 활용해 메모리 릭을 검출하는 경우(혹시 아래와 같은 구문을 처음 보신다면 메모리릭검출을 검색해서 관련부분을 살펴보고 오세요.)

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

// 출력창으로 정보를 출력시킨다.
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG  );
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG  );
_CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_DEBUG  );

#define DEBUG_NORMALBLOCK new ( _NORMAL_BLOCK, __FILE__, __LINE__ )
#ifdef new
#undef new
#endif
#define new DEBUG_NORMALBLOCK


이런 케이스나
c:\test\test.cpp(9) : {228} normal block at 0x00666458, 4 bytes long.
 Data: <    > CD CD CD CD

이런 케이스로 메모리릭이 났다는 것을 알려준다.
{81253} normal block at 0x03BB0AD0, 16 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

첫번째의 경우 파일명, 라인까지 친절하게 알려주기 때문에 메모리릭을 잡는데 어려움이 없지만
두번째의 경우 파일명과 라인이 없어 다소 당황스러울 수도 있다.(당연히 나올거라 예상했을거에요...)
하지만 이를 해결하기 위한 기타 여러가지 정보들이 있다.

다시 두번째 케이스를 보면,
{81253} normal block at 0x03BB0AD0, 16 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

여기서 81253는 몇번째로 할당했는가를 나타낸다. 그리고 0x03BB0AD0 주소에서 릭이 발생했다는 것을 알 수 있다. 여기서 normal block은 new 또는 malloc중 하나로 동적할당 했다는 얘기고 총 16바이트가 릭이 발생했단 얘기다. 우리는 여기서 81253과 0x03BB0AD0 두가지 정보를 가지고 브레이크 포인트를 걸 예정이다.


첫번째 방법

main함수의 최상단에 다음과 같은 구문을 삽입
_crtBreakAlloc = 81253;

그리고 디버깅을 해보면, 해당지점에 브레이크포인트가 걸린다. 콜스택으로 따라가면 어디에서 릭이 발생했는지 알 수 있다.


두번째 방법

일단 F11로 디버깅을 시작한다.
ALT+F9를 눌러서 중단점 창을 연 후, 새로만들기>새데이터중단점에서 주소부분에 저 주소(0x03BB0AD0)를 입력하고, 적당한 바이트(위의 경우는 16)를 기입하고 확인
F5로 계속 진행하여 그 부분을 찾아낸다.