. 삽입 기록

삽입 동작을 기록하는 함수는 URInsert 함수이며 Insert 함수가 이 함수를 호출한다. 레코드에 어떤 문자열이 어느 위치에 삽입되는가를 기록하는 간단한 일을 하지만 병합 처리와 취소 레코드 삭제를 해야 하기 때문에 다소 복잡하다. 일단 순서도를 그려본 후 코드를 작성하도록 하자.

다소 복잡해보이는데 이 순서도를 그대로 코드로 옮기면 다음과 같이 된다. 순서도가 이해되면 코드도 이해가 될 것이다. 순서도에 비해 코드량은 그리 많지 않다.

 

void CApiEdit::URInsert(int nPos, TCHAR *str)

{

     if (CanRedo()) {

          ClearRedo();

     } else {

          if (pUR[nowur].action==UR_INSERT) {

              if (pUR[nowur].pos + lstrlen(pUR[nowur].data)==nPos) {

                   AllocURData(nowur,lstrlen(pUR[nowur].data)+lstrlen(str)+1,5);

                   lstrcat(pUR[nowur].data,str);

 

                   if (str[0]==‘ ‘ || str[0]==‘\r’) {

                        NextRecord();

                   }

                   return;

              } else {

                   NextRecord();

               }

          } else {

              if (pUR[nowur].action != UR_NONE) {

                   NextRecord();

              }

          }

     }

 

     AllocURData(nowur,lstrlen(str)+8,0);

     pUR[nowur].action=UR_INSERT;

     pUR[nowur].pos=nPos;

     lstrcpy(pUR[nowur].data,str);

}

 

아래쪽에 있는 네 줄이 병합과 취소 레코드를 전혀 처리하지 않을 때의 코드이며 레코드에 삽입 동작을 기록하기만 하는 가장 간단한 경우이다. 병합이나 취소 레코드 삭제는 일단 고려하지 말고 이 네 줄의 동작부터 분석해보자. 순서도상의 굵은 화살표를 따라 실행되는 코드가 바로 이 네 줄의 코드이다.

기록하기 전에 AllocURData 함수를 호출하여 먼저 data에 메모리를 할당하는데 이때 할당되는 메모리는 통상 9~10바이트가 된다. 영문인 경우 영문 길이 1자에 여유분 8을 더해 9바이트가 할당되며 한글인 경우 한글 2바이트에 8을 더해 10바이트가 할당된다. 한글 문서를 보면 한 단어는 통상 4음절 이하로 구성되는 경우가 많고 뒤쪽의 공백과 널 종료문자까지 고려하면 10바이트로 할당하면 된다. 그래서 초기 할당시에 여유분 8을 주었다.

만약 이 값이 8보다 작다면 예를 들어 4정도 된다면 불과 한 음절밖에 기억을 못하므로 매번 재할당이 일어나 메모리 단편화가 심해질 것이다. 이 값이 또 16정도로 충분히 큰 값을 가지게 되면 매 레코드가 8음절을 기억할 수 있는 메모리를 할당하므로 이 또한 낭비가 심해 바람직하지 않다. 그래서 재할당은 최소화하면서도 메모리 낭비가 없는 최적의 값을 찾았는데 8 정도가 적당한 것 같고 메모리를 더 절약하려면 6도 괜찮은 값인 것 같다.

data에 초기 메모리를 할당한 후 actionUR_INSERT로 대입하여 삽입 동작임을 기록하고 pos에 삽입이 일어난 오프셋을 기록하였다. data 멤버에는 새로 삽입된 str 문자열이 복사됨으로써 삽입 레코드가 완성된다. 이후 계속 편집될 때 이 레코드는 이어지는 삽입 동작과 병합되거나 아니면 이 상태대로 남아 있게 된다.

재실행 가능한 상태일 때 취소 레코드를 삭제하는 처리는 아주 간단하다. 재실행 가능한 상태인가는 CanUndo 함수를 호출해보면 쉽게 알 수 있고 이 함수가 TRUE를 리턴하면 ClearRedo 함수를 호출하여 취소 레코드를 삭제하면 된다. ClearRedo 함수는 현재 레코드 이후의 모든 레코드를 삭제하여 빈 레코드로 만들어주며 이렇게 만들어진 새 레코드에 삽입 동작이 기록된다.

다음은 병합 처리를 보자. 병합이 이루어지기 위해서는 현재 레코드가 삽입 레코드여야 한다. 삽입 레코드가 아닌 경우는 다음 레코드에 또는 빈 레코드인 경우는 현재 레코드에 삽입 동작을 그냥 기록하면 된다. 삽입 레코드인 경우 현재 레코드의 삽입 위치와 새로 삽입되는 위치가 연속적인지를 점검한다. pUR[nowur].pos + lstrlen(pUR[nowur].data)==nPos 조건문은 현재 레코드의 삽입 위치에 문자열 길이를 더한 위치가 새로 삽입되는 위치와 같다는 뜻이며 즉 현재 레코드 바로 뒤에 삽입되는가를 검사한다. 이 조건이 만족되면 새로 삽입 레코드를 작성하지 않고 현재 레코드에 병합한다.

두 레코드는 같은 종류의 동작이며 위치가 연속적이므로 lstrcat 함수로 data와 새로 삽입되는 문자열을 연결하기만 하면 된다. 새로 추가되는 문자열로 인해 data의 버퍼가 부족할 수도 있으므로 AllocURData 함수를 호출하여 data 버퍼가 충분한 길이를 가지도록 한다. 이 함수는 data가 자신의 길이에 새로 추가된 문자열 str과 널 종료문자분을 더한 길이인지 보고 그보다 짧으면 여유분 5바이트를 포함하여 재할당하도록 한다. 현재 레코드는 삽입 레코드이지만 위치가 연속적이지 않으면 병합할 수 없으므로 다음 레코드로 이동하여 새 레코드에 삽입 동작을 기록하도록 한다.

레코드를 병합한 후 새로 입력된 문자열이 공백이나 개행코드인지 점검한다. 만약 공백이나 개행코드가 새로 추가되었다면 즉, 단어가 완성되었다면 현재 레코드는 더 이상 다른 레코드와 병합되지 않아야 하므로 다음 레코드로 미리 이동시켜 다음 번 삽입 때 병합되지 않도록 하였다.

이상으로 삽입 동작을 기록하는 URInsert 함수의 코드에 대해 분석해보았다. 메모리 동적 할당, 병합 처리, 취소 레코드 삭제 등등의 다소 복잡한 코드들이 있는데 이 함수의 동작을 자세히 관찰해보고 싶으면 기능을 모두 완성한 후 디버거의 도움을 받아 보기 바란다. 매 음절 입력시마다 이 함수가 어떻게 레코드를 관리하는지를 살펴 보면 시간이 좀 걸리더라도 어렵지 않게 이해가 될 것이다.