2010. 7. 20. 23:01

'형식 캐스팅' : ...로의 변환이 있지만 액세스할 수 없습니다.


CTest* a;
CParent* p = (CParent*)a;
등과 같이 부모클래스로 캐스팅할때..

아래처럼 실수로 쓰지 상속관계를 정의하지 않은 경우에 private으로 자동설정되어 접근할 수 없어 발생하는 에러다. 간단히 상속관계를 public으로 바꿔주면 해결된다.

CTest : CParent
{
};
를..

CTest : public CParent
{
};
로 바꿔주면 된다.


애초에 private 상속은 두가지 규칙을 가진다. 아래는 Effective C++에 나온 부분을 발췌한것.
첫번째로 부모클래스로의 변환불가
두번째로 부모클래스의 멤버들이 자식클래스의 private 멤버가 된다.(출처 Effective C++)
기타..

따라서 저런 에러가 발생하는 것이다.
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. 7. 7. 19:43

파일간 #include 설계에 대한 생각


프로그래밍을 반복할 때마다 어떻게해야 include를 효율적으로 나타낼수 있을까 수없이 고민을 해왔다. 먼저 필요한 헤더만 포함하는 것은 가독성에 상당히 좋으므로 참으로 현명한 방법이라 생각한다. 하지만 이는 때때로 치명적이다. c/c++에서는 include를 중복으로 해버리면 에러가 나버리고, 교차포함한 경우 짜증지수를 완전 올려주는 원인이 되기도 한다. 그 외로 include가 필요하지 않은 곳에 하거나 include 설계를 잘못한 경우 엄청난 컴파일시간에 직면할 수도 있기 때문이다. 그렇다면 최고의 방법은 무엇일까.. 아직도 잘 모르겠다.

1. 아무것도 포함하지 않는 헤더는 필요하다면 일단 무조건 포함시킨다.
'아무것도' 라는말이 좀 걸리는데 사실 c/c++ 표준라이브러리는 포함되어도 아무것도 포함되지 않은 헤더로 간주한다. c/c++ 표준 라이브러리에서 우리의 라이브러리를 포함할 이유가 없기 때문에 교차포함의 두려움이 없다. 따라서 이건 명백하게 포함시킬 수 있는 부분이다. 헤더가 아무것도 포함하지 않았으므로 교차포함의 두려움은 없다. 흔히 이런 헤더는 어떠한 자료구조를 모아놓은 경우가 많다. 버텍스를 정의하는 구조체.. 어떤 기능을 정의하는 구조체. 만약 그 헤더에 우리 라이브러리중 하나의 헤더를 포함하고 있다면 조심할 필요가 있다.

2. 헤더에는 짧은 시간에 자주 실행되는 인라인 함수만 포함시킨다.
이것은 당연한 얘기로.. 짧은 시간에 자주 실행되는 함수라면 최적화를 위해 인라인 함수(물론 아주 짧은 함수의 경우)로 만들어야 한다. 그것을 제외하고는 소스파일에 포함시키는 방법이다. 아래는 상속을 받은 경우 포함할 필요가 없다는 것을 보여준다. 어차피 컴파일은 소스파일로 하는 것이니까. 단, 포함 순서가 중요하다
----------- Parent.h --------------
class CParent
{
};

----------- Child.h --------------
class CChild : public CParent
{
}

----------- Child.cpp --------------
#include "Parent.h"
#include "Child.h"
...


3. 부득이 교차포함해야 하는 경우는 부모급 헤더에서만 포함시킨다.
관리하기 아주 쉬운 형태로 부모급헤더에만 포함시키고, 자식금 헤더에서는 class CTest; 라는 식으로 암시만 주고, 소스파일에서 부모급헤더를 포함시키는 방법이다. 프로그래밍하다보면 자식급의 부류가 상당히 많아질 수 있는데.. 그럴때 관리하기 아주 편한 형태라 생각한다. 하지만 이런 구조의 경우 포함되는 부모급클래스를 인라인함수로 호출할 수 없다. 그러나 자식급 클래스는 대부분 부모급 클래스와 생명줄이 연결되어 있긴 하지만 부모클래스에 어떤 처리를 부탁하는 일이 없기 때문에 인라인 함수를 만들라고 하더라도 부모급 클래스의 레퍼런스만 리턴해주는 경우가 대부분이기 때문에 문제가 되는 경우는 드물다. 하지만 부모급 헤더에서는 인라인 함수가 자주 나올 확률이 높다. 그렇기 때문에 부모급 헤더에만 포함시키는 것이 좋다.
이 경우는 장점과 단점을 모두 지니는데, 그렇게 하다보면 부모급 클래스에선 다량의 헤더파일이 추가될 것이고 그것을 필요로 하는 모든 소스파일은 모두 다시 컴파일 될것이다.(물론 헤더가 수정되었을 때 이야기다) 하지만 포함시킬때는 부모클래스헤더만 포함시키면 그 예하에 포함되어있는 모든 헤더가 포함된셈이므로 포함할 필요가 없긴하다. 하지만 이 경우 가독성이 떨어지는 문제가 생기기도 한다. 아래를 보자.
----------- Mgr.h --------------
#include "A.h"
#include "B.h"
#include "C.h"
class CMgr
{
   A* a;
   B* b;
   C* c;
};

----------- A.h --------------
class CMgr;
class CB;
class CA
{
   CMgr* mgr;
   CB* b;
};

----------- A.cpp --------------
#include "A.h"
#include "Mgr.h"

CA::CA()
{
   mgr ..
   b ..
}

단순히 저렇게 처리할 수도 있다는 얘기다. 심지어 A.cpp의 A.h를 없애도 상관은 없다. (지금 상태로는) 가독성이 떨어진다고 말했었는데 A.cpp에 B.h가 include되어있다면 A.cpp에서 B.h를 쓰고있구나.. 라는 정보를 얻을 수 있다. 하지만 저 상태로는 B.h를 쓰고 있구나 하는 정보를 당장엔 알 수 없다. 또한 여기서 B.h헤더를 고쳐주게 되면 Mgr.cpp가 재컴파일되고 A.cpp도 재컴파일된다. 그 이유는 B.h가 Mgr.h에 포함되어있고 Mgr.g는 A.cpp에 포함되어 있기 때문이다. 이것은 프로젝트가 거대하다면 단 한글자만 고쳐도 엄청난 컴파일시간이 도사릴 수 있다는 것을 알려준다. 따라서 Mgr.h만 포함하면 모두 다 될거란 생각은 버려야한다. 컴파일 시간이 두렵다면.. 하지만 불행중 다행으로.. B.h가 고쳐진 경우가 아닌, B.cpp가 고쳐진경우는 링크만 다시 되는 형태로 된다. 왜냐하면 헤더는 변하지 않았으니까. 따라서 인라인함수가 많거나 템플릿은 저렇게 포함될 때 고민좀 해봐야한다. 또한 #pragma once 등으로 A.cpp에 Mgr.h와 B.h를 둘다 포함시킬 수 있다. 그러면 가독성은 일단 해결이다.


4. 1, 2번만을 활용하고 소스파일엔 필요한 것만 포함시킨다.
어떻게 보면 3번은 컴파일시간을 대량으로 늘리는 계기가 될수도 있다고 생각한다. 따라서 3번도 2번으로 최적화 시킨 후, 소스파일에는 일일이 필요한 것만 포함시킨다. 이것은 간혹 너무도 귀찮지만(실제로 너무 귀찮다.), 무슨 헤더가 포함되어있는지 알기 쉽고 컴파일시간을 최소화시켜주기 때문이다.

5. 비슷한 부류는 하나의 헤더로 묶은 후 그 헤더를 포함시킨다.
이것도 당연한 얘기다. 일단 여러 헤더를 하나로 묶으면 더 간결해지고 알아보기 쉬운 형태로 바뀌게 된다. 특히 이런 것들은 미리 컴파일된 헤더(또는 라이브러리)로 만들어놓고 묶어서 헤더를 포함시키는 경우가 많다. 예를 들어 특정 외부 라이브러리를 대표하는 헤더파일들이 그 예라고 볼 수 있겠다. (ex) ogre.h, d3d9.h)
2010. 6. 25. 14:03

단기간에 많은 처리를 하는 선행조건을 가지는 함수의 분리


선행학습으로 인라인함수(Efficient C++)과 80-20의 법칙(Efficient C++, More Effective C++, 기타)을 읽어보시면 이해가 빠르십니다. 


어떤 함수가 처리되는데 특정한 조건이 필요한 경우를 보자. 그런데 그 함수가 단시간에 자주 호출된다면, 명령어 하나라도 최적화할 필요가 있다.

파란색 - CQuadTreeNode의 소스
연두색 - CQuadTree의 소스


예를 들어 쿼트트리내 오브젝트들을 가져오는 경우 해당 노드에 다음과 같은 함수를 호출할 수 있다.

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum );

위 함수는 노드내 오브젝트와 절두체의 충돌판정 후 절두체와 충돌한다면 해당 오브젝트의 렌더링 콜백함수를 호출하는 함수로써 다음과 같은 형식을 가지고 있다. 하지만 선행조건이 걸려있다. m_iObjectCount <= 0, 오브젝트의 개수가 0보다 같거나 작다면 더이상 진행하지 않고 함수를 끝낸다.

CQuadTreeNode
VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  if( m_iObjectCount <= 0 )
  {
      return;
  }

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
     if( pFrustum->IsAABBInFrustum(*(*it)->GetWorldBoundAABB()) )
       (*it)->AddedRenderEntryArrayCallBack();
  }
}


이 함수는 쿼드트리클래스의 CQuadTree::CheckInFrustumAndNotifyToNode함수에서 다음과 같은 형식으로 호출될 수 있다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}


렌더링 쿼드트리의 특성상 아주 작은 시간에도 매우 자주 호출됨에 따라 단 하나의 명령어도 간과해서는 안된다. 즉, CQuadTreeNode::CheckInFrustumAndNotifyToNode 함수의 함수호출 명령어조차도 최소화 시켜야하는데 이 얘기는 inline으로 선언해야한단 얘기다. 하지만 inline으로 하기에는 소스의 길이가 다소 길다.(싱글톤메소드라고 해서 예외사항은 있다.) 만약 위 소스는 절대 inline이 될 수 없다 가정하면,
오브젝트의 개수 부분만을 인라인화 해서 아예 이 함수가 호출되지 않게끔 방지할 순 있다
. 다음을 보자. 수정된 부분은 굵은글씨로 표시해두었다.
 
CQuadTreeNode
inline BOOL CQuadTreeNode::IsExistNode()
{
  return m_iObjectCount>0 ? TRUE:FALSE;
}

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  // if검사부분을 아예 없애버렸다.
  // 이 경우는 다소 위험한 방법이긴한데 릴리즈시 약간의 오버헤드를 없앨 수 있다.
  // 만약, CQuadTree::CheckInFrustumAndNotifyToNode함수에서만 호출된다면,
  // 아래 코드는 99%안전한 코드다.
  // 특정 위치의 한곳에서만 호출되는 것을 싱글톤 메소드라고 하는데 이 경우는 FORCEINLINE 매크로로
  // 강제 인라인화 할 수 있다.(VS에서는 지나치게 긴 메소드를 인라인화에서 제외시킨다.)
  assert( m_iObjectCount > 0 && "오브젝트가 하나도 없는데 호출됐네요." );

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
    if( (*it)->IsInFrustum( pFrustum ) )
       (*it)->AddedRenderEntryArrayCallBack();
  }


그리고 쿼드트리에서도 약간의 수정이 필요하다. 수정된 부분은 굵은글씨로 표시해두었다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       if( node->IsExistNode() )
         node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}



성능이 비약적으로 상승한다.

'C/C++ > 패턴 및 리팩토링' 카테고리의 다른 글

파일간 #include 설계에 대한 생각  (0) 2010.07.07
2010. 6. 15. 04:09

기본 클래스를 정의하지 않았습니다.


이 에러는 존재하지 않는(컴파일러의 입장에서) 클래스를 상속한 경우에 발생하는 오류메시지다.
여러가지 원인이 있는데 비교적 잦은 예를 들어보겠다.

1. 부모클래스가 들어있는 헤더파일을 include하지 않은 경우
해당 클래스가 있는 헤더파일을 포함시켜준다.

2. include는 하였지만 namespace이 달라 인식하지 못한 경우
부모클래스가 아래와 같이 어떤 namespace 내에 존재한다면..
namespace MySpace
{
    class Base
    {
    };
}

① namespace이름::class이름 .. 으로 처리한다.
class Derived : public MySpace::Base
{
};


② 또는 using namespace namespace이름; 을 써준다.
using namespace MySpace;

class Derived : public Base
{
};


3. 헤더파일을 include했으나 교차포함해서 인식하지 못한 경우
다음참조 : http://ekessy.tistory.com/20 (교차 포함 시 대량의 오류가 발생하는 경우)

2010. 6. 13. 09:56

교차 포함 시 대량의 오류가 발생하는 경우


error C2146: 구문 오류 : ';'이(가) ... 식별자 앞에 없습니다.
error C4430: 형식 지정자가 없습니다. int로 가정합니다. 참고: C++에서는 기본 int를 지원하지 않습니다.
error C4430: 형식 지정자가 없습니다. int로 가정합니다. 참고: C++에서는 기본 int를 지원하지 않습니다.

...

위와 같은 에러가 나는 이유는 클래스를 찾을 수 없는 경우에 발생합니다. 분명히 클래스가 있는데도 불구하고 이런 에러메시지가 발생하는 경우는.. 보통 교차포함을 한 경우에 이런 에러가 자주 발생합니다.
이게 무슨 얘기냐.. 다음을 보시죠..

b.h
#pragma once
#include "a.h"
class B
{
public:
 A a;
};

a.h
#pragma once
#include "b.h"
class A
{
public:
 B b;
};

양쪽의 클래스들은 서로를 필요로 하기 때문에 서로 #include을 해주었습니다.
언뜻 보기에는 문제가 없어보이죠. 실제로 자바에서는 에러가 나질 않습니다. 하지만 c++에서는 에러를 유발합니다. 무수히 많은 에러를 유발하죠.... 아주 많이....
 
하나하나 따라가보면 그 이유를 알 수 있습니다.
일단 b.cpp파일을 컴파일한다고 합시다. 그럼 b.cpp에는 최상단에 b.h가 include되어있을 겁니다. 그래서 컴파일러는 b.h를 포함시키죠. b.h를 가보니 상단에 a.h가 include되어있습니다. 그럼 그 헤더파일을 포함시키려 하겠죠. 그래서 a.h에 가보니.. 상단에 b.h가 include되어있습니다. 좀전에 b.cpp에서 b.h를 include했는데도 불구 한번더 include하려하지요.(아직 class B가 전부 읽혀진건 아닙니다.) 하지만 #pragma once구문 보이시죠? 이 구문때문에 헤더파일은 단 한번만 불려와지게 됩니다. 결국 a.h에 있는 #include "b.h"구문은 있으나 마나한 셈이 되죠. 그래서 class A의 B b부분에서 클래스 B는 이세상에 없는 클래스로 인식하고 형식이 없다는 에러메시지를 발생시키는 겁니다. 만약 class A에 포함된 에러 헤더파일 중 B를 필요로 하는 헤더파일이 있다면.. 에러메시지는 더욱더 많이 발생할 겁니다. 마찬가지로 a.cpp를 컴파일 하는경우 똑같은 에러가 나겠네요.

또한 서로 포함하지 않았는데도 불구, 이 에러메시지가 발생하는 경우가 있습니다.
그 경우는 서로는 포함하지 않았지만, 각 파일에 포함된 헤더파일들에 포함되어 간접적으로 서로를 포함한 경우가 해당됩니다.
b.h
#pragma once
#include "a.h"
class B
{
public:
 A a;
};

a.h
#pragma once
#include "c.h"
class A
{
public:
 B b;
};

c.h
#pragma once
#include "b.h"
class C
{
};


정말 간단하게 생각하면 #pragma once가 문제라고 생각하실 수 있습니다.
하지만 이걸 없애버리면 더 큰 문제가 발생합니다. class B가 두번 로드되는 경우죠. 심벌은 단 한개만 유효합니다. class B가 여러개 있을수는 없겠죠. 그렇다면, 단 한번만 불려와지고.. a.h에서도 인식할 수 있는 방법이 있느냐..? 물론 존재합니다. 여러가지 기법들이 존재하죠.



해결책

1. 가장 간단한 방법
헤더파일에서는 컴파일러에 단지 "암시"만 주고 포함시키지 않는 방법입니다.
b.h
#pragma once
class A; // class A가 존재한다고만 알려줍니다.
class B
{
public:
 A* a; // 포인터로 바꾸었습니다.
};

a.h
#pragma once
class B; // class B가 존재한다고만 알려줍니다.
class A
{
public:
 B* b;
};

※ 주의) 각각의 소스파일엔 포함시켜줘야합니다. 이렇게 하면 서로 include를 하지 않기 때문에 위와 같은 에러가 발생하지 않습니다. class A; 라고 하기만 하고 끝낸 부분이 눈에 띄는데, 이 부분은 실제로 클래스를 정의하진 않고 단지 앞으로 정의될 것이다, 나중에 링크과정때 찾아보라고 하는 일종의 암시입니다. 당장은 컴파일을 위해 때우는(?) 수단이라고 보시면 되겠죠. 그리고 A a에서 A* a로 포인터로 바뀐 부분을 알 수가 있는데, 일반 변수의 경우 크기를 알아야 하기 때문이죠. 따라서 일반 변수로 했다면 B를 컴파일 하는 도중 A의 크기를 구하려고 할 것이고, 에러를 유발하곤 합니다. 반면에 포인터 변수는 어떤 타입에도 상관없이 4바이트를 고수합니다.

한가지 주의하실 점은, 저렇게 하고 b.h에서 A의 멤버변수(함수)에 접근하는 인라인 함수같은 코드는 넣으시면 안됩니다. 컴파일 되는 당시로썬 A의 상세코드는 알길이 없기 때문이죠. 단 다음과 같은 경우는 가능합니다. 그 이유는 당장엔 A의 내용을 알필요가 없기 때문입니다. 소스파일에 넣으시기 바랍니다.
b.h
#pragma once
class A; // class A가 존재한다고만 알려줍니다.
class B
{
public:
 A* a; // 포인터로 바꾸었습니다.
 B() : a(NULL) {}
 ~B(){ if( a != NULL)delete a; } // 이 구문은 절대 안됩니다. 현재로썬 a의 크기를 알수 없기 때문입니다.
 A* GetAPtr() { return a; }
};




2. 좀더 나은 방법
b.h
#pragma once
#include "ABase.h"
#include "BBase.h"
class B : public BBase
{
public:
 B();
 virtual ~B();
 ABase* a;
};

a.h
#pragma once
#include "ABase.h"
#include "BBase.h"
class A : public ABase
{
public:
 A();
 virtual ~A();
 BBase* b;
};

b.cpp
#include "b.h"
#include "a.h"

B::B()
{
  a = new A();
}
B::~B()
{
  if( a!=NULL ) delete a;
}

BBase.h
class BBase
{
public:
  virtual ~BBase(){}
};

클래스를 상속으로 계층관계로 만들어버린 후, 부모클래스를 포함시키는 방법입니다. 정의는 부모클래스지만 당연히 할당은 자식클래스로 하게 되겠죠?^^; 이 방법은 실제로 자주 사용되고 있습니다. 유명한 패턴중 상태패턴에서도 이런 구조로 사용되죠. 그리고 컨테이너들도 저런 방식과 유사하게 설계되었을거라 생각합니다. 하지만, 무리하게 이 방식으로 할 필욘 없다고 생각합니다. 몇몇의 경우는 제외하고는 이 방식보단 처음방식이 더 나을수도 있겠습니다..^^



맺음말
실제로 이런 디자인은 말라고 합니다만.. 현재 라이브러리에서는 이런 디자인이 비일비재합니다. 가장 간단한 예로 MFC에서 부모와 차일드 윈도우 관계죠. 또는 게임 오브젝트 관리자와 게임 오브젝트들과 서로 포함시켜서 프로그래밍을 단순화 시키기도 합니다. 때로는 이런 디자인이 간편하고 가독성에도 일조한단 얘기죠.
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");
}