-
문자열 처리 - 한글 개념프로그래밍 언어 C, C++ 2021. 1. 22. 01:52728x90
jamesbbun.tistory.com/10 에서 이미 인코딩에 대해 다룬 내용이다.
요즘 게임 개발하는데, utf-8이 인코딩이 공통으로 사용되고 있어서 별로 깊게 다룰일이 없다.
개발 노트를 뒤지다가 발견했다. 피쳐폰 개발할 적이었던거 같은데, 워낙에 옛날 것이라서...
기억이 가물가물.. 자료들 출처도 정확히 모르겠고, 그래도 이런 내용이 요즘은 잘 없으니,
상식에 좋겠다는 판단하에 공유하니 한번 읽어보길 바란다.
문자열을 다루는 작업 중에서 가장 불편하고 시간 소모가 많이 드는 일이, 적절한 한글 코드 변환이다. 한글 코드는 표준으로 정해진 하나의 코드만 사용되는 것이 아니라, 여러 코드들이 각기 사용되기 때문에 데이터 입출력에 한 코드에서 다른 코드로의 변환 작업은 아주 빈번히 일어난다. 이 글에서는 한글 코드 각각에 대해서 설명하는데, 그 순서는 개발된 시간 순이다.
1. 조합형 한글 코드
맨처음 컴퓨터가 한국에 보편적으로 도입되던 시기(순전히 주관적인 판단)에 조합형 코드라는 한글 코드가 개발되었다. 조합형 코드는 다음과 같다.맨처음 컴퓨터가 한국에 보편적으로 도입되던 시기(순전히 주관적인 판단)에 조합형 코드라는 한글 코드가 개발되었다. 조합형 코드는 다음과 같다.
Bit Code 10진 코드 16진 코드 초성 중성 종성 00000 0 0x00 00001 1 0x01 채움 채움 00010 2 0x02 ㄱ 채움 ㄱ 00011 3 0x03 ㄲ ㅏ ㄲ 00100 4 0x04 ㄴ ㅐ ㄳ 00101 5 0x05 ㄷ ㅑ ㄴ 00110 6 0x06 ㄸ ㅒ ㄵ 00111 7 0x07 ㄹ ㅓ ㄶ 01000 8 0x08 ㅁ ㄷ 01001 9 0x09 ㅂ ㄹ 01010 10 0x0A ㅃ ㅔ ㄺ 01011 11 0x0B ㅅ ㅕ ㄻ 01100 12 0x0C ㅆ ㅖ ㄼ 01101 13 0x0D ㅇ ㅗ ㄽ 01110 14 0x0E ㅈ ㅘ ㄾ 01111 15 0x0F ㅉ ㅙ ㄿ 10000 16 0x10 ㅊ ㅀ 10001 17 0x11 ㅋ ㅁ 10010 18 0x12 ㅌ ㅚ 10011 19 0x13 ㅍ ㅛ ㅂ 10100 20 0x14 ㅎ ㅜ ㅄ 10101 21 0x15 ㅝ ㅅ 10110 22 0x16 ㅞ ㅆ 10111 23 0x17 ㅟ ㅇ 11000 24 0x18 ㅈ 11001 25 0x19 ㅊ 11010 26 0x1A ㅠ ㅋ 11011 27 0x1B ㅡ ㅌ 11100 28 0x1C ㅢ ㅍ 11101 29 0x1D l ㅎ 11110 30 0x1E 11111 31 0x1F 이 코드는 각각의 음소를 결합해 한 글자를 만드는 방식 그대로를 이용한 것으로 초성, 중성, 종성 결합으로 만들 수 있는 모든 문자에 대해 표현 가능하다. 보다 초기에 이런 조합형 방식의 코드로 N byte, 3 byte 방식들이 존재했었다. 그 후 보다 개선된 방식으로 2Byte안에 모두 표현할 수 있는 코드가 바로 위 테이블의 코드이다.
다음 표는 2byte로 표현되어지는 한 글자가 조합형에서 실제 어떻게 구성되는 지를 표현한다.
1bit 5bit 5bit 5bit 0=영어, 1=한글 초성 중성 종성 그렇다면 위 Table을 통해 '짱'을 나타내는 2byte를 만들 수 있을것이다.
1bit 5bit 5bit 5bit 1 01111 00011 10111 즉, 1011110001110111이라는 2byte bit streams이 짱을 표현하게 되는 것이다.
2. euc-kr(완성형 한글 코드)
위 조합형 코드는 실제 OS에서 쓰이지는 않는다. 그 이유가 무엇일까?
확실한 이유는 아니지만, 추측해 보면 영문자를 표현하는 아스키와 비교해서 한글의 코드셋도 각 글자에 대해서 Table을 만들어 표현하는 것이 일관성이 있어 보였기 때문이 아닐까 한다. 즉, '가', '나', '다' 와 같은 한글에 대해서 0x11, 0x12, 0x13처럼 고정된 값을 매핑하면 아스키 코드로 a-z, A-Z를 표현하는 방식과 유사해진다.
하지만 한글에서 쓰일 수 있는 글자수는 알파벳보다 훨씬 훨씬 많다. 맨 위 표에서 볼 수 있듯이 한글은 초성개수 19개 중성개수 21개 종성개수 27개인데, 종성이 없는 경우까지 감안하면 표현 가능한 글자는 19*21*28=11172개나 된다. 아스키 코드는 겨우 256개의 data 공간을 가지는데, 한글을 여기에 다 넣기는 어림 반푼어치 없다. 그래서 한글과 같이 데이터 공간을 많이 필요로 하는 비영어권 언어를 표현하기 위해 확장 아스키 코드가 사용된다.(이 부분은 아스키 코드 관련 글을 참조) 이런 확장 아스키 코드를 이용해 한글을 표현한 코드가 흔히 알고 있는 KS 완성형 한글 코드이다. 이것은 euc-kr로도 불려지고, KSC5601이라고도 한다. 이 코드셋에는 11172개의 모든 글자를 다 코드화 하진 않았다. 자주 쓰이는 2350개의 한글에 대해서만 정의가 되어 있다.
"이 코드는 bit stream으로 어떤 모습일까요?" 라는 문자열을 KS 완성형 한글 코드로 바꾼다면 어떤 모습일까? 이 글자를 조합형으로 표현하는 것은 어렵지 않지만, KS 완성형 한글 코드로 바꾸는 것은 대단히 어렵다. 위 문자열을 KS 완성형 한글 코드로 바꾸기 위해서는 각각의 글자에 대해서 KS 완성형 한글 코드표를 보고 변환해야 하는데, 이 코드표에는 각 글자간의 어떠한 규칙도 없기 때문이다. 이 완성형 한글 코드나 확장형 한글 코드에서도 한글은 2byte로 표현된다.(잘 모른다면 ASCII Code를 설명한 글로 되돌아가 자세히 살펴볼 필요가 있다.) 한글자를 표현하기 위해서 앞 1byte는 확장형 ASCII CODE TABLE에 따라서, 그리고 그 뒤 1byte는 앞 1byte값에 따른 확장 코드 영역에 따라 정의 된다.
위 내용을 간단히 정리하면 euc-kr은 다음과 같이 구성되어 있다고 볼 수 있다.
상위 1BYTE 하위1BYTE 176-200(0xB0-0xC8) 161-254(0xA1-0xFE) 3. cp949(확장형 완성형 한글 코드)
그렇다면, 2350개의 글자에 속하지 않은 글자를 키보드로 적어 넣으면 어떻게 될까? 이 코드셋을 사용하는 어플리케이션에서는 당연히 깨져서 나온다. 초창기 인터넷 사용자라면 채팅할 때 오타 '끾'과 같은 글자는 깨져서 보여지던 것을 기억할 수 있을 것이다. 현재 인터넷에는 여러 단어의 축약형, 파생형들이 사용되는데 '햏'과 같은 것도 일종의 그런 종류이다. 이런 종류의 글자가 많아지면서 2350개의 글자로는 사용자의 요구를 충족시킬 수 없기 때문에 확장형 완성형 한글 코드가 만들어지게 되었다. 이것이 바로 CP949인데, 따라서 euc-kr은 cp949의 서브셋이라고 말할 수 있다. 그러나 cp949는 사실 ASCII Code의 원칙을 위배한 것인데, 그 이유는 ASCII Code에서 언어를 표현한 공간으로 쓰일 수 없는 영역에까지 언어 기술을 위해 사용하기 때문이다.(국제 표준 협회는 ASCII CODE에서 0-31의 제어 문자 영역과 128-159의 영역은 문자 코드로 사용하지 못하게 하였다. 그러나 CP949는 128-159 영역에 문자를 사용한다. KS 완성형 한글 코드는 이 영역을 제외한 176-200(0xB0-0xC8)까지의 공간을 사용한다.)
4. 유니코드
새로운 유니코드는 기존 코드 체계의 문제를 극복하기 위해 제안되었다. 기존 코드 체계는 어떤 문제가 있었을까?
기존 코드 체계라면 쉽게 생각할 수 있는 cp949를 예를 들어 설명해보자. cp949가 확장형 아스키 코드를 활용해 한글의 코드 셋을 표현한 것이라는 것을 상기해본다면 중국어와 일본어도 유사한 방식으로 표현되는 것을 알 수 있다.(ASCII Code를 설명한 글에서 자세히 설명) 그렇다면 독일어나 프랑스어 스페인어는 어떻게 표현되어 있을까? 우리가 본 ASCII Code 영역에는 위 다른 언어들에 대한 언급을 볼 수 없다. 그 이유는 아시아권 언어만을 고려해서 만들어진 코드 셋이기 때문이다. 그러면 독일어나 프랑스어와 같은 비아시아권 언어들은 따로 독자 코드셋을 가지고 있어야 하는데, 이런 여러 코드셋은 각 언어간의 호환을 보장하지 못하거나 어렵게 만드는 원인이 된다. 이런 문제를 해결하기 위해 유니코드가 제안되었다.
즉, 유니코드는 전세계의 모든 언어의 문자들에 대해서 호환성을 고려한 일관된 표현을 하도록 제안된 것이다. 유니코드는 2byte 문자체계라고 생각하기 쉬운데, 틀린 말이다. 유니코드는 여러가지 인코딩 방식을 가지고 있다.(UTF8, UTF16, UTF16-BE, UTF16-LE, UCS2, UCS4 등등) 이 중에서 2byte 문자체계의 인코딩 방식은 UCS2와 UTF16이다. 이런 다양한 인코딩 방식이 존재하는 이유는 표현의 제약성 때문이다. 2byte 문자체계로 세계 모든 언어를 다 표현할 수 없기 때문이다.(여기에는 모든 기호들, 고어들 포함된다.) 그래서 더 많은 메모리 공간이 필요하였고, UCS4와 같은 4byte 문자체계 인코딩 방식이 생겨난다. 그러나 고정길이 4byte 방식은 모든 언어를 다 표현할 만큼 큰 공간을 가지고 있으며 고정된 길이이기 때문에 편리한 디코딩을 지원하지만, redudancy가 너무나 큰 비효율적 문자 체계라고 할 수 있다. 그래서 모든 언어를 표현할 수 있으면서 최대한 효율적인 code를 만들려고 노력했는데, 그 결과가 UTF-8이다.
가변길이 인코딩 방식인 UTF-8은 효율성이 아주 좋지만, 언제나 그렇듯 디코딩에서는 고정길이 방식에 비해 더 많은 수고를 해야 한다.
유니코드에는 여러 인코딩 방식이 있기 때문에 각각의 인코딩 방식에 맞게 디코딩을 해줘야만 제대로 문자열을 읽어올 수 있다. 유니코드의 맵은 너무 커서 제대로 살펴보기 어렵지만 개략적인 정리는 다음과 같다.
기본 2byte영역에 들어가는 부분이 기본 다국어 평면 영역이다.(이 코드 영역은 Window운영체제에서는 보조프로그램의 시스템도구의 문자표에서 확인할 수 있다) 이외 보조 다국어 평면이나 보조 상형 문자 평면이나 보조 특수 목적 평면과 같은 데이터 영역은 기본 다국어 평면에 다 담을 수 없는 기타 글자들을 위한 공간이다. 이 코드표를 활용해 문자를 표현하면 된다. 한글 영역과 기본적인 한자는 기본 다국어 평면에 다 들어가 있다. 보다 자세한 내용은 위키피디아나 유니코드 홈페이지에서 볼 수 있는데, 일반적으로 사용하는 경우 기본 다국어 평면 만을 대상으로 작업한다고 생각해도 무리가 없다.(없길 바라는게 정신 건강에 좋을 수도... 어려운건 아니지만 복잡한건 누구나 싫어하니까...)
글이 길어져서 급 마무리 하려고 하니, 여타의 필요 없는 부분은 모두 생략하고 필요한 부분만 언급한다. 아스키 코드 영역은 0x0000-0x00FF에 기술되어 있다. 한글이 정의되어 있는 영역은 다음 그림을 보면 쉽게 알수 있다.
위 그림을 보면 알 수 있듯이 한글은 다음 식에 의해 계산될 수 있다.
한글 코드값 = 0xAC00 + (초성값*21*28) + (중성값*28) + 종성값
초성, 중성, 종성의 개수가 각각 19개, 21개, 27개(종성이 없는 경우 포함하면 28)임을 생각하면 어떤 형태로 코드 값이 계산되는지 이해할 수 있다.
이것을 대표적인 인코딩 방식 UTF-8로 표현하려면 다음의 가변길이 코드 Map을 활용해야 한다.
이것을 보면 왜 UTF-8이 사실 효율적인 측면을 제외하고, 코드값을 계산하는 측면이나, 자소를 분리하는 측면 모두에서 확장형 한글 완성형 코드보다 뛰어날 것을 짐작할 수 있다.
한글의 코드 체계는 대단히 복잡해 보일 수 있는데, 사실 자세히 파고 들면 그렇게 어렵지 않다. 하지만 그 막연함을 자세히 파고 들지 않으면 문자열을 다룰때마다 애를 먹게 된다. 이 기회에 자세히 이해하고 넘어간다면 문자열을 처리하는 작업이 오히려 즐거운 일이 될 수 있을 것이다.
'프로그래밍 언어 C, C++' 카테고리의 다른 글
Aseprite 컴파일하기 (0) 2024.09.17 visual studio c/c++에서 보기 싫은 인라인 (0) 2022.12.14 랜덤함수 (0) 2021.01.17 정의 vs 선언 2 (0) 2021.01.16 정의 vs 선언 1 (0) 2021.01.16