요즘은 ATL에서 CString을 지원하면서 std::string을 사용할 일이 줄어들긴 했지만, 여전히 유용한 클래스임에는 틀림없다.

가끔 MFC등 윈도용으로 개발된 클래스를 포팅해야 할 때 제일 문제가 되는 것 중에 하나가 CString이기도 한데 -_-
여튼. std::string에서 아쉬운 부분중에 하나인 ReplaceAll 함수를 간단히 구현해 보자.

   

#include <string>

 

typedef std::basic_string<TCHAR> _tstring;

 

_tstring replace_all( const _tstring& source, const _tstring& pattern, const _tstring& replace )

{

_tstring result = source;

_tstring::size_type pos = 0;

_tstring::size_type offset = 0;

_tstring::size_type pattern_len = pattern.size();

_tstring::size_type replace_len = replace.size();

 

while ( ( pos = result.find( pattern, offset ) ) != _tstring::npos )

{

result.replace( result.begin() + pos, result.begin() + pos + pattern_len, replace );

offset = pos + replace_len;

}

return result;

}

 

출처 : http://blog.daum.net/studiocoma/6800925

'C/C++언어 > STL' 카테고리의 다른 글

STL 컨테이너(map, multimap)  (0) 2010.07.04
STL 숙제.. 렌터카 2번째..  (0) 2008.05.02
STL 강좌입니다.  (0) 2007.09.06
"C++ STL 실전 프로그래밍 예제 소스"중 일부  (0) 2007.03.20

 map과 multimap은 '연관 컨테이너'입니다. 모든 연관 컨테이너(set, multiset, map, multimap)는 '노드 기반 컨테이너'입니다. 또 모든 연관 컨테이너는 균형 2진 트리로 구현됩니다. 균형 2진 트리는 보통 레드-블랙 트리를 사용합니다. map은 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 여기서 key는 유니크합니다. multiset도 map과 같이 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 단, multiset은 map과 달리 중복된 key가 존재할 수 있습니다.

 

map의 주요 개념을 그림으로 표현하면

map_개념.png 

set과 map은 데이터의 key가 유니크합니다.

 

multiset의 주요 개념을 그림으로 표현하면

multimap_개념.png 

multiset과 multimap은 같은 key값의 데이터를 컨테이너에 저장할 수 있습니다.

 

map, multimap의 주요 특징과 함수

 map도 set처럼 데이터를 추가하는 push_? 류의 함수를 가지고 있지 않습니다. 삽입(insert)한다는 의미에서 insert() 함수를 가지고 있습니다.

map의 특징은 key와 value의 쌍으로 데이터를 저장합니다. 그리고 key값을 이용하여 빠르게 value값을 찾을 수 있습니다. 연관 컨테이너에서 유일하게 [] 연산자 중복을 제공합니다.

 

기본적인 map 예제

#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}
  1. 100
    50
    80
    100
    100

map은 key와 value의 쌍으로 구성된 데이터들의 집합입니다. []연산자의 인덱스는 key이며 인덱스가 가리키는 메모리 값이 value입니다.

주의할 점은 m[5] = 100 연산에서 5라는 key가 없으면 노드 '추가'가되며 5라는 key가 있으면 '갱신'이 됩니다. 아래에서 공부합니다.

그림으로 설명하면..

map_간단_코드.png

map의 트리 그림입니다.

map_데이터_추가.png 

map의 key값 5로 value를 접근하는 그림입니다.

map_데이터_접근.png 

 

 map은 key와 value의 쌍을 pair 객체로 저장합니다. STL의 쌍을 이루는 모든 요소는 pair 객체를 사용합니다.

위 예제는 insert()함수를 사용하여 아래 예제로 바꿀 수 있습니다.

#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    pair<int,int> pr( 4, 100);
    m.insert( pr );

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}

  1. 100
    50
    80
    100
    100

 map은 key, value를 pair 객체로 각각 first와 second에 저장합니다.

그림으로 표현하면

map_pair_객체.png 

 

 

반복자를 사용한 모든 key, value 출력 예제

#include <iostream>
#include <map>
using namespace std;

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<int, int>::iterator iter;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<(*iter).first <<"]: " << (*iter).second << endl;

    cout << "==============" << endl;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<iter->first <<"]: " << iter->second << endl;
}
  1. m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80
    ==============
    m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80

 map도 양방향 반복자를 제공합니다.

그림으로 간단하게..

 map_반복자.png

 

 

연관 컨테이너는 모두 동일한 함수군을 가지며 동작 방식도 같습니다.

map의 검색 함수들입니다.

#include <iostream>
#include <map>
using namespace std;

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<int, int >::iterator iter;

    iter = m.find( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.lower_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.upper_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    pair< map<int, int>::iterator, map<int, int>::iterator> iter_pair;
    iter_pair = m.equal_range(5);

    if( (iter_pair.first) == m.end() && (iter_pair.second == m.end()) )
        cout << "없음!" << endl;
    else
    {
        cout << "m["<< iter_pair.first->first <<"]: " << iter_pair.first->second <<" ~ ";
        cout << "m["<< iter_pair.second->first <<"]: " << iter_pair.second->second <<endl;
    }
}
  1. m[5]: 100
    m[5]: 100
    m[7]: 80
    m[5]: 100 ~ m[7]: 80

 동작은 set에서 설명했으므로 패스~!

그림으로 설명하면...

map_검색_함수.png 

 

 

 

마지막으로 multimap 예제입니다.

map과 다른 점은 [] 연산자가 없으며 key 값이 중복될 수 있습니다.

#include <iostream>
#include <map>
using namespace std;

void main( )
{
    multimap<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    m.insert( pair<int,int>( 4, 100) );
    m.insert( pair<int,int>( 7, 200) );
    m.insert( pair<int,int>( 7, 300) );

    pair<multimap<int,int>::iterator, multimap<int, int>::iterator > iter_pair;
    iter_pair = m.equal_range( 7 );
    multimap<int,int>::iterator iter;
    for( iter = iter_pair.first ; iter != iter_pair.second ; iter++)
        cout << "m["<< iter->first <<"]: " << iter->second <<' ';
    cout << endl;

}
  1. m[7]: 80 m[7]: 200 m[7]: 300

 설명은 multiset과 같습니다.

아래는 위 예제의 그림입니다.

multimap_equal_range(1).png  

 

 

 

여기까지 STL의 컨테이너를 마무리합니다.

C++ 표준에 추가된 해쉬 컨테이너는 다음에 공부합니다. 

출처 : http://coolprogramming.springnote.com/pages/4826369

 

최근엔 hesh_map 등이 더 인기가 있는거 같은..

쓰기에는 map 과 같은 인터페이스에 using namespase std 가 아닌 stdext 를 쓴다

자세한 레퍼렌스는 http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(HASH_MAP);k(DevLang-%22C%2B%2B%22);k(TargetOS-WINDOWS)&rd=true 를 참고하자

지금까지는 하나의 클래스에 대해 이야기를 했습니다. 지금부터는 하나 이상의 클래스를 이야기할까 합니다. 즉, 클래스 간의 관계에 대한 이야기입니다.

 

클래스의 관계는 여러 가지가 있지만 재사용 측면에서 크게 두 가지 관계가 존재합니다.( 우리는 복잡한 것을 생각하지 않으므로 간단하게 접근합니다.)

  • 첫째, '-이다'의 관계( is_a 관계)

    • 상속(inheritance)
  • 둘째, '-가지다'의 관계( has_a 관계)

    • 포함(composition)

 

첫째, is_a 관계는 Person 클래스와 Student 클래스의 관계입니다.

  • Student(학생)은 Person(사람) 이다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

상속Person.png 

이때 Person 클래스를 부모(Parent) 클래스라 하고 Student, Professor 클래스를 자식(child) 클래스라 합니다.

 

 

둘째, has_a 관계는 Car 클래스와 Radio 클래스의 관계입니다.(이것은 다음에 진행합니다. --;)

  • Car(자동차)는 Radio(라디오)를 가진다. 관계이기 때문에..
  • 그림으로는 아래와 같이 표현할 수 있습니다.

포함car.png 

 

 1, 상속

상속은 부모 클래스의 모든 내용(속성과 행동)을 자식 클래스가 물려 받는 것입니다.

위 그림에서 Person의 속성(멤버 변수)와 행동(멤버 함수)을 Student가 물려 받습니다. 그리고 자신만의 상태와 행동을 추가하여 내용을 확장합니다.

그래서 상속을 이용하면 코드를 재상용하여 효율적으로 클래스를 생성할 수 있습니다.

 

Student 클래스를 만들기 위한 간단한 예제코드를 볼까요?

첫째, 상속을 사용하지 않는 Student 클래스 정의

class Student
{
    char name[20];
    int age;
    int grade;
public:
    void Eat( ) {   }
    void Study( ) { }
};

void main( )
{
    Student std1;
}

 학생은 사람이므로 사람으로서 필요한 모든 내용(속성과 행동)을 Student 클래스가 정의하고 있습니다. 이렇게 되면 Professor 클래스를 정의할 때도 사람에 필요한 모든 내용을 정의해야 합니다. 그러면 코드 중복이 발생하여 여러 가지 불이익을 받게 됩니다.

 

Student 객체의 그림입니다.

상속없는_student_객체.png 

 

 

둘째, 상속을 사용한 Student 클래스 정의

class Person
{
    char name[20];
    int age;
public:
    void Eat( ) {   }
};
class Student : public Person
{
    int grade;
public:
    void Study( ) { }
};

void main( )
{

    Person person1;

 

    Student student1;

}

여기서 Person 클래스가 이미 존재한다고 가정한다면 Student 클래스를 정의하는 것은 위쪽 예제보다 훨씬 간단합니다. 속성 grade와 행동 Sudent()만 정의할 뿐이니까요. 그리고 부가적으로 따라오는 이득도 만만치 않습니다. 문법적으로 상속은 " : public Person"와 같이 합니다.(빨간색 부분)

 

Person 객체와 Student 객체의 그림입니다.

당연한 이야기지만 Student 객체는 위쪽 상속을 사용하지 않는 객체의 속성, 행동과 같은 객체가 만들어 집니다. 상속을 사용해서 Student 클래스를 정의한 것뿐이니까요.

상속없는_student_객체(2).png 

 

 

2, 함수 재정의(function overriding)

 함수 재정의란 부모 클래스에 정의된 멤버 함수를 자식 클래스에서 다시 정의하는 것을 말합니다.

 

예로 아래와 같은 Person 클래스가 있다고 가정합니다.

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
void main( )
{
    Person person1("김영수", 20);

    person1.Print( );
}
  1. name : 김영수, age : 20

 person1의 Print() 멤버 함수는 이름과 나이를 출력합니다.

 

이때 학생을 정의하기 위한 Student 클래스가 필요합니다.

이미 Person 클래스가 존재하므로 Student 클래스는 Person을 상속받아 만들기로 결정합니다.

아래와 같이..

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20

 그리고 학생 객체 student1을 만들어 학생의 정보를 출력(Print() 호출)하지만 Print() 함수는 grade의 정보를 출력하지 못합니다.

Print()함수는 Person의 메소드로 grade의 어떠한 내용도 알지 못하기 때문입니다.

한마디로 Person 클래스보다 Student 클래스가 더 구체화된 클래스이므로 Print() 함수의 기능도 Student에 맞게 더 구체화 시켜야 합니다.

그래서 부모 클래스(Person)의 함수(Print())를 자식 클래스(Student)에서 재정의하는 것입니다.

아래 예제와 같이...

#include <iostream>
using namespace std;

class Person
{
    char name[20];
    int age;
public:
    Person(const char* n, int a)
    {
        strcpy(name, n);
        age = a;
    }
    void Eat( ) {   }
    void Print( )
    {
        cout << "name : " << name <<", " <<"age : " << age << endl;
    }
};
class Student : public Person
{
    int grade;
public:
    Student(const char* n, int a, int g):Person(n,a), grade(g)     {
    }
    void Study( ) { }
    void Print( ) // 함수 재정의
    {
        Person::Print(); // 이름과 나이를 출력하고
        cout << "grade : " << grade << endl; // 학년도 출력합니다.
    }
};
void main( )
{
    Student student1("김학생", 20, 1);

    student1.Print( );
}
  1. name : 김학생, age : 20
    grade : 1

 Student 클래스에서 Print()함수를 재정의하여 이름과 나이도 출력하고(Person::Print()) 학년도 출력합니다. 재정의 함수에서 부모의 함수를 호출하려면 접근 연산자(::)를 사용해야 합니다. ( Person::Print() <=  이렇게... )

아니면 재귀함수 호출이 되겠죠?

 
출처 : http://coolprogramming.springnote.com/pages/3422007

[MFC] 디렉토리 선택 가능한 대화상자(SHBrowseForFolder)


디렉토리만 선택해야하는 경우 아래와 같은 박스가 필요함. 아래 코드에서 약간은 수정하여 사용할 수 있습니다.
플래그 설정으로 디렉토리, 파일 선택을 할 수 있습니다. (파일도 가능함)


[Source Code]
        :
static int CALLBACK BrowseCallbakProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM pData)

{

switch(uMsg)

{

case BFFM_VALIDATEFAILED:

CString strText;

strText.Format("%s", reinterpret_cast<LPTSTR>(lParam));

AfxMessageBox(strText);

break;

}

return 0;

}

 

void CNet_ProgDlg::OnButton6() // 여기는 클레스, 함수 이름이 바꾸어야 합니다.

{

    // TODO: Add your control notification handler code here

   
    ITEMIDLIST *pildBrowse;

char pszPathname[MAX_PATH];

      

LPITEMIDLIST pidl = NULL;

BROWSEINFO bInfo;

ZeroMemory( &bInfo, sizeof(BROWSEINFO) );

      

SHPathToPidl( CSIDL_DESKTOP, &pidl ); // 루트 디렉토리 설정 부분.

bInfo.hwndOwner = GetSafeHwnd();

bInfo.pidlRoot = pidl;

bInfo.pszDisplayName = pszPathname;

bInfo.lpszTitle = "Please Select Directory....";

bInfo.lpfn = BrowseCallbakProc;              
    // 콜백함수가 필요없다면 NULL이나 지움. 위의 static int 함수 필요 없어짐

bInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE ;
    // 디렉토리만 설정가능하도록 하였습니다... 플래그 설정은 맨 아래 참조하세요, 또는 MSDN

 

pildBrowse = ::SHBrowseForFolder(&bInfo);

      

if( pildBrowse != NULL )

{

SHGetPathFromIDList(pildBrowse, pszPathname);

        m_strDownFolder = (LPCTSTR)pszPathname; // 선택한 폴더 또는 파일… 이 부분을 목적에 맞게 수정.

        UpdateData(FALSE);                              

}

 

}

 

HRESULT CNet_ProgDlg::SHPathToPidl( LPCTSTR szPath, LPITEMIDLIST* ppidl )  // 여기는 클레스 이름 바꾸어야 함.

{

LPSHELLFOLDER pShellFolder = NULL;

OLECHAR wszPath[MAX_PATH] = {0};

ULONG nCharsParsed = 0;

      

HRESULT hr = SHGetDesktopFolder( &pShellFolder );

      

if( FAILED(hr) ) return FALSE;

      

MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, szPath, -1, wszPath, MAX_PATH );

hr = pShellFolder->ParseDisplayName( NULL, NULL, wszPath, &nCharsParsed, ppidl, NULL );

pShellFolder->Release();

      

return hr;

}

        : 

 

 

/*

// Browsing for directory.

#define BIF_RETURNONLYFSDIRS 0x0001 // For finding a folder to start document searching

#define BIF_DONTGOBELOWDOMAIN 0x0002 // For starting the Find Computer

#define BIF_STATUSTEXT 0x0004

#define BIF_RETURNFSANCESTORS 0x0008

#define BIF_EDITBOX 0x0010

#define BIF_VALIDATE 0x0020 // insist on valid result (or CANCEL)

 

#define BIF_BROWSEFORCOMPUTER 0x1000 // Browsing for Computers.

#define BIF_BROWSEFORPRINTER 0x2000 // Browsing for Printers

#define BIF_BROWSEINCLUDEFILES 0x4000 // Browsing for Everything

*/

-bInfo.ulFlags 플래그

 

BIF_BROWSEFORCOMPUTER : 네트워크의 컴퓨터만 선택가능

BIF_BROWSEFORPRINTER : 프린터만 선택가능

BIF_BROWSEINCLUDEFILES : 파일도 표시

BIF_DONTGOBELOWDOMAIN : 네트워크의 컴퓨터를 표시하지 않는다

BIF_EDITBOX : 에디트 박스를 표시한다

BIF_RETURNFSANCESTORS : 네트워크의 컴퓨터만 선택가능

BIF_RETURNONLYFSDIRS : 폴더만 선택가능

BIF_STATUSTEXT : 스테이터스 텍스트를 표시한다

BIF_VALIDATE : 부정 입력시에, BFFM_VALIDATEFAILED 이벤트

 

 

//Must be static funtion.
static int CALLBACK BrowseForFolder_CallbackProc(HWND m_hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
 if(uMsg == BFFM_INITIALIZED)
  SendMessage(m_hWnd, BFFM_SETSELECTION, (WPARAM)TRUE, (LPARAM)lpData);

 return 0;
}

위 함수는 꼭 static 함수여야 한다. 그리고 아래에서 함수포인터를 넣어준다.
아래 코드는 폴더 선택 다이얼로그를 띄워야 하는 곳에 넣어주면 되겠지.

 BROWSEINFO bi;
 ZeroMemory(&bi, sizeof(BROWSEINFO));
 bi.hwndOwner =  this->GetSafeHwnd();
 bi.pidlRoot = NULL;
 bi.pszDisplayName = NULL;
 bi.lpszTitle = NULL;
 bi.ulFlags = BIF_RETURNONLYFSDIRS;
 bi.lpfn = BrowseForFolder_CallbackProc;
 bi.lParam = (LPARAM)(LPCTSTR)m_strContentsDownloadFolder; //초기값 설정.

 LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
 TCHAR szPath[MAX_PATH] = {0,};  

 if( pidl )
 {
  SHGetPathFromIDList( pidl, szPath );
  m_strContentsDownloadFolder = szPath;
  GetDlgItem( IDC_DOWNLOAD_ADDRESS )->SetWindowText( m_strContentsDownloadFolder );
 }

위와 같이 하면 폴더를 선택할 수 있는 다이얼로그가 뜬다.

 

 

보통 많은 프로그램들을 보면 버튼 눌렀을때 폴더 선택할수 있게 다이얼로그를 띄워준다.

이것을 어케 하는지 알아본다.

간단하다.

 

   ITEMIDLIST *pidlBrowse;
   char pszPathname[1000];
   BROWSEINFO BrInfo;
   BrInfo.hwndOwner = GetSafeHwnd();
   BrInfo.pidlRoot = NULL;

  
   memset(&BrInfo, 0, sizeof(BrInfo));
   BrInfo.pszDisplayName = pszPathname;
   BrInfo.lpszTitle = "경로 선택";
   BrInfo.ulFlags = BIF_RETURNONLYFSDIRS;

   pidlBrowse = ::SHBrowseForFolder(&BrInfo);

   if( pidlBrowse != NULL)
   {
       ::SHGetPathFromIDList(pidlBrowse, pszPathname);
       m_strFileDownDir = pszPathname;  // 폴더 경로
   }

 

반환되는 폴더 경로는 pszPathname 에 저장된다.

 

대부분의 개발자들은 버그를 없애기 위해 많은 시간과 노력을 들인다. 그럼에도 막상 소프트웨어를 출시하고 나면 미처 발견하지 못한 버그로 낭패를 당하기 십상이다. 시간과 비용의 재투자는 말할 것도 없다. 이 같은 이유에서 개발자라면 누구나 자신이 작성한 프로그램의 버그를 찾아주는 툴이 있었으면 하는 바람을 가진다.

Sparrow는 C/C++ 프로그램의 소스를 분석하고 치명적인 오류를 찾아주는 툴이다. 테스트 케이스 실행을 통해 오류를 찾는 기존 테스팅 방법과 달리 프로그램 실행 없이 소스 코드만 분석하여 자동으로 프로그램의 오류를 찾아준다. 시간과 비용을 줄이는 완전히 다른 형태인 것이다.

■ 소스 코드에서 오류 검색
Sparrow는 C/C++에서 흔히 발생하는 메모리 관련 오류를 찾아낸다. 메모리 관련 오류로는 버퍼 오버런(buffer overruns), 메모리 누수(memory leaks), 메모리 해제 후 사용(memory uses after free) 등이다. 이번 리뷰는 프로그램 소스를 쉽게 구할 수 있는 공개 소프트웨어 분석으로 진행했다. 테스트 환경은 펜티엄4 3.2GHz와 4GB 메모리가 설치된 PC와 리눅스 운영체제를 사용하였다.

사용 방법은 간단하다. Makefile이 있는 디렉토리에서 간단한 명령어를 입력하면 분석을 시작하고, 결과까지 알려준다. 먼저 smake라는 명령어를 실행해 소스 트리의 구조를 파악하고 실행 파일이나 라이브러리를 만드는데 필요한 파일을 정리한다.

C/C++ 환경의 메모리 오류 검색 smke 이후 생성된 분석 단위

그리고 분석하고 싶은 실행 파일이나 라이브러리를 선택하여 분석을 하면 된다. 분석 결과는 HTML 페이지로 만들어지므로 웹 브라우저에서 확인이 가능하다. 우선 tar를 분석하는 데 걸리는 시간을 측정하였더니 100초 정도가 소요되었다. tar의 소스 크기가 28,000라인 정도 되니까 분석 속도는 매우 빠른 편이다.

Sparrow는 tar에서 총 26개의 메모리 누수와 버퍼 오버런이 발생할 수 있다고 분석했다. 대부분의 소스 코드 분석기 툴이 그렇듯이, 여기서 알려주는 오류는 진짜 오류가 아닐 수 있다. 따라서 오류의 진위여부를 확인할 필요가 있다.

■ 스코어 기능, 분석 결과 진위 여부 확인
Sparrow는 분석 결과로 알려준 오류가 진짜 오류인지 거짓 오류인지를 사용자가 쉽게 판단 할 수 있도록 두 가지 기능을 제공한다. 하나는 분석기에서 나온 오류의 형태를 보고 통계적인 방법을 사용해 오류의 여부를 점수로 매기는 스코어 기능이다. 사용자는 오류에서 스코어가 높은 순서대로 하나씩 확인하여 걸러내면 된다.

이는 Sparrow만의 독특한 기능이라고 하겠다. 다른 한 가지는 프로그램이 실행되고 어떤 과정에서 오류가 발생하는 지를 확인할 수 있도록 오류의 근원에서 발생 시점까지 흐름을 추적해준다. 해제 후 사용 오류와 메모리 누수 오류의 경우에는 함수 호출과 리턴 등의 프로그램 실행 흐름의 큰 변화가 일어나는 지점에 한해 정보를 제공한다.

tar 분석 결과 중 일부 버퍼 오버런 경고 화면

버퍼 오버런의 경우는 실행 흐름의 정보가 매우 자세하다. 예를 들어, 버퍼 buf가 buf[x]라는 식을 통해 접근이 되었다면, buf와 x의 값에 영향을 주는 명령어만 링크로 연결하여 보여준다. 이러한 연결 고리를 Sparrow에서는 reason chain이라고 한다. 사용자는 오류 확인을 위해 꼭 필요한 명령문만 확인하면 된다는 얘기다.

실제로 tar 분석으로 나온 버퍼 오버런이 진짜인지 reason chain으로 쉽게 확인할 수 있었다. 부가적으로는 매크로, 변수, 함수를 정의한 지점을 찾는 기본적인 기능과 정의된 함수가 어느 지점에서 호출되었는지 함수 포인터를 통해 호출한 곳까지 찾아 알려준다.

■ 프로그램 버그 해결 도우미
Sparrow는 단순히 오류가 발생한 위치만 알려주는 툴이 아니다. 그 오류가 발생한 실행 흐름 중에서 꼭 체크해야 하는 부분을 알려주므로 라인 하나하나를 일일이 살펴볼 필요가 없다. 작업 속도가 생명인 테스트 과정에서 더없이 중요한 사항이다. 참고로 큰 규모의 프로그램 분석 성능은
www.spa-arrow.com에서 확인할 수 있다.

메모리 누수 경고 화면 메모리 누수가 발생한 지점 화면

최근 들어 소프트웨어 오류에 대한 관심과 우려가 집중되고 있다. 프로그램 덩치가 거대하고 복잡해지면서 오류의 원인도 점점 찾기가 힘들어질뿐더러 숨겨진 오류가 언제 나타날지 모르기 때문이다. 더군다나 디지털화가 가속되면서 곳곳에 쓰이는 소프트웨어의 오류는 일상생활과 밀접하게 연관될 수밖에 없다. 그래서 버그 해결의 중요성은 누차 강조해도 모자람이 없다.

Sparrow는 사용자가 분석 결과를 쉽게 확인할 수 있게끔 편의성 높은 UI와 기능을 제공한다. 누구나 사용할 수 있음을 의미한다. 오랜 기간 동안 사용되고 검증된 공개 프로그램을 분석한 탓에 리뷰 과정에선 버퍼 오버런 오류가 거의 없었다. 반면 메모리 누수 오류는 꽤 많이 발견되었다. 프로그램 버그로 인한 고민을 한다면 Sparrow가 그 대안이 될 수 있겠다.

 
전민식 월간 마이크로소프트웨어 필자 msjin@gmail.com | 2007-05-07 13:01


더 보기 http://www.ebuzz.co.kr/content/buzz_view.html?ps_ccid=21988#ixzz0sg5q9E5I

+ Recent posts