본문 바로가기

OpenFOAM/소스코드 파해치기

[OpenFOAM 소스코드 파해치기] autoPtr과 tmp

일본어 원문링크


작성일 : 2012년  5월   19일

번역일 : 2016년  3월   29일


크롬 브라우저로 보시는 것을 권장해 드립니다.


 ------ OpenFOAM 소스코드 파해치기 시리즈 ------

OpenFOAM 소스코드 파해치기 목차로 이동

 ------------------------------------------------------------

 ----- OpenFOAM 소스코드를 다루는 문법 기본 -----

OpenFOAM 소스코드를 다루는 문법 기본으로 이동

 ------------------------------------------------------------



 

  들어가기 


autoPtr과 tmp에 대해 살펴보자.



  사용 버전


OpenFOAM 2.1.0



  autoPtr  


자동변수

보통 사용되지는 않지만 "자동변수" 라는 용어가 있다. 이것은 C 언어에서 함수 내의 static 변수에 해당하는 개념이다. 자동변수는 정의된 함수에서 나오면 메모리에서 자동적으로 삭제된다. 한편, static 변수는 함수에서 나와도 메모리에서 지워지지 않는다. 예를 들 면 아래와 같은 함수를 생각할 수 있다.

void f1() { int a = 0; a++; printf("%d\n", a); } void f2() { static int a = 0; a++; printf("%d\n", a); }


f1() 의 출력은, 언제나 "1"이지만 f2()는 실행할때마다 "1", "2", "3",... 으로 값이 증가하게 된다. f2()의 경우 변수 a가 메모리에 그대로 존재하기 때문이다.



자동포인터
포인터를 자동변수로 정의하면 함수에서 나올때 주소가 메모리에서 지워질 뿐, 할당된 메모리 자체가 사라지지는 않는다. 따라서 포인터가 차지한 메모리의 비할당 작업이 필요하다.

void f() { int *a; a = malloc(sizeof(int)*N);

...

free(a); }


매번 수행하기에 귀찮은 작업이기에 "자동포인터"라는 개념이 탄생했다. C++의 오브젝트에서는 만들어질때 생성자, 삭제할때 소멸자를 자동적으로 호출한다. 자동포인터는 그러한 특성을 이용하여 생성자를 통해 메모리의 주소를 받고, 소멸자를 통해 그 메모리를 해방시킨다. 이렇게 하면 오브젝트가 삭제될때 포인터가 가르키는 메모리가 해제되게되어 메모리 관리에 용의하다.



autoPtr
OpenFOAM 에서는 자동포인터를 구현하는 클래스로, autoPtr을 제공하고 있다.
$FOAM_SRC/OpenFOAM/memory/autoPtr의 코드를 살펴보자.
autoPtrI.H

template<class T> inline Foam::autoPtr<T>::autoPtr(T* p) : ptr_(p) {} ... template<class T> inline Foam::autoPtr<T>::~autoPtr() { clear(); }


생성자 autoPtr()로 포인터를 저장, 소멸자 ~autoPtr()로 clear()를 호출하고 있다. clear()는 아래와 같다.

template<class T> inline void Foam::autoPtr<T>::reset(T* p) { if (ptr_) { delete ptr_; } ptr_ = p; } template<class T> inline void Foam::autoPtr<T>::clear() { reset(0); }


포인터가 어떤곳을 가르키고 있다면 메모리를 삭제하고, 삭제한 메모리가 잘못 사용되지 않도록 0을 설정하고 있다.


일시적으로 메모리를 확보하고 싶다면 자동포인터를 사용하면 편리하다. 이렇게 기능을 강화한 포인터를 "스마트포인터"라고 부르기도 한다.




  tmp  


tmp는 무엇인가?

tmp는, 이름으로부터 스마트 포인터의 한 종류로 보인다. 실제 메모리를 자동적으로 제거하는 점에서 autoPtr과 닮아있으나 더 높은 차원의 기능을 수행한다. tmp는 쓰레기 수집기이다.



쓰레기 수집기
C/C++에서는 메모리의 확보가 가능하지만, 메모리의 해제도 직접 수행해야 한다. 단순한 프로그램에는 크게 문제가 되지 않지만, 복잡한 프로그램에서는 메모리의 해제를 깜빡하거나 이중해제하거나 이미 해제된 메모리에 접근하거나 메모리가 포인터에도 참조되지 않은 상태고 있다던지 다양한 문제가 발생한다. 게다가 포인터에 기인하는 버그는 메모리와 관련되므로, 이해하기 어려운 현상이 발생하는 경우가 많고, 디버깅이 상당히 복잡해진다. 이러한 문제들을 회피하기 위해 메모리의 해제를 자동적으로 수행하는 것이 쓰레기 수집기이라는 개념이다.

쓰레기 수집기는 어느 포인터에도 참조되지 않게된 메모리를 해방시킨다. 그렇기 때문에 "참조카운터"를 사용한다. 먼저, 최초의 포인터에 메모리가 할당된다. 참조 카운터가 1 이 된다. 이 주소를 다른 포인터에 대입한다. 이렇게 되면 메모리가 참조하는 포인터가 2개가 되므로 참조 카운터는 2 가 된다. 이때, 대입되는 포인터가 이미 메모리를 가지고 있다면 그 메모리의 카운터가 하나 줄어들게 된다. 또한, 포인터 자체를 삭제하는 경우에도, 참조카운터가 하나 줄어들게 된다. 이렇게하여 참조카운터가 0 이되면 그 메모리는 어떠한 포인터에도 참조되지않으므로 삭제한다.



tmp
tmp 는 $FOAM_SRC/OpenFOAM/memory 에서 다음과 같이 정의된다.

tmpI.H

template<class T> class tmp { // Private data //- Flag for whether object is a temporary or a constant object bool isTmp_; //- Pointer to temporary object mutable T* ptr_; //- Const reference to constant object const T& ref_; ...


ref_ 가 참조카운터에 해당한다. 형태가 "const T&" 로 되어 있는데 이것은 Field 의 정의를 보면 이유를 알 수 있다.

template<class Type>

class Field

:

public refCount,

public List<Type>


즉 tmp 는, 참조 카운터클래스 refCount를 계승하고 있는 Field 의 하위 클래스 (GeometricField 등)을 템플릿 클래스로 상정하고 있다.


refCont의 조작은 어느정도 예상가능하다. tmp 의 코드를 살펴보자

tmpI.H.

template<class T> inline Foam::tmp<T>::tmp(T* tPtr) : isTmp_(true), ptr_(tPtr), ref_(*tPtr) {} ... template<class T> inline Foam::tmp<T>::tmp(const tmp<T>& t) : isTmp_(t.isTmp_), ptr_(t.ptr_), ref_(t.ref_) { if (isTmp_) { if (ptr_) { ptr_->operator++(); } else { FatalErrorIn("Foam::tmp<T>::tmp(const tmp<T>&)") << "attempted copy of a deallocated temporary" << abort(FatalError); } } } template<class T> inline Foam::tmp<T>::~tmp() { if (isTmp_ && ptr_) { if (ptr_->okToDelete()) { delete ptr_; ptr_ = 0; } else { ptr_->operator--(); } } }


일반적 포인터를 받을 때는 그것을 설정, tmp 를 받을때는 참조카운터를 증가시킨다. 오브젝트를 제거할때는 참조카운터를 줄이며 메모리를 삭제할수 있는 상태이면 삭제한다.


대입연산자는 다음과 같다.

template<class T> inline void Foam::tmp<T>::operator=(const tmp<T>& t)

{ if (isTmp_ && ptr_) { if (ptr_->okToDelete()) { delete ptr_; ptr_ = 0; } else { ptr_->operator--(); } } if (t.isTmp_) { isTmp_ = true; ptr_ = t.ptr_; if (ptr_) { ptr_->operator++(); } else { FatalErrorIn("Foam::tmp<T>::operator=(const tmp<T>&)")

<< "attempted copy of a deallocated temporary" << abort(FatalError); } } else { FatalErrorIn("Foam::tmp<T>::operator=(const tmp<T>&)")

<< "attempted to assign to a const reference to constant object" << abort(FatalError); } }


대입되는 쪽은 참조카운터를 줄이거나, 메모리를 삭제한다. 대입하는 데이터를 설정하고 참조카운터를 증가시키고 있다.

이 방식은 불필요한 메모리 할당(메모리 리크) 을 방지함과 동시에 메모리를 가능한 복제하지 않고 여러곳에서 사용할 수 있어 효율성이 증대된다.



어떻게 사용할 것인가?
함수가 뱉는 값의 형태가 tmp 인 경우, tmp 를 받도록 하면 된다.

오브젝트가 확보한 메모리의 포인터를 그 오브젝트 밖에서 받을 때, 원래의 오브젝트를 지워버리면 보통 포인터가 가르키는 메모리도 삭제된다. 메모리가 삭제되는 책임을 메모리를 확보한 클래스가 지기 때문이다(이렇게 하지 않으면 참조되지 않은 메모리가 계속 남아있게 된다). tmp 를 사용하면, 메모리해제의 책임을 tmp에 위임하게 되므로 포인터를 쉽게 밖의 클래스에 전달할 수 있게 된다. 이때 원래의 오브젝트를 삭제해도 그것을 받은 포인터(tmp)가 가르키는 메모리는 삭제되지 않고 남는다.

참고로, simpleFoam의 UEqn 이 tmp 로 정의되어 있는 이유는, 아마도 pEqn을 만들기 전에 메모리를 삭제해 두고 싶기 때문일 것이다. pEqn을 만들기 전에 UEqn.clear()가 호출되거 있는데 tmp의 clear()는 메모리를 무조건 삭제한다. 여기서의 tmp는 보통의 포인터로 사용되고 있다.
UEqn.H

tmp<fvVectorMatrix> UEqn ( fvm::div(phi, U) + turbulence->divDevReff(U) == sources(U) );


pEqn.H

{ p.boundaryField().updateCoeffs(); volScalarField rAU(1.0/UEqn().A()); U = rAU*UEqn().H(); UEqn.clear(); phi = fvc::interpolate(U, "interpolate(HbyA)") & mesh.Sf(); adjustPhi(phi, U, p); // Non-orthogonal pressure corrector loop while (simple.correctNonOrthogonal()) { fvScalarMatrix pEqn ( fvm::laplacian(rAU, p) == fvc::div(phi) ); ...