객체 지향의 원칙

개발 노트 2009. 1. 23. 15:46 posted by 무병장수권력자


객체 지향의 원칙

작성자 : 김문규
최초 작성일 : 2009. 1.28

1. OCP (Open Close Principle, 개방 폐쇄의 원칙)
 - 변하는 것과 변하지 말아야 하는 것의 구분에 대한 원칙
 - 확장에 대해서는 개방되고 변경에 대해서는 폐쇄되어야 한다.
 - 인터페이스를 활용하여 느슨한(Loosely Coupled) 설계를 한다.
 - 인터페이스 자체는 변하지 말아야 하고 이에 대한 구현을 추가, 변경, 삭제하여야 한다.

2. SRP (Single Reponsibility Principle, 단일 책임의 원칙)
 - 하나의 클래스의 단 하나의 책임만을 가지도록 한다.

3. ISP (Interface Segregation Principle, 인터페이스 분리의 원칙)
 - 최대한 작게 인터페이스를 분리하라.

4. DIP (Dependency Inversion Principle, 상관 관계 역전의 원칙)
 - 인터페이스와 그 구현의 연결을 코드에서 하지말고 프레임워크에서 수행하도록 한다.
 - 기존의 caller 함수가 callee 함수를 호출하는 단순한 관계에서 발전하여 callee에 caller의 hook method를 등록하고 필요한 시점에 caller가 이를 다시 호출하는 것을 말한다.
 - 좋은 사례로는 이벤트 드리븐(event driven), 콜백(call back)이 있다.

5. LSP (Liskov Substitution Principle, 리스코프 대체 원칙)
 - 클래스 간의 하위호환성을 보장하도록 설계/구현해야 한다.
 - 베이스 클래스의 인터페이스를 하위 클래스에서는 충실하게 구현해야 한다.
 - 따라서, 상속 구조에서 모든 서브 타입은 언제나 기반 타입으로 교체 가능해야 한다.

상속(Inheritance) 관계를 설명한 유명한 비유

IS-A

[1] 만약 두 객체가 똑같은 일을 한다면 둘을 한 클래스로 표현하고 이들을 구분할 수 있는 필드를 두면 된다.
[2] 똑같은 연산을 제공하지만, 이들을 약간씩 다르게 한다면 공통의 인터페이스를 만들고 둘이 이를 구현하도록 한다. (인터페이스 상속).
[3] 공통된 연산이 없다면 완전 별개인 2개의 클래스를 만들면 된다.
[4] 만약 두 객체가 하는 일에 추가적으로 무언가를 더한다면 구현 상속을 사용한다.

하지만 항상 옳은 것은 아니므로 그냥 참고만 하도록!


** 참고
마이크로소프트웨어 글 (zdnet 게재)
http://www.zdnet.co.kr/ArticleView.asp?artice_id=00000039134727
http://www.zdnet.co.kr/ArticleView.asp?artice_id=00000039135552
http://www.zdnet.co.kr/ArticleView.asp?artice_id=00000039139151
http://www.zdnet.co.kr/ArticleView.asp?artice_id=00000039137043
우야꼬의 Adobe RIA
http://wooyaggo.tistory.com/80
http://wooyaggo.tistory.com/81
http://wooyaggo.tistory.com/82





Domain Name을 IP 주소로 변환하기 (Domain Name Resolution)

작성자 : 김문규
최초 작성일 : 2009. 1.22

1. 들어가며
이번 과제에서 HTTP request의 Referer(레퍼러)를 체크해서 전송 객체 인증을 해야 했습니다. 물론 정해진 프로토콜로 전송 객체의 실제 주소를 암호화해서 보내주고 있었습니다. 그런데, referer 정보는 domain name을 그대로 포함한 채로 전달되는 경우가 많습니다.
그래서, domain name을 DNS 서버를 통해서 resolution 할 필요가 있었습니다.
그리고, 저는 2절에서 소개할 코드를 사용했습니다.

2. 아주 유용한 실전 예제 코드

/* gethostbyname.cpp : Defines the entry point for the console application.
*/
#include <stdio.h>
#ifdef WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib");
#elif UNIX
#include <netdb.h>   
#include <sys/socket.h>   
#include <arpa/inet.h>   
#include <netinet/in.h>
#endif
int getHostIP(char *domain)
{
 int ret = 0;
 struct hostent *pHost;
 struct in_addr *pAddr;

#ifdef WIN32
 WSADATA wsaData;
 ret = WSAStartup(WINSOCK_VERSION, &wsaData);
 if(ret != 0){
  printf("WSAStartup() failed, error code = %d\n", ret);
  return 300006;
 }
#endif

// 알파벳으로 시작하는 이름일 경우에는 domain name으로 간주합니다.
 if( ((domain[0] > 'A')&&(domain[0] < 'Z')) || ((domain[0] > 'a')&&(domain[0] < 'z')) ) { 
  pHost = gethostbyname(domain);
  if(pHost == NULL){
#ifdef WIN32
   ret = WSAGetLastError();
#endif
   printf("Error in gethostbyname(), error code = %d\n", ret);
   goto HANDLE_ERROR;
  }
  else{
   printf("Official name of the host is: '%s'\n", pHost->h_name);   
   while(*pHost->h_addr_list != NULL){
     pAddr = reinterpret_cast<in_addr *>(*(pHost->h_addr_list));
     printf("%s\n", inet_ntoa(*pAddr));
     pHost->h_addr_list++;
   }

  }
#ifdef WIN32 
  ret = WSACleanup();
  if(ret != 0){
   ret = WSAGetLastError();
   printf("WSACleanup() failed, error code = %d\n", ret);
   goto HANDLE_ERROR;
  }
#endif
  return 0;
 }
else if( (domain[0] > '0')&&(domain[0] < '9') ) { // 첫글자가 숫자일 경우에는 decimal notation으로 표현된 ip 주소라고 가정
  printf("%s\n", domain);
  return 0;
 } else goto HANDLE_ERROR;
HANDLE_ERROR:
#ifdef WIN32
 WSACleanup();
#endif
 return ret;
}
int main(int argc, char **argv)
{
 getHostIP("www.naver.com");
    return 0;
}

적색으로 표시한 것이 핵심이에요~
실제로는 gethostbyname 이라는 함수와 struct hostent,  struct in_addr를 조작해서 결과를 얻어내게 됩니다. 이에 대한 자세한 내용은 아래와 같습니다.

struct hostent {
        char    *h_name;        /* 호스트의 공식 이름 */
        char    **h_aliases;    /* 별칭 리스트 */
        int     h_addrtype;     /* 호스트 주소 타입 */
        int     h_length;       /* 주소의 길이 */
        char    **h_addr_list;  /* 주소 리스트 */
}
#define h_addr  h_addr_list[0]  /* 구 버전과의 호환을 위해 */

struct in_addr {
  union {
    struct {
      u_char s_b1,s_b2,s_b3,s_b4;
    } S_un_b;
    struct {
      u_short s_w1,s_w2;
    } S_un_w;
    u_long S_addr;
  } S_un;
}

#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);

3. 마치며
네트워크 관련 프로그래밍은 할 때 마다 헷갈립니다. 아무래도 동그라미 쳐 가면서 외워야 겠습니다. ^^


[UNIX/C/C++] 유닉스에서 디렉토리 다루기

개발 노트 2009. 1. 21. 18:32 posted by 무병장수권력자


유닉스에서 디렉토리 다루기

작성자 : 김문규
최초 작성일 : 2009. 1.21

1. 들어가며
서버 프로그래밍이든 클라이언트 프로그래밍이든 중요하게 다루어 지는 부분은 파일 시스템을 액세스해서 로컬 자원의 정보를 획득하는 것이 아닐까요?
대부분의 프로그래밍 책에서 파일을 열고 닫는 것을 많이 다룹니다. 그런데, 디렉토리에 대한 내용은 많이 보지 못한 것 같습니다.

그래서, 이번 포스트의 주제는 '유닉스에서 디렉토리 다루기' 입니다.

2. 디렉토리 다루기
특정 디렉토리부터 하위 디렉토리에 있는 파일 또는 디렉토리를 검색하는 알고리즘을 생각해 보겠습니다.
1) 현재 디렉토리에 존재하는 모든 파일 검색
2) 검색된 항목이 파일이면 다음
3) 검색된 항목이 디렉토리이면 1)로 가서 다시 검색

이를 구현시에는 다음과 같은 기능이 필요합니다.
1) 현재 디렉토리의 파일 리스팅
2) 리스팅된 파일이 일반 파일인지 디렉토리인지 구분하기
3) 재귀 함수 호출

이번 주제에 맞게 1), 2)에 대해서 간단한 예제와 함께 알아 보겠습니다.

1)디렉토리 리스팅 (Directory Listing)

디렉토리 리스팅을 위해서는 다음과 같은 구조체와 함수들을 사용하면 됩니다.

#include <dirent.h>

struct   dirent
{
    long                  d_ino;                                 // inode 넘버
    off_t                  d_off;                                 // offset
    unsigned short   d_reclen;                          // d_name 길이
    char                  d_name[NAME_MAX+1];     // 파일이름
}

DIR*  opendir(const  char*  name);                 // 열기
int   closedir(DIR*  dp);                                    // 닫기
struct  dirent*  readdir(DIR*  dp);                    // 읽기

간단한 예제 입니다.
#include <stdio.h>
#include <dirent.h>
int main (void)
{  
    DIR *pDir = NULL;
    dirent *pFile = NULL;
    pDir = opendir(".");
    if(!pDir) {
        printf("ERROR\n");
    }
    while( (pFile = readdir(pDir)) != NULL )
    {
        printf("%s\n", pFile->d_name);
    }
    closedir(pDir);
    return 0;


2) 파일 속성 알아내기

파일 속성을 알아내기 위해서는 다음과 같은 구조체와 함수들을 사용하면 됩니다.

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// 입력 변수만 조금 다를 뿐 기능은 같습니다.
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *file_name, struct stat *buf);

struct stat
{
    dev_t         st_dev;      /* device */
    ino_t         st_ino;      /* inode */
    mode_t        st_mode;     /* protection */
    nlink_t       st_nlink;    /* number of hard links */
    uid_t         st_uid;      /* user ID of owner */
    gid_t         st_gid;      /* group ID of owner */
    dev_t         st_rdev;     /* device type (if inode device) */
    off_t         st_size;     /* total size, in bytes */
    unsigned long st_blksize;  /* blocksize for filesystem I/O */
    unsigned long st_blocks;   /* number of blocks allocated */
    time_t        st_atime;    /* time of last access */
    time_t        st_mtime;    /* time of last modification */
    time_t        st_ctime;    /* time of last change */
};

모든 필드가 각각의 의미를 가지고 있지만 이번 포스트에서는 관심 있는 플래그는 st_mode 입니다. 이 값이 파일의 성격을 알려주는 플래그이거든요.

이 값은 다음의 매크로를 이용해서 확인 가능합니다.
S_ISLNK(m)  is it a symbolic link?
S_ISREG(m)  regular file?
S_ISDIR(m)  directory?
S_ISCHR(m)  character device?
S_ISBLK(m)  block device?
S_ISFIFO(m) fifo?
S_ISSOCK(m) socket?

예제 코드로 설명한 내용을 확인해 보겠습니다.
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
int main (void)
{  
    DIR *pDir = NULL;
    dirent *pFile = NULL;
    struct stat buf;
    pDir = opendir(".");
    if(!pDir) {
        printf("ERROR\n");
    }
    while( (pFile = readdir(pDir)) != NULL )
    {
        memset(&buf, 0, sizeof(struct stat));
        lstat(pFile->d_name, &buf);                    // buf에 stat 정보 저장
        if(S_ISDIR(buf.st_mode)) printf("[DIR]%s\n", pFile->d_name);
        else if(S_ISREG(buf.st_mode)) printf("[FILE]%s\n", pFile->d_name);
        else printf("[ELSE]%s\n", pFile->d_name);
    }
    closedir(pDir);
    return 0;
}  

3. 마치며
이 방법은 유닉스와 리눅스에서만 사용가능 합니다. 흠흠. 기왕이면 윈도우에서도 사용가능 했으면 좋았을 텐데요.
그럼 즐프하세요.











[C/C++] Template for generic programming

개발 노트 2009. 1. 20. 17:01 posted by 무병장수권력자


Template for generic programming

작성자 : 김문규
최초 작성일 : 2009. 1.20

1. Template 란?
generic programming을 위해 C++에서 제공하는 수단입니다. 이를 위해서는 이외에도 macro, overloading이 있습니다. 하지만, macro는 debugging이 어렵고 overloading은 귀찮기 때문에 가능하다면 template을 추천합니다.
** gereral programming이란, 코드의 재사용성을 높일 수 있도록 효과적으로 소프트웨어를 구현하는 것을 가리킵니다.

2. Function Template (함수 템플릿)
어떤 함수를 일반화한 것을 함수 템플릿이라 하고, 함수 템플릿으로 처리하기 곤란한 경우에 대해서만 따로 구현하는 것을 특수화라고 한다.
** 함수 -> 템플릿 -> 특수화

Template를 선언하는 방법은 아래와 같습니다.
template <typename T1, typename T2 ....>
T function_name(T1 a, T2 b ...)
{
    logic
}
typename 대신에 class를 사용하기도 합니다.

간단한 예를 들어 보겠습니다.
#include <iostream>
#include <cstring>
using namespace std ;
//max returns the maximum of the two elements
template <class T>
T max_(T a, T b)
{
    return a > b ? a : b ;
}
// Specialization of max for char*
template <>
char* max_(char* a, char* b)
{
    return strcmp(a, b) > 0 ? a : b ;
}
int main()
{
    cout << "max(10, 15) = " << max_(10, 15) << endl ;
    cout << "max('k', 's') = " << max_('k', 's') << endl ;
    cout << "max(10.1, 15.2) = " << max_(10.1, 15.2) << endl ;
    cout << "max(\"Aladdin\", \"Jasmine\") = " << max_("Aladdin", "Jasmine") << endl ;
    return 0 ;
}


main 함수에 앞선 3개의 max_는 template function을 그대로 호출하게 되고 마지막 max_는 specialized template function을 호출하게 됩니다.

3. Class Template (클래스 템플릿)
선언의 방법은 유사합니다. class 선언부 상단에 template <typename T> 처럼 일반화 시킬 입력 변수의 종류를 선언하고 기존에 int, double등 타입으로 표시하던 부분을 typename으로 바꾸어 주면 땡!

예를 들면

template <typename T>
class Data
{
 T data;
public:
 Data(T d)
 {
  data = d;
 }
 void SetData(T d)
 {
  data=d;
 }
 T GetData()
 {
  return data;
 }
};

int main()
{
 Data<int> d1(0);
 d1.SetData(10);
 Data<char> d2('a');

 cout<<d1.GetData()<<endl;
 cout<<d2.GetData()<<endl;
 return 0;
}


근데, C++의 경우에는 .hpp 파일과 .cpp 파일을 따로 사용하시는 경우가 더 많지요? 이 경우에는 다음과 같이 하시면 됩니다.
data.hpp

template<typename T>
class Data
{
 T data;
public:
 Data(T d);
 void SetData(T d);
 T GetData();
};

data.cpp

template<typename T>
Data<T>::Data(T d)
{
 data = d;
}

template<typename T>
void Data<T>::SetData(T d)
{
 data = d;
}

template<typename T>
T Data<T>::GetData()
{
 return data;
}


4. 마치면서
generic programming은 소프트웨어 개발을 하면서 많이 듣는 표준화, 공용화 측면에서 의미 있어 보입니다. 그래서, 능력과 시간이 된다면 당연히 템플릿을 기반으로 개발해야 하지 않을까 생각합니다. 그런데, 템플릿도 단점은 있습니다.
먼저 사용하기가 쉽지 않아 보입니다.
그리고, 실행 바이너리의 크기가 커집니다.
그래도, 좋다고요? 그럼 사용해 볼까요?

<참조 자료>
http://shinlucky.tistory.com/196
http://www.iis.sinica.edu.tw/~kathy/vcstl/templates.htm
http://kid1412.springnote.com/pages/1449032


[Unix] vi 컨닝 페이퍼

개발 노트 2009. 1. 19. 13:09 posted by 무병장수권력자


http://www.ibm.com/developerworks/kr/library/tutorial/l-vi/index.html
여기서 그냥 가져 왔음

Unix 개발자라면... 외우든가~ 아니면 책상에 붙여 놓든가~



[C/C++] 스마트 포인터

개발 노트 2009. 1. 16. 16:57 posted by 무병장수권력자


작성자 : 김문규
최초 작성일 : 2009. 1.16

1. 들어가며
C++에도 자동으로 메모리를 관리해 주는 기능이 있다면, 사용하시겠습니까?
물론 사람마다 다를 꺼에요. 자바 개발자의 경우에는 프로그래밍을 시작하면서 부터 가비지 콜렉션에 대해서 이해하고 믿는 반면에 C 개발자 들은 어찌도 자기 자신만 믿으려 하는 지... 남이 해주는 건 다 못 믿어서 직접 짜려고만 하지요.
호호...저만 그런가요?

C++에도 일부 메모리를 관리해 주는 기능이 있으며,
세계 각국의 개발자들이 만든 3rd party 라이브러리를 사용한다면 좀 더 강력한 메모리 자동 관리 기능을 사용할 수 있습니다.


결론에 다시 말하겠지만,
본인이 실수 없이 완벽한 코딩을 할 수 있고 시간과 노력이 아깝지 않다면 직접 구현하십시오.
이 기능은 때와 개인의 취향에 따라 선택되어야 할 것입니다.


2. 소유권 독점 방식 Smart Pointer, auto_ptr
C++ 표준입니다. 그런데, 사용이 곤란합니다.

일단 권장하는 사용법부터 보실까요?
#include <memory>
using namespace std;
class CTest
{
public:
    inline int getValue() { return m_ivalue; }
    inline void setValue(int _iValue) { m_ivalue = _iValue; }
protected:
    int m_ivalue;
};
int main(void)
{
    auto_ptr<CTest> pTest(new CTest());
    pTest->setValue(3);
    printf("return = %d\n", pTest->getValue());
    return 0;
}

객체를 생성하고서 이를 가리키는 포인터를 auto_ptr로 얻습니다. 이를 사용하고 나서 굳이 delete를 호출하지 않아도 적당한 때(?)가 되면 자동으로 해당 메모리 영역이 해제됩니다.

좋아 보입니다.
주의할 점은 클래스나 구조체 같은 것에 사용 가능하지만 배열에 사용할 수 없다는 것입니다.
그리고, auto_ptr은 소유권 독점 방식의 smart pointer이기 때문에 사용이 다소 직관적이지 않습니다. 아래의 예를 보세요.

#include <memory>
using namespace std;
class CTest
{
public:
    inline int getValue() { return m_ivalue; }
    inline void setValue(int _iValue) { m_ivalue = _iValue; }
protected:
    int m_ivalue;
};
void modifyTest(auto_ptr<CTest> _pTest)
{
    _pTest->setValue(5);
};
int main(void)
{
    auto_ptr<CTest> pTest(new CTest());
    pTest->setValue(3);
    modifyTest(pTest);
    printf("return = %d\n", pTest->getValue());
    return 0;
}

적색으로 표시한 부분이 추가 되었습니다. 일반적인 포인터의 개념으로 바라본다면 전혀 문제가 없어야 합니다. 그런데, 결과는 segmentation error 입니다. 이유는 modify_ctest 가 호출되는 시점에 pTest의 소유권이 main에서 modify_ctest로 옮겨 졌기 때문입니다. 그래서, 소유권을 박탈당한 main은 pTest를 사용할 수 없게 되는 겁니다. 이를 Trasfer of ownership 이라고 합니다.
솔직히 참 어의 없다고 생각이 됩니다. 이걸 쓸 바에는 그냥 일반 포인터를 다시 사용하겠습니다.

3. 소유권 공유 방식, boost::shared_ptr
관련된 라이브러리는 http://www.boost.org 에서 사용법 확인 및 다운로드가 가능합니다.
설치 하셨다고 가정하고 설명하겠습니다.

당연히 boost::shared_ptr은 2절의 소유권 독점 방식의 문제점을 해결했습니다. 우리가 알고 있는 공유 개념의 포인터이면서도 메모리는 자동으로 관리되는~ 사랑스런 러블리 포인터 입니다.
그래서 다음의 코드는 정상적으로 동작하게 됩니다.

#include <boost/smart_ptr.hpp>

using namespace std;

class CTest
{
public:
    inline int getValue() { return m_iValue; }
    inline void setValue(int _iValue) { m_iValue = _iValue; }
protected:
    int m_iValue;
};

void modifyTest(boost::shared_ptr<CTest> _pTest)
{
    _pTest->setValue(5);
};

int main(void)
{
    boost::shared_ptr<CTest> pTest(new CTest());
    pTest->setValue(3);

    modifyTest(pTest);
    printf("return = %d\n", pTest->getValue());

    return 0;
}

원리는 이렇습니다.
boost::shared_ptr은 reference counter를 두어서 해당 shared_ptr을 사용하는 객체의 개수를 관리합니다. 이 reference counter의 값이 1이상이라면 이 메모리를 해제하면 안된다는 뜻이겠지요. counter가 0보다 클 때는 해당 메모리 영역을 유지하다가 counter가 0이 되면 이를 해제하는 것이지요.

원리야 어찌됬던 간에 auto_ptr의 사용시에 약간 어색했던 부분은 이제 해결되었습니다. 하지만 boost:shared_ptr 역시 배열에는 사용할 수 없다는 점에서는 동일합니다. 배열에 적용하기 위해서는 boost::shared_array를 사용하셔야 합니다.

** 주의 사항이 있습니다.
- unnamed shared_ptr을 사용하지 말 것
- 순환 참조하지 말 것
- 하나의 객체에 대한 포인터를 두개 이상의 shared_ptr로 가리키지 말 것
자세한 내용은 다른 포스트에서 다루도록 하겠어요

4. 배열에 적용 가능한 smart pointer , boost::shared_array

shared_ptr과는 다르게 new [] 로 만들어지는 메모리 영역을 관리할 수 있는 smart pointer 입니다.
개인적으로는 가장 많이 사용하고 싶은 녀석입니다. 일단 예제를 보지요.

#include <boost/smart_ptr.hpp>
using namespace std;
class CTest
{
public:
    boost::shared_array<char> allocMemory();
};
boost::shared_array<char> CTest::allocMemory() {
    int iSizeResult = 0;
    iSizeResult = 50;
    boost::shared_array<char> pMemoryCreatedInside(new char[iSizeResult]);  // 메모리 할당
    sprintf(pMemoryCreatedInside.get(), "%s", "I am MK!");

    // 반환해야 하기 때문에 할당한 메모리를 해제 해서는 안됩니다.
    return pMemoryCreatedInside;
}
int main(void)
{
    boost::shared_ptr<CTest> pTest(new CTest);
    boost::shared_array<char> pMemoryUsedOutside = pTest->allocMemory();

    // allocMemory에서 할당한 값을 사용합니다.
    printf("%s\n", pMemoryUsedOutside.get());

    // 일반 포인터였다면 다소 어색하지만 여기서 pMemoryUsedOutside를 해제해야 합니다.
    // 까 먹기 쉽상이지요.

    return 0;
}

이처럼, 라이브러리 기반으로 개발하다 보면 메모리의 할당자와 메모리의 참조자, 그리고 메모리의 해제자가 달라지는 경우에 대한 경우가 많은데 이 경우 메모리 해제 문제를 어떻게 처리할 지 고민하게 됩니다. shared_array와 shared_ptr은 이 경우에 훌륭한 해법을 제공합니다.

각자 나름의 방법이 있겠지만, 저는 이중 포인터(**)를 활용해서 최대한 메모리 할당자와 메모리 해제자 만큼은 같아지게 하려고 합니다. 전 할 때마다 어려워요~ 머리도 아파지고 ^^

그런데, shared_array를 사용하면 이런 복잡한 포인터를 쓸 필요가 없어진다는 거죠~
그냥 논리적으로 위치해야 하는 곳에서 생성하고~ 그대로 두면 알아서 해제되기 때문이지요~

5. 마치면서
많은 C/C++ 개발자는 자바 개발자를 때때로 부러워합니다. 그 큰 이유는 gabage colletor가 아닐까 합니다. 인간은 모두 실수를 저지르기 때문이겠지요. 하지만, smart pointer는 이런 요구 사항을 어느 정도는 만족시켜 주고 있습니다.
하지만, smart pointer도 아무때나 마구 사용할 수는 없습니다. 잘못 사용할 경우에는 오히려 심각한 메모리 문제를 일으킬 수도 있습니다.
이제, 선택은 저와 여러분에게 달려 있네요~. 사용해 보세요. 편한지 아님...문제 덩어리인지~






base64 인코딩 ,디코딩의 원리

네트워크 2009. 1. 14. 09:59 posted by 무병장수권력자


작성자 : 김문규
최초 작성일 : 2009. 1.14

1. 써보셨나요?
사용해야 할 경우는 참 많겠지만.. 저는 이럴 때 써요.
 - 웹에서 GET 파라미터로 넘길 때
 - 한글 데이터가 이 기종 시스템간 이동하면서 변조되지 않도록 하기 위해서
   (요곤 조금 더 설명이 필요해요.. 다른 포스트에서 다루던가 아님 덧글 주세요.)

2. 그럼 원리는 아시나요?
base64 란 64진수라는 뜻입니다.
즉, base64 인코딩이란, 현재 시스템에서 사용하는 2진수 데이터를 64진수 데이터로 변경해서 표현하겠다는 것이죠.

아시다시피 64는 2^6 이지요. 데이터의 단위를 6bit으로 표현하겠다는 겁니다.
그런데, 일반적으로 컴퓨터는 byte라고 하는 8bit를 기준으로 데이터를 표현하는 것이 일반적입니다.
결론적으로 약간 수학적으로 정리하자면, 8bit으로 표현하는 데이터를 6bit으로 표현하겠다는 것이지요.

그럼 이제 조금만 더 설명해보겠습니다.
아래의 표를 참조하세요.



step1) 먼저 예를 들면 "abc"는 8bit 문자로 구성된 byte 문자열 입력입니다.
step2) 그럼 이 값은 각 문자를 의미하는 8 bit 이진값으로 표현이 됩니다.
step3) 이를 6bit 단위로 다시 구분해 봅니다.
step4) 이를 base64 문자셋으로 변환합니다.
          여기서 base64 문자셋이란 a-z, A-Z, 0-9, +, / 로 이루어진 문자셋입니다.
          A ~ Z : 0 ~ 25
          a ~ z : 26 ~ 51
          0 ~ 9 : 52 ~ 61
          +       : 62
          /       : 63
step5) 만일 딱 떨어지지 않으면 어쩌죠?
BASE64 인코딩은 24bit 단위로 항상 수행합니다. (24는 8과 6의 최소공배수입니다.)
그래서, 남는 비트가 생기면 그 뒤에 6bit character를 0으로 padding 합니다. (예제의 Q로 인코딩 하는..)
그리고나서도 남는 6bit character는 = 으로 치환합니다.




3) 그럼 코드로 만들면 어떻게 될까요?
C로 만든 코드를 구해서 포스팅 했습니다. 확인하세요~
http://www.iamcorean.net/125


'네트워크' 카테고리의 다른 글

HTTP (Hyper-Text Transfer Protocol) 개요  (0) 2008.10.11
LDAP (Lightweight Directory Access Protocol)  (0) 2008.07.16
IPv6  (0) 2008.07.07
SNMP (Simple Netwok Management Protocol)  (0) 2008.07.07


[C, C++] LDD, List Dynamic Dependencies

개발 노트 2009. 1. 13. 15:13 posted by 무병장수권력자


작성자 : 김문규
최초 작성일 : 2009. 1.13

라이브러리 작업을 하다보면 동적으로 참조되는 것들이 무엇이 있는지 알아야 할 때가 많습니다.
특히 혼자만 쓰는 게 아니라 배포되어야 한다면 더욱더 그러하겠지요.

Windows는 툴이 많다고 하더군요.
하지만 개인적으로는 dependency walker가 좋아요~

Unix....
언제나 어렵습니다. 그래서 잠시 구글링을 했습니다. 근데 LDD라는 것이 있더군요. 우왓 짱!

아래 소스를 빌드하시고 첫번째 변수로 해당 파일 넣으시면 되네요.

ldd [파일명]

작업시 유용하게 사용하세요~ AIX에서 테스트 하였습니다. 다른 UNIX에서는?

/*
 * %W% revision of %E%  %U%
 * This is an unpublished work copyright (c) 1993 HELIOS Software GmbH
 * 30827 Garbsen, Germany
 */
/*
 * ldd.c - List dynamic dependencies. Performs a similar function as the
 *    SunOS or System V.4 ldd command. This one works by loading the
 *    target program through load() and then printing all loaded
 *    modules as returned by loadquery().
 *
 * To compile:
 *  xlc -D_ALL_SOURCE -o ldd -O ldd.c -bnoso -bI:/usr/lib/syscalls.exp
 */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ldr.h>
void errxit(char *);
int main(int argc, char **argv)
{
 struct ld_info *lp;
 char *buf;
 int size = 4*1024;
 int i;
 
 if (argc != 2) {
  fprintf(stderr, "Usage: %s program\n", argv[0]);
  exit(1);
 }
 if (load(argv[1], 0, NULL) == NULL) {
  char *buffer[1024];
  if (errno != ENOEXEC)
   errxit(argv[1]);
  buffer[0] = "execerror";
  buffer[1] = argv[1];
  loadquery(L_GETMESSAGES, &buffer[2], sizeof(buffer)-8);
  execvp("/usr/sbin/execerror", buffer);
  errxit("execerror");
 }
 if ((buf = malloc(size)) == NULL)
  errxit("malloc");
 while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) {
  free(buf);
  size += 4*1024;
  if ((buf = malloc(size)) == NULL)
   errxit("malloc");
 }
 if (i == -1)
  errxit("loadquery");
 lp = (struct ld_info *)buf;
 while (lp) {
  /*
   * Skip over ourselves. The main part is always the first
   * in the list of loaded modules.
   */
  if (lp != (struct ld_info *)buf) {
   char *fileName = lp->ldinfo_filename;
   char *memberName = fileName + strlen(fileName) + 1;
   printf("%s", fileName);
   if (*memberName)
    printf("(%s)", memberName);
   printf("\n");
  }
  if (lp->ldinfo_next == 0)
   lp = NULL;
  else
   lp = (struct ld_info *)((char *)lp + lp->ldinfo_next);
 }
 free(buf);
 return 0;
}
void errxit(char *s)
{
 perror(s);
 exit(1);
}


[C/C++] C++에서 사용하는 casting 연산자

개발 노트 2009. 1. 5. 17:05 posted by 무병장수권력자


작성자 : 김문규
최초 작성일 : 2009. 1. 6

1. 문제 상황
C 컴파일러는 내맘대로 casting이 가능합니다.
혹자는 장점이라고 합니다만...
한편으로는 개발자의 실수를 그대로 용납한기 때문에 실은 아주 무책임한 컴파일러가 아닐수 없는 것이지요.

1: void main()
2: {
3:     char *str="korea";
4:     int *pi;
5:     char *pc;
6:     pi=(int *)str;
7:     pc=(char *)*pi;
8:     printf("%s\n",pc);
9:     getchar();
10:}

위의 코드에서 문제점을 찾거나 동작을 예측하실 수 있으신가요? 오~ 대단하시네요.
제가 돌려보니 죽습니다. 이유는 무분별한 포인터 캐스팅 때문입니다. line 7에서 문제가 일어납니다.

이유는....
line 6 : pi 는 "korea"를 4바이트를 정수를 가리키는 포인터가 되버립니다.
line 7 : 여기서 실수를 저질렀어요. 정수 값을 포인터로 casting 해버립니다. 결과는 pc는 0x65726f6b 번지를 가리키는 char 포인터가 되버립니다.

그래서, 운좋으면 쓰레기값을 반환하지만 대개의 경우에는 이 프로그램은 죽습니다.

이런 문제는 물론 제가 포인터 casting을 잘못한 문제가 있지요. ^^
하지만, 이를 컴파일러가 귀뜸만 해주었다면....하는 아쉬움이 남습니다.

C++ 컴파일러는 이보다 좀 더 명시적인 캐스팅 방법을 제공하며 이번 포스트에서는 이 내용을 다루어 볼까 합니다.

2. C++ casting
C++에서 추가된 casting 방식의 장점은 개발자가 casting의 목적을 명확하게 명시함으로써 개발자의 의도를 컴파일러가 알게하고, 그 결과 컴파일러는 개발자의 의도와 다른 casting 실수를 개발자에게 알려주는 것입니다

다음과 같은 casting이 존재합니다. 2개의 포스트로 나누어서 간단하게 내용을 파악해 보도록 하겠습니다.

1) static_cast
상속 관계에 있는 포인터 간 변환만 허용하는 casting 연산입니다.

int i; double d;
i = static_cast<int>(d)

이렇게 사용하는 겁니다.

상속 관계에 있는 포인터 간 변환만 허용한다는 점이 기억해야할 점입니다.
char *str="korea";
int *pi;
pi=static_cast<int *>(str);
이거~안됩니다.

2) dynamic_cast
위험 소지가 있는 down casting에 대해서 런타임 시에 casting 에러를 발생시켜주는 연산자 입니다.

downcasting의 경우, 자식을 가리키는 포인터를 부모형의 포인터로 가리키려고 하는 경우에 없는 멤버 또는 기능이 존재할 수 있기 때문에 문제가 될 수 있습니다.
dynamic_cast 연산자는 이처럼 문제가 있는 경우, 런타임 시에 실제 참조하고 있는 객체의 타입을 조사해서 위험한 downcasting이라고 판단되면 NULL 값을 반환하게 됩니다.

다만, 한가지 기억할 사항은 단지 상속 관계가 중요한 것이 아니라 virtual 함수가 존재해야 한다는 것입니다. 그렇지 않을 경우에는 상속관계가 있다하더라도 컴파일 시 에러를 발생합니다.

#include <stdio.h>
class CParent
{
public:
 int age;
 virtual void sayWhoIam() { printf("Parent\n"); };
};
class CChild : public CParent
{
public:
 virtual void sayWhoIam() { printf("Child\n"); };
};
void
main()
{
 CParent *parent = new CParent();
 CParent *p_parent1;
 CChild *child = new CChild();
 CChild *p_child1, *p_child2;
 // up casting
 p_parent1 = dynamic_cast<CParent*>(child);
 printf("address of p_parent1 = %x\n", p_parent1);
// child가 upcasting 된 것을 다시 downcasting : safe
 p_child1 = dynamic_cast<CChild*>(p_parent1);   
 printf("address of p_child1 = %x\n", p_child1);
 // unsafe down casting
 p_child2 = dynamic_cast<CChild*>(parent);
 printf("address of p_child2 = %x\n", p_child2);
}

결과값은 아래와 같습니다.


3) const_cast
const로 선언된 변수의 상수성을 없애고자 할 때 사용하는 연산자 입니다.
const 변수의 값을 바꾸고 싶을 때, 잠시 상수성을 없애고서 해당 값을 변경할 수 있도록 하지요.
상수성만을 없애는 것이 그 기능이기 때문에 int->double 또는 그 반대와 같이 당연히 가능한 형변환 조차도 const_cast를 사용한 경우에는 불가능하다는 사실을 기억해 두세요.

#include <stdio.h>
void main()
{
    char str[]="string";
    const char *c1=str;
    char *c2;
    c2=const_cast<char *>(c1);
    c2[0]='a';
    printf("%s\n",c2);
}


적색으로 표시한 7번째 라인은 물론 C에서 처럼
c2 = (char*)c1
으로 사용할 수 있습니다.
하지만 앞선 경우와 마찬가지로 개발자의 의도를 컴파일러에 분명하게 밝힌다는 것이 그 의미를 가집니다.

4) reinterpret_cast
다시 등장한 제멋대로 casting을 위한 연산자 입니다.
기본 타입 변환을 제외한 모든 변환이 가능합니다. 그렇기 때문에 컴파일러는 해당 연산자에 대해서는 어떠한 에러 또는 주의도 발생시키지 않습니다.
(기본 타입 변환은 static_cast를 사용해야 합니다.)

int *pi;
pi = reinterpret_cast<int*>(0x12345678);

3. 맺음말
C/C++ 프로그래밍을 하다보면, 순간의 편리함을 위해서 무리한 casting을 사용하는 경우가 적지 않습니다. 그 당시에는 그럴만한 이유가 있겠지만 개발이 종료되고 한참의 시간이 흐르면 본인조차도 그렇게 코딩한 이유를 모를때가 많지요.
그렇기에 casting과 같이 모호한 데이터 변환의 경우에는 그 나름의 이유를 명확하게 명시하는 것이 중요할 것입니다. 이런 측면에서 C++의 추가된 casting 연산자의 목적을 이해하는 것이 좋겠습니다.
감사합니다.

4. 참조 자료
http://www.winapi.co.kr/clec/cpp3/33-2-1.htm
http://www.winapi.co.kr/clec/cpp3/33-2-2.htm
http://www.winapi.co.kr/clec/cpp3/33-2-3.htm
http://www.winapi.co.kr/clec/cpp3/33-2-4.htm
http://www.winapi.co.kr/clec/cpp3/33-2-5.htm