| [ CnUnix ] in KIDS 글 쓴 이(By): shamin (S Kim) 날 짜 (Date): 2003년 6월 4일 수요일 오후 02시 14분 04초 제 목(Title): Re: c++ exception handling (윈도, 유닉스 예외상황에 대해 논하기 전에 먼저 "표준"와 "표준의 구현"의 차이부터 알아야겠습니다. 왠기 긴 이야기가 될 것 같군요. 이 이야기를 하기 전에 다음과 같은 상황을 한 번 살펴봅시다. "한국사의 이해"를 가르치는 교수님은 언제나 수업의 첫 시간에 수업방식을 설명하면서 이번 학기내로 수행해야 할 숙제를 한 가지를 내 주셨다. 그 내용인 즉슨, 요구 조건 : "담양에 있는 온달산성에 갔다와서 수구(水口) 앞에 서 있는 자신의 사진을 찍어오라. 그리고 가능하다면 기행문을 작성하여 제출하라." 채점 방식 : 사진을 찍어오지 않는 사람은 본 과목을 통과하지 못할 것이고, 기행문을 작성하여 제출한 사람은 성적상에 약간의 이득이 있을 수 있다는 것도 함께. 매 학기, 대부분의 학생들은 이 숙제에서 제한하고 있는 최소한의 요구조건인 사진만을 찍어 제출했고, 몇몇 학생은 기행문까지 제출하였다. 또 어떤 학생은 엉뚱한 온달산성의 풍경사진만을 제출하기도하였다. 이제 채점이 어떻게 이루어지는지 살펴보면, 어떤 학기에는 최소조건을 만족한 사람은 B, 여기에 기행문 제출자는 A, 엉뚱한 사진을 제출한 사람은 F였고 또 어떤 학기에는 최소조건을 만족한 사람도 A 기행문 제출자도 A, 엉뚱한 사진을 제출한 사람만 F였다. 자, 위 채점 성적 결과에서 처음의 요구조건과 채점방식에 어긋나는 점이 있나요? 채점 결과는 서로 다르지만, 그렇다고 해서 교수님이 처음 말씀하셨던 방식에 어긋난 것은 아닙니다. 상황논리에 따라 다소 차이가 나는 채점을 하였지만. 갑자기 이 얘기를 왜 꺼냈느냐하면, 바로 이것이 표준과 표준의 구현의 차이를 명확히 나타낸 것이기 때문입니다. 즉, C++ 언어의 표준에서는 문법과 요구조건 등을 명시하기는 합니다만, 구체적인 구현 사안들에게 까지 조건들을 제시하지는 않습니다. 이는 구체적인 사안들이 다양한 환경에서 어떻게 다를 수 있는지 예측할 수 없다는 점과 함께 구체적인 사인의 제시와 함께 그 만큼 범용성, 이식성의 가능성이 더 줄어들기 때문이기도 합니다. 예를 들어 현재 관련 사항와 관련하여 다음의 문장이 있습니다. ISO C++98 5.6p4 [...] If the second operand of / or % is zero the behavior is undefined; [...] 즉, 나눗셈 혹은 나머지 연산의 두 번째 피연산자가 zero로 평가될 수 있는 값일 때 행동이 정의되지 않음을 나타냅니다. 이제 정의되지 않는다 함을 살펴봅시다. ISO C++98 1.3.12 undefined behavior behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. 즉, 행동의 결과를 예측할 수 없는 "표준이 강제하지 않거나, 혹은 [비정의 행동]으로 명시한 행동"을 뜻합니다. 이제 표준을 실제로 구현하는 g++, gcc, vc++과 같은 컴파일러에선 설계시에 나눗셈의 두 번째 피연산자 평가를 어떻게 해야 할까요? 구현체1 - 두 번째 피연산자를 검사하여 표준예외 클래스 invalid_argument 를 던짐 구현체2 - 두 번째 피연산자를 검사하여 구현체가 제공하는 클래스 division_by_zero 를 던짐 구현체3 - 두 번째 피연산자를 검사하여, 0으로 평가될 수 있는 값이 오면 "core dump"를 내고 프로그램 사망 구현체4 - 두 번째 피연산자를 검사하여, 0으로 평가될 수 있는 값이 오면 reset. 구현체5 - 비정의된 행동의 모든 결과로 프로그램 실행 중지. 구현체6 - 아무것도 검사하지않고, 계속 실행. 결과는 물리적 실행 결과에 의존적. ... 위 구현체들은 모두 표준이 요구하는 조건을 잘 만족시켜주는 "올바른 구현체(Conforming Implementation)"입니다. 즉, 구현체 개개인은 표준이 정의한 사항들을 최소한 만족하되, 구현체가 얼마나 똑똑한가, 얼마나 성의를 보이는가, 무엇을 목적으로 하는가 등의 조건에 따라 표준에서 정의하지 않은 사안들 혹은 선택가능한 사안들, 행동결과를 보장하지 않는 사안들에 대한 행동 결과가 달라지게 되는 것입니다. 여기서 중요한 점은, 이러한 세세한 사안들이 모든 서로 다른 C++ 구현체마다 다르게 나타날 수 있는 행동을 보인다는 것입니다. 지금의 division by zero와 같은 경우가 바로 이 경우죠. vc++의 구현체는 예외를 던져주고 계속 실행이 보장되는 반면 g++은 코어 덤프를 내고 실행이 중단되는 극단적인 상황을 불러오게 됩니다. 이 외에도 std::rand의 되돌림 값인 의사난수의 질, 동일한 시퀀스 포인트를 갖는 수식에서 두 번 이상의 개체 수정, 주 환경(hosted environments)에서의 void main의 사용 등 이와 같은 예는 수도 없이 많이 있습니다. 이런 사안들에 잘 대처하는 방법은 바로 이런 상황에 의존적이지 않은 프로그램을 작성하거나, 혹은 특정한 구현체가 잘 정의하고 있는 행동에 따라 이식성이 보장되지 않는 프로그램을 작성하는 방법 밖에는 없습니다. 이식성이 보장되어야 하는 프로그램이라면 반드시 피해가야하거나 이식성이 보장되는 구현체의 제한이 필요합니다. (지금은 사실 "정의되지 않는 행동"과 "명시되지 않는 행동", "지역적인 행동" 등의 구분도 없고, 구현체도 실행환경 번역환경 등의 구분도 없이 글을 작성하였습니다만, 이와 같은 세세한 구분에 따라 행동양식은 서로 커다란 차이가 있습니다.) 이제 예외처리에 대해 살펴봅시다. 예외처리는 ingree님께서 상상하시는 것과 같이 모든 예외적인 상황에 정의된 예외를 던져주는 기술이 아닙니다. 예외처리를 위한 try...catch 기술은 단지 프로그램의 어느 실행시점에서 제어권과 정보를 다른 실행지점으로 전달해 주는 것이고, 이를 이용해 예외적인 상황에 보다 명확하고 직관적으로 대처할 수 있게 해 주는 것입니다. 표준 라이브러리에 있는 대부분의 클래스, 함수들은 예외적인 상황이 발생했을 때 던져주어야 할 예외까지 정의하고 있으므로, 우리는 단지 어떤 상황에서 예외를 기대할 수 있는 것입니다. 즉, 모든 수식과 예측 불가능한 상황에 대해 예외를 기대할 수 있는 것이 아닙니다. 제가 처음에 얘기했듯이, 해결책은 두 번째 피연산자를 직접 검사하는 방법밖에 없습니다. :) -- S Kim |