'방어적 프로그래밍'에 해당되는 글 1건

  1. 2005/10/29 방어적 프로그래밍 (2)
native 프로그래밍을 하다보면 메모리 참조 에러나 메모리 누수 때문에 골치를 앓게 된다. 나 또한 그런 삽질을 많이 했고 어떻게 하면 줄일수 있을까 생각을 해봤지만 참으로 애매하다.

일명 방어적 프로그래밍이라는 패턴을 지키면 좀 더 쉽게 프로그래밍을 할 수 있다. 네이티브 프로그래밍을 하다보면 포인터 사용은 절대 불가피하다. 하지만 포인터는 양날의 칼날인것은 확실하다. 이런 포인터와 더불어 런타임 에러를 일으킬수 있는것을 적어보자면 다음과 같다.

1. 널 포인터 또는 할당되지 않는 메모리 참조
2. 0으로 나누는 수식
3. 메모리 누수
4. 무한루프


우선 생각나는 것은 이것이지만 더 있을 것이다.

또한 4번 무한 루프를 없애는 방법은 아직 발견하지 못했으므로 패스하고-_-); 1~3까지의 방법을 알아보자.

그다지 어려운것은 아니다. 단지 조금 귀찮을 뿐이다. 하지만 귀차니즘을 넘어서서 구축을 해 놓는다면 나중이 편해진다.

그럼 하나씩 알아보자.

1. 널 포인터 또는 할당 되지 않는 메모리 참조
포인터는 항상 NULL로 초기화를 하자. 또한 포인터를 사용하기 전에는 꼭 NULL검사를 하자. 하지만 NULL포인터의 경우에는 if (p) 라는 구문으로 검사가 가능하지만 잘못된 메모리 참조는 어쩔수가 없다. 잘못된 메모리 참조를 막기위해서는 핸들기반 엑세스 방식을 사용하면 된다. 어떤 메모리 또는 객체를 엑세스 할려고 할때 직접 포인터로 엑세스를 하는게 아니라 핸들을 발급 받아 엑세스를 하도록 한다. 핸들을 매니징 하는 시스템에서는 해당 핸들이 유효한지를 검사하고 유효하다면 메모리를 엑세스 하는 식으로 하면 된다. 이런 방식은 이미 우리가 사용하는 운영체제에도 사용되고 있는 방식이니 새로운 방식은 절대 아니다. 하지만 만들어놓고 사용하니 더이상의 메모리 참조 에러와는 안녕인것이다. 물론 핸들 매니저가 버그가 있어서 참조 에러가 났을 경우도 있지만 오히려 이것이 더 좋은 점이다. 참조 에러가 났다는 의미는 핸들 기반 매니저의 버그인것이 확실 하기 때문에 버그를 찾기가 매우 수월하다는 것이다.

2. 0으로 나누는 수식
어떤 수를 0으로 나눌 경우에는 운영체제에서 예외가 발생하여 프로그램이 중지 되게 된다. 이는 잘못된 포인터의 엑세스와 마찬가지로 꽤나 골치 아픈 문제이다. 하지만 이는 포인터보다는 적게 나타나지만 꼭 한번씩 나타나는 문제이다. 정수의 경우에는 에러가 나지만 실수의 경우에는 값이 무한대(INF)가 되어서 원하지 않는 결과가 나오거나 게임과 같은 3D그래픽 환경에서는 오브젝트가 화면에 나타나지 않는 경우가 생긴다. 이를 방지하기 위해서는 단 한가지 방법밖에 없다. 제수가 0인지 아닌지를 검사하는 수 밖에-_-); 그렇지만 매번 이런걸 검사하면 성능에 그다지 이득될게 없다. 따라서 이런 검사는 디버그에서만 동작하도록 assert로 묶어두는것이 좋다. assert,.. 이거 꽤나 괜찮은 방법이다. 적극 활용하도록 하자. 0으로 나누는 문제 뿐만 아니라 포인터의 유효성 검사부분에서도 if(p)가 부담이 된다면 이부분을 assert로 묶어 놓고 디버그모드일때만 동작하게 하고 안정성이 입증되면 릴리즈로 컴파일 하면 된다.

3. 메모리 누수
c++에는 생성자와 소멸자가 있다. 이를 적극 사용하자. 객체가 생성될때 항상 생성자가 호출되며 객체가 삭제 될때는 항상 소멸자가 호출 된다. 단 주의할 점이 있다. 객체를 new와 delete를 이용해서 생성 삭제할때만 생성자와 소멸자가 호출이 된다는것이다 c스타일의 malloc과 함수나 free같은 함수를 사용하면 절대 생성자와 소멸자가 호출이 안된다. 반드시 주의하도록 한다, c++으로 작업 할때는 malloc과 free 함수은 잊도록 하자. 더 좋은 new와 delete가 있으니 이를 사용하도록 한다. 간단한 객체의 경우에는 메모리 누수가 날 부분이 적어지겠지만 한 객체에서 동적으로 어떤 객체를 포함하는 형식의 구조를 가지면 수십개 또는 수백개가 될지도 모르는 동적 객체들을 신경쓰기가 힘들어 진다. 이때 객체가 소멸될때 호출되는 소멸자에 모든 메모리 해제 루틴을 넣어두면 만사 해결이다. 한가지 더 지킬게 있다면 모든 객체는 가능하면 호출자와 소멸자 뿐만 아니라 자체적으로 객체를 초기화 하는 함수와 자체적으로 삭제하는 public함수를 만들어 놓으면 편해진다. 가끔 가다보면 생성한 한 객체를 완전 바로 생성후의 상태로 돌려놓아야 하는 경우가 있다. 이는 매번 메모리를 할당 제 하면 부담이 되기 때문에 메모리 풀과 같은 형태의 구조를 가지는 부분에서는 매우 편하다. 이런 구조는 어떤 객체를 사용하고 나서 메모리 풀에다가 해제를 요청하면 메모리 풀은 이 객체를 메모리에서 해제 하지 않고 "사용하지 않음" 상태로 만들어놓는 과정만 거치면 느린 메모리 할당 해제를 수행하지 않아도 되기 때문에 높은 수행 속도를 가질수 있다.

마지막으로 프로그래밍은 가능하면 방어적으로 하도록 하자. 언제 어디서 문제가 터질지 모른다. 그때가서 머리 싸매지 말고 습관적으로 방어적 프로그램을 하자.
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
2005/10/29 22:38 2005/10/29 22:38