Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

우리가 델파이 오브젝트를 생성할 때 무슨 실수를 하는가?


Recommended Posts

Softacom Information "What Are The Mistakes We Made In Creating Delphi Objects?" 을 번역했습니다. (원문 작성: 2021년 11월, 최종 번역: 2024년 5월)

현실에서 모든 개발자, 심지어 프로그래밍 초보자도 오브젝트를 안전하게 생성하고 소멸할 수 있다. 고전적인 생성으로 대다수 프로그램들 안에서 사용되는 방식은 아래와 같다:

MyObject:= TMyClass.Create();
try
  {some code…}
finally
  MyObject.Free;
end;

그런, 과거에는, 많은 논쟁이 오브젝트 생성 장소를 두고 벌어졌었다: 예외 핸들러 앞이 좋은가 아니면 try-finally-end 생성 안에 두는 것이 좋은가에 대해서 말이다.  물론, 지금은 이 질문에 대한 정답을 찾는 것이 어렵지 않다. 잘-작성된 코드 예문을 온라인 자료에서 많이 찾을 수 있다. 스택오버플로우, 도서, 공식 도움말 문서 등이다.

하지만, 여러분은 왜 그런 지 그리고 잘못된 변형을 사용하면 무슨 일이 벌어지는 지를 정말 이해하고 있는가? 여러분이 기술 면접 동안 이 질문에 대해 대답해야 할 수도 있다. 만약 정답을 알지 못한다면, 이 글을 계속 읽어 보자. 그리고 윈도우 IDE를 사용할 때 빼먹는 실 수를 하지 않는 법을 배워보자.

차례


델파이 코드 문제를 디버깅하는 최고의 방법은 무엇인가?

여러분은 디버그 메시지들을 다양한 방식으로 얻을 수 있다. 하지만 가장 편한 방법 중 하나는 CodeSite  로깅 시스템을 사용하는 것이다. 그것의 라이트(lite) 버전을 겟잇 패키지 매니저(GetIt package manager)에서 설치할 수 있다. 이 링크를 사용하면 된다: https://getitnow.embarcadero.com/?q=codesite&product=rad-studio&sub=all&sortby=date&categories=-1
 

오브젝트를 사용하는 잘못된 방식은 무엇인가?

예시로, 클래스 하나를 생성하자.

TMyClass = class
private
  function getSelfPointer: Pointer;
public
  constructor Create(beRaized: Boolean = False);
  procedure Free; overload;
end;
 
implementation
 
{$R *.dfm}
 
{ TMyClass }
constructor TMyClass.Create(beRaized: Boolean);
begin
  if beRaized then
    raise Exception.Create('Error Message');
end;
 
procedure TMyClass.Free;
begin
  // inherited;
  CodeSite.Send( 'Destroy object');
{$IFNDEF AUTOREFCOUNT}
  if Self <> nil then
  begin
    CodeSite.Send( ' Destroyed object address:', IntToHex(Integer(Pointer(self))));
    Destroy;
  end;
{$ENDIF}
end;
 
 
function TMyClass.getSelfPointer: Pointer;
begin
  Result := Pointer(Self);
end;

우리의 클래스 안에서 우리는 Free 메서드를 재정의할 것이다. 그저 우리가 소멸하기를 원하는 오브젝트의 주소를 보기 위해서다. 우리는 TObject 에 있는 Free 메서드를 복사해서 그 오브젝트를 소멸하는데 사용할 것이다. 고유한 메서드는 오직 getSelfPointer 메서드 하나다.  이것은 그 오브젝트에 대한 포인터를 반환한다.
이 클래스 생성자는 논리적인 의미를 파라미터로 받는다. 만약 이것이 True면, 예외가 생성된다. 그리고 오브젝트는 생성되지 않는다.

이제, TMyClass 타입인 전역(global) 변수 두 개를 보자.

var
  fMain: TfMain;
 
  MyObject,
  MyObject2: TMyClass;

 

델파이 생성자 안에서 우리는 무슨 오류를 얻을 수 있는가?

폼 위에 버튼 두 개를 놓자. 그리고 그것들을 위해 클릭 핸들러들을 정의하자.

procedure TfMain.Button1Click(Sender: TObject);
begin
  MyObject:= TMyClass.Create();
  try
    CodeSite.Send('Actual MyObject address: ', IntToHex(Integer(MyObject.getSelfPointer ), 8));
  finally
    MyObject.Free;
  end;
 
  MyObject2:= TMyClass.Create();
  try
    CodeSite.Send('MyObject2 was created. Actual MyObject2 address: ', IntToHex(Integer(MyObject2.getSelfPointer ), 8));
    // MyObject:= TMyClass.Create(True);
    try
      CodeSite.Send('Try creating MyObject');
 
      MyObject:= TMyClass.Create(True);
 
    finally
      CodeSite.Send( 'MyObject. Finally');
      MyObject.Free;
    end;
  finally
 
  end;
end;
 
procedure TfMain.Button2Click(Sender: TObject);
begin
  CodeSite.Send( 'MyObject2.ClassName', MyObject2.ClassName );
end;

시작으로, MyObject 라는 오브젝트를 하나 생성한다. 그리고 그 메모리 주소를 로그에 기록한다. 그리고 즉시 소멸한다. 그리고 나서, MyObject2를 생성한다. 그 주소를 로그에 기록한다. 그리고 MyObject를 다시 생성한다.  이와 동시에, 우리는 그 생성자 안에서 예외를 발생시킨다. 실제로, 이 오브젝트는 생성되지 않을 것이다.

공식 도움말 문서에 따르면 다음과 같다 - https://docwiki.embarcadero.com/RADStudio/Sydney/en/Methods_(Delphi) - "만약 클래스 참조 위에서 호출되는 생성자를 실행하는 동안 예외가 발생하면, Destroy 소멸자가 자동으로 호출된다. 그래서 그 마무리되지 않은 오브젝트를 소멸한다."

이 코드 실행 결과를 로그 안에서 보면 다음과 같다:

Actual MyObject address: = 018A8B80
Destroy object
Destroyed object address: = 018A8B80
MyObject2 was created. Actual MyObject2 address: = 018A8B80
Try creating MyObject.
MyObject. Finally
Destroy object

맨 처음에, MyObject 가 생성된다. 그리고 즉시 소멸된다. 그리고 나서 MyObject2가 생성된다. 그리고 우리가 처음으로 놀라게 될 일이 여기에 있다. 첫 오브젝트의 주소는 소명한 오브젝트의 주소와 완전히 똑같다. 하지만, 실제로, 예상치 못한 것은 전혀 아니다. 델파이에서 오브젝트 저장소가 정돈되는 독특한 방식을 알고 있다면 말이다.

그러면 이제 우리는 Free 메서드가 비록 오브젝트를 소멸하기는 하지만, 그 오브젝트로 이끄는 링크는 소멸하지 않는다는 점을 명심해야 한다. 
즉, MyObject 는 nil이 아니다.
 

예외 핸들러 안에서 오브젝트를 생성하는 동안 무슨 일이 생기는가?

만약 지금 예외 핸들러 안에서 오브젝트를 생성하는 동안,, 오류가 발생하면 무슨 일이 생기는가? 그래 맞다. finally 블록이 실행된다. 그리고 Free 메서드가 호출된다.

우리는 이 코드 부분에 대해 말하고 있다:

try
  CodeSite.Send('Try creating MyObject');
 
  MyObject:= TMyClass.Create(True);
 
finally
  CodeSite.Send( 'MyObject. Finally');
  MyObject.Free;
end;

우리는 그 오브젝트가 생성되지 않는다는 것을 기억한다. 하지만, 정적 메서드인 Free는 그 오브젝트가 018A8B80 주소에 있다고 여전히 생각한다. 그 오브젝트를 처음 생성 후 해당 링크가 저장되어 있기 때문이다.  그런데, 실제로 그 주소는 다른 오브젝트의 주소다. 그것이 소멸되게 되는 것이다. 정말 그런지 확인하려면 Button2를 누르기만 하면 된다.

procedure TfMain.Button2Click(Sender: TObject);
begin
  CodeSite.Send( 'MyObject2.ClassName', MyObject2.ClassName );
end;

우리는 그 유명한 액세스 위반(Access Violation) 오류를 얻게 된다.

그 결과, 이 생성자를 사용해서 오브젝트를 생성하고 소멸하기 때문에, 우리는 다른 오브젝트를 소멸했다. 이와 비슷한 오류들은 꽤 위험하다. 오브젝트들의 리스트에서 말이다. 그리고 이런 오류를 찾는 것은 상당히 어려운 과정이다. 우리가 두번째 생성한 오브젝트가 MyObject 였다면, 즉 대부분의 경우에서 적용하는 일반적인 방식으로 했다면, 이런 종류의 일이 생기지 않는다는 점은 분명하다.
 

액세스 위반(access violation), 델파이 오브젝트, 클래스에 대해 더 많은 것을 읽으려면 어디로 가면 되는가?

액세스 위반(access violation), 델파이 오브젝트, 클래스에 대해 더 많은 것을 읽으려면 어디로 가면 되는가?
이 정보에 대해 보다 깊이 이해하고 싶다면, 다음 글을 읽어보기 바란다: http://rvelthuis.de/articles/articles-pointers.html , 또한 Aleksandr Alekseev가 제공하는 자료들도 있다: https://www.gunsmoker.ru/2009/04/freeandnil-free.html

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.