기본 콘텐츠로 건너뛰기

auto_ptr 사용.

출처 발구르기 | 발구르기
원문 http://blog.naver.com/yaiin0911/120022027021

[WinApi.co.kr]

36-3.auto_ptr

36-3-가.자동화된 파괴

C++의 클래스는 파괴자라는 특별한 함수를 가지는데 이 함수는 객체가 파괴될 때 자동으로 호출된다.
그래서 객체가 동적으로 메모리를 할당하거나 시스템 자원을 사용하더라도 파괴자에 정리 코드를 작성해 놓으면 별도의 조치가 없더라도 객체가 사라질 때 해제 작업을 하도록 되어 있다.
파괴자의 이런 동작은 굉장히 편리한데 지역 객체일 경우 함수안에서 마음대로 만들어 쓰다가 그냥 나가기만 하면 된다.
범위를 벗어난 변수는 스택에서 제거되며 이때 객체의 파괴자가 호출되어 자신이 사용하던 자원을 알아서 정리하는 것이다.

앞 절에서 연구해 본 string 클래스를 생각해 보면 파괴자가 얼마나 편리한가를 알 수 있다.
string 객체는 가변 길이의 문자열을 저장하기 위해 버퍼를 동적으로 할당해서 관리하는데 개발자가 신경쓰지 않아도 이 메모리는 자동으로 회수된다.
이런 면을 보면 파괴자는 역시 편리한 함수이다. 그러나 파괴자는 스택에 정적으로 할당된 객체에 대해서만 동작하며 동적으로 할당한 메모리에 대해서는 책임지지 않는 문제점이 있다.
다음 예제를 보자.
예제 : dynalloc
#include <iostream>
using namespace std;

void main()
{
     double *rate;

     rate=new double;
     *rate=3.1415;
     cout << *rate << endl;
     // delete rate;
}
실수형 변수를 가리키는 rate 포인터를 선언하고 이 포인터에 실수형의 길이만큼 동적 할당하여 그 번지를 저장했다.
이렇게 되면 *rate는 실수형 변수가 되므로 동적으로 할당된 메모리를 실수형 변수처럼 사용할 수 있다. rate는 이 함수의 지역변수이므로 함수가 종료될 때 자동으로 해제된다.
그러나 rate가 가리키는 메모리는 자동으로 해제되지 않는데 동적으로 할당했다는 것은 필요할 때까지 쓰겠다는 의사 표현이므로 직접 해제하기 전까지는 힙에 계속 남아 있는다.
메모리 관리 원칙에 의해 한 번 할당한 메모리는 해제할 때까지 다른 용도로 재사용되지 않으므로 명시적으로 delete를 호출해야만 해제된다.
그래서 동적으로 할당한 메모리는 반드시 대응되는 해제 코드(free, delete)로 해제해야 한다.
위 예제에는 delete 호출문이 주석으로 처리되어 있으므로 이렇게 되면 할당한 메모리를 더 이상 사용할 수 없는 메모리 누수(Memory Leak)가 발생할 것이다.
동적으로 할당된 메모리는 이름이 없으므로 포인터를 잃어 버리면 더 이상 참조할 수 없고 해제하지도 못한다.
물론 이런 짧은 코드에서 delete문을 빼 먹는 실수는 잘 하지 않을 것이다.
그러나 코드가 아주 길고 복잡하다 보면 해제하는 코드를 깜박 잊어 버리는 경우가 종종 있다.
또는 해제하는 코드가 있다 하더라도 예외 처리 구문에 의해 함수를 강제로 종료할 때는 이 코드가 실행되지 못하는 경우도 있는데 다음 코드가 이런 예를 보여 준다.
void func()
{
     double *rate=new double(3.14);

     if (어떤 조건) {
          throw("야! 똑바로 못해");
     }

     ....

     delete rate;
}
정상적인 실행 흐름이라면 new와 delete가 짝을 이루어 할당, 해제가 이상없이 진행되지만
예외 조건에 의해 throw를 호출하면 함수 실행을 즉시 중지하고 호출부의 catch로 점프해 버리므로 delete는 실행되지 못한다.
이럴 경우 예외 처리 구문은 스택 되감기를 통해 지역 객체의 파괴자를 자동으로 호출하도록 되어 있지만 지역 객체가 가리키는 메모리까지 해제되는 것은 아니다.
따라서 불가피하게 메모리 누수가 발생하게 된다.
이런 식의 메모리 누수는 양이 많지 않을 경우 당장은 별 문제가 되지 않으며 컴파일 중에 에러가 나는 것도 아니다.
그러나 오랫동안 실행되는 프로그램은 시스템 자원을 야금 야금 갉아 먹으므로 언젠가는 말썽을 부릴 것이다.
이 문제는 생각보다 심각한데 사람은 해제 코드를 빼먹는 실수를 종종 하는데 비해 몇 달, 몇 년동안이나 실행되어야 하는 서버 프로그램의 경우 조금의 메모리 누수도 허용되지 않기 때문이다.
멀티 태스킹 환경에서 메모리 누수는 자신뿐만 아니라 같이 실행되는 다른 프로그램에도 피해를 끼친다는 점에서 심각하다.
단순 포인터는 파괴자를 가지지 않기 때문에 C++의 파괴자로는 이 문제를 제대로 해결할 수 없다. 포인터 변수만 해제될 뿐이지 포인터가 가리키는 메모리는 해제되지 않는다.
이런 문제를 해결하기 위해 만들어진 것이 바로 auto_ptr이다. auto_ptr은 동적으로 할당된 메모리도 자동으로 해제하는 기능을 가지는 포인터의 래퍼 클래스이다.
auto_ptr의 파괴자에 포인터 해제 코드를 작성하면 어떤 경우라도 안전한 해제를 보장할 수 있다.
다음 예제를 보자.
: auto_ptr
#include <iostream>
#include <iostream>
#include <memory>

using namespace std;
void main()
{
     auto_ptr<double> rate(new double);
     *rate=3.1415;

     cout << *rate << endl;
}
auto_ptr 템플릿은 memory 헤더 파일에 정의되어 있으므로 사용하려면 이 헤더 파일을 먼저 포함시켜야 한다. auto_ptr은 다음과 같이 정의되어 있는 클래스 템플릿이다.
template<typename T> class auto_ptr
포인터가 가리키는 대상체의 타입 T를 인수로 받아 들이며 T *형의 포인터를 대신 관리한다.
생성자로 포인터를 전달하면 이 포인터를 가지고 있다가 파괴자에서 delete로 해제하므로 포인터뿐만 아니라 포인터가 가리키는 메모리도 자동으로 해제된다.
예제 코드를 보면 auto_ptr<double> 타입의 객체 rate를 선언하되 새로운 double형 변수를 동적 할당하여 생성자로 전달했다.
auto_ptr은 이 포인터를 내부 멤버 변수에 저장해 놓고 *, ->, = 등 포인터에 사용하는 대부분의 연산자를 오버로딩하여 이 객체에 대한 모든 연산을 내부 포인터에 대한 연산으로 중계하는 역할을 한다.
그래서 rate를 마치 double형의 포인터인 것처럼 사용할 수 있다. rate객체에 *연산자를 적용하면 동적으로 할당된 메모리에 대해 *연산자가 적용되어 이 값을 읽거나 변경할 수 있다.
래퍼이므로 래핑한 대상을 그대로 흉내내는 것이다.
rate의 파괴자에서는 delete를 자동으로 호출하므로 함수가 끝날 때 rate를 해제할 필요가 없으며 해제되지도 않는다.
delete rate 코드를 함수 끝에 작성하면 컴파일 에러로 처리되는데 rate 객체 자체는 포인터가 아니기 때문이다.
예외 처리 구문에 의해 스택 되감기를 실행할 때도 rate객체의 파괴자가 호출되며 이때 메모리도 해제된다.
단순 포인터는 파괴자가 없지만 auto_ptr은 클래스이므로 파괴자가 호출된다.
다음은 좀 더 복잡한 예제를 보자.
단순 타입에 대한 포인터가 아닌 객체에 대한 포인터를 auto_ptr로 관리할 수도 있다.
다음 예제는 string 객체를 동적으로 할당한 후 해제하지 않고 리턴함으로써 의도적으로 메모리 누수를 발생시킨다.
: dynstring
#include <Turboc.h>
#include <string>
#include <iostream>

using namespace std;

void main()
{
     string *pStr=new string("AutoPtr Test");

     cout << *pStr << endl;
     // delete pStr;
}
pStr 변수만 파괴될 뿐 이 변수가 가리키는 string 객체는 파괴되지 않으며 뿐만 아니라 string 객체가 관리하는 문자열 버퍼도 파괴되지 않는다.
만약 문자열의 길이가 아주 길다면 이때의 메모리 누수는 심각한 시스템 자원 누출이 될 것이다.
string 객체는 동적으로 할당되었으므로 함수가 종료될 때 자동으로 파괴되지 않으며 예외 처리 구문에 의해 강제 종료될 때도 마찬가지이다.
이 문제도 auto_ptr을 사용하면 해결할 수 있다.
: autostring
#include <Turboc.h>
#include <string>
#include <iostream>
#include <memory>

using namespace std;

void main()
{
     auto_ptr<string> pStr(new string("AutoPtr Test"));
     cout << *pStr << endl;
}
string 대상체를 가리키는 auto_ptr 객체 pStr을 선언하고 새로운 string객체를 동적으로 할당한 번지를 생성자로 전달했다.
이때 pStr의 메모리 내부는 아마도 다음과 같은 모양이 될 것이다.
main 함수가 종료되면 지역 객체 pStr이 파괴되며 이 과정에서 pStr의 파괴자가 호출된다. 파괴자는 내부적으로 유지하고 있는 포인터를 delete한다.
삭제되는 대상이 string 객체이므로 이 과정에서 string의 파괴자가 호출되며 문자열 버퍼도 정리된다.
설사 main이 비정상적으로 종료되더라도 정리 코드가 자동으로 실행되므로 메모리 누수는 발생하지 않는다.
동적으로 메모리를 할당하거나 객체를 생성할 때는 auto_ptr 템플릿을 사용하면 확실히 안전하기는 하다.
그러나 단순 포인터를 쓰는 것에 비해 다소 번거롭다는 단점이 있다.
자신이 책임지고 해제한다거나 예외가 발생할 가능성이 전혀 없다면 굳이 auto_ptr을 쓰지 않아도 상관없다.
그러나 많은 개발자들이 이런 확신을 하지 못하기 때문에 동적 할당할 때는 auto_ptr을 통해 확실한 해제를 보장받고자 하는 것이다.

댓글

이 블로그의 인기 게시물

80040154 오류로 인해 CLSID가 {xxxx-...}인 구성 요소의 COM 클래스 팩터리를 검색하지 못했습니다.

원문보기 .NET 으로 만든 응용프로그램에서 com 객체를 호출한 경우 Windows7 64bit 에서 제목과 같은 에러가 발생했다. Win32 COM 과 .NET 프로그램간의 호환성 때문에 생긴 문제였다. 원인은 .NET 실행시 JIT 컴파일러에 의해 최적화된 기계어로 변환되기 때문.. Win32 COM은 컴파일시.. Win32 COM에 맞춰 빌드 속성에서 하위버전으로 맞춰 컴파일을 다시하는 방법도 있지만 메인 프로젝트가 .NET이라면 참조되는 모든 프로젝트를 다 바꿔야할 노릇.. 또 다른 방법은 COM+를 이용하여 독립적으로 만드는 것이다. 분리시키는 방법은 아래 주소해서 확인할 수 있다. http://support.microsoft.com/kb/281335 나의 경우는 Win32 COM DLL을 64비트 .NET 프로그램에서 참조하니 COM 객체를 제대로 호출하지 못하였습니다. 그래서 .NET 프로그램의 Target Machine을 x86으로 설정하니 제대로 COM 객체를 호출하였습니다.

[Pyinstaller] 실행 파일 관리자 권한 획득하기

고객사에서 일부 사용자에게서 프로그램 오류가 발생한다며 아래와 같이 에러 캡처를 보내왔습니다. 프로그램에서 로그를 남기기 위해 로그 파일을 생성하는데 권한의 문제로 로그 파일을 생성하지 못해 프로그램 오류가 발생한 것 같습니다. 처음에는 Python 코드에서 관리자 권한을 요청하는 코드를 넣으려고 했는데, 실제로 Stackoverflow를 찾아보면 이런 내용이 나옵니다. 프로그램이 관리자 권한으로 실행되지 않았다면 관리자 권한으로 다시 프로그램을 실행시키는 코드입니다. import os import sys import win32com.shell.shell as shell ASADMIN = 'asadmin' if sys.argv[-1] != ASADMIN: script = os.path.abspath(sys.argv[0]) params = ' '.join([script] + sys.argv[1:] + [ASADMIN]) shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params) sys.exit(0) 하지만 개인적으로 이런 방식은 마음에 들지 않았고 조금 더 찾아보니 Pyinstaller로 exe 파일을 만들 때 옵션을 설정하여 관리자 권한을 요청하도록 할 수 있다고 합니다. --uac-admin을 옵션에 추가하면 프로그램 실행 시 관리자 권한을 요청할 수 있습니다. pyinstaller.exe --uac-admin sample.py 하지만 안타깝게도 이 방식은 원하는 대로 동작하지 않았습니다. 마지막으로 manifest 파일을 이용하여 시도해보았습니다. spec 파일을 이용하여 pyinstaller로 빌드하면 <실행 파일 이름>.manifest 라는 파일이 생성됩니다. 파일에서 아랫부분을 찾아볼 수 있습니다. <security> <re

초간단 프로그램 락 걸기

프로그램에 락을 걸 일이 생겨났다. 하드웨어 락을 걸면 쉬울텐데 그 정도는 아니고 프로그램의 실행 날짜를 제한 해 달라고 한다. 그래서 파일(license.lic)을 가지고 락을 걸리고 결정을 했다. 요구 사항은 아래와 같다. 1. license.lic 파일이 없으면 프로그램을 실행 할수 없게 한다. 2. 지정한 날짜를 넘어서는 프로그램을 실행 할수 없게 한다. 3. 사용자가 시스템 날짜를 되돌렸을때 인식하여 프로그램을 실행 할수 없게 한다. 음.... 1.번 문제는 사용자가 프로그램을 실행하기 위해서 license.lic 파일을 받아야만 한다. license.lic 파일에는 최근 실행 날짜/종료날짜 이런식으로 적도록 한다.(물론 내용은 암호화 한다.) 최근 실행날짜는 프로그램이 실행때마다 업데이트 하도록 하고 시스템 날짜와 비교하여 시스템 날짜가 최근 실행 날짜보다 이전의 날짜면 시스템 날짜를 되돌렸다고 인식하도록 한다.(3.번 문제 해결) 시스템 날짜와 종료 날짜를 비교하여 시스템 날짜가 종료 날짜를 넘으면 프로그램을 실행 할수 없도록 한다.(2.번 문제 해결)