기글 하드웨어 질문 게시판
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110zzz 10zzxxxx 10xxxxxx 10xxxxxx
UTF-8을 보면 아스키 코드와 혼동되지 않게 위해 저런식으로 한다는데 어차피 아스키 코드 맨 앞자리가 0인이상
그냥 시작코드가 1이면 2바이틀 읽고 10이면3바이트를 읽는 식으로 하면 되지 않나요? 굳이 왜 110,1110으로 하고 뒤에 이어지는 바이트들의 앞에 10을 붙이는 걸까요?
컴퓨터는..
0xxxxxxx
1xxxxxxx xxxxxxxx
10xxxxxx xxxxxxxx xxxxxxxx
11xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
..로 하면 혼동을 한다는 소린데 글자처리를 할때 '앞숫자가 1이면 2바이트로 끊고 10이면 3바이트로 끊어서 처리해라'가 사람인 제 눈으로 보기엔 오히려 더 알아보기 쉽고 효율적인데 도대체 무엇때문에 컴퓨터는 이것을 혼동한다는 건가요?
한글조합형 코드도 1xxxxxxx xxxxxxxx 이런식으로 앞 비트를 1로해서 2바이트씩 한글을 처리하는 방식인데 단점 중 하나가 2번째 바이트 첫자리에 0이 올 경우 아스키 코드랑 헷갈릴 수 있다고..
그런데 제가 한글97을 잘만 사용했었습니다. 그 단점이라는 것이 일종의 버그로 초창기에나 있었을 수 있던일이 아닐까 생각했는데 그래서 그걸 아는 사람이 단점으로 적어놓은 거라고요. 그런데 한참 뒤에 나온 UTF-8이 저런식으로 처리하는 것을 보면 진짜 컴퓨터가 혼동할 수도 있다는 건데 어차피 글자를 쓰면 여기서부터 여기까지는 글자다 라고 선언을 하지 않습니까. 따로 구분을 하는데 다른것과 뒤섞이는 것도 아니고. 1,2바이트를 섞어서 가져온다고 하더라도 어차피 아스키가 먼저오든 2바이트 코드가 먼저오든 첫자리숫자보고 판단하면 되는 거고
컴퓨터에서 데이터가 들어올때 8+8 8 8+/8 8 8+8/8 8... 뭐 이렇게 중간에 끊어진다해도 앞에 1붙었으니 그 다음에 오는 데이터 가지고 합쳐서 처리하면 될 것 같은데 말이죠.
한글97은 조합형으로 에러없이 잘만 사용되었는데 도대체 아스키랑 혼동될 수 있다는 것은 우리나라 얘기가 아니라 다른 나라 사람들이 그렇게 읽을 수 있다는 걸까요. 당시에는 각자 나라마다 인코딩도 종류별로 있었으니까요. 그나라 사람들 컴퓨터는 한글조합형에 대해 모르니 1xxxxxxx xxxxxxxx 의 첫번째 바이트는 자기나라가 쓰는 아스키 확장으로 읽고 2번째 바이트는 아스키코드나, 아스키 확장으로 읽는다. 그래서 혼동될 수 있다는 것은 애초에 한글조합형을 구현할 수 없는 컴퓨터의 얘기니 단점일 수 없다???????\
UTF-8이 이런 성질을 가지도록 설계한 까닭은 어떤 경우에도 한 문자에 대한 바이트 표현이 다른 문자에 대한 바이트 표현의 일부가 되는 경우가 없도록 하기 위함이다. 따라서 텍스트 안에 들어 있는 다른 텍스트를 찾는 데 쓰이는 바이트 단위의 부문자열 매칭이 UTF-8 문자열에서도 쓰일 수 있다. 통합 완성형, 상용 조합형, Shift JIS, Big5과 같이 ISO 2022 체계에 부합하지 않는 이전의 가변 길이 인코딩은 그런 성질을 지니지 않았고 (기존 인코딩 가운데 EUC-KR이나 EUC-JP 등은 이 점에서는 UTF-8과 비슷한 성질을 지닌다.), 더 복잡한 알고리즘을 써야 했다. 왜냐하면, 이들 인코딩은 ASCII 영역의 글자를 나타내는 바이트를 2바이트로 나타내는 다른 글자의 두 번째 바이트로 사용하기 때문이다. 또한, UTF-8에서 하나 이상의 바이트들이 손실되었을 때도, 다음의 정상 문자를 찾아서 동기화할 수 있기 때문에 피해를 줄일 수 있다.
그 설계 때문에 어떤 바이트들이 올바른 UTF-8로 확인되면, 그 문자열이 실제로 UTF-8로 인코딩되었을 가능성이 매우 높다. 임의의 바이트들이 순수한 ASCII 인코딩이 아닌 UTF-8 문자열일 가능성은 2바이트 문자의 경우 1/32, 3바이트 문자의 경우 5/256으로 매우 낮다. 또한 ISO-8859-1과 같은 기존의 인코딩으로 표현된 자연어 문자열이나 문서를 UTF-8로 표현된 것으로 오인할 가능성도 매우 낮다.
위키피디아의 내용인데 아스키 영역의 글자를 아스키 영역으로 사용한다는 1xxxxxxx은 7비트 아스키 코드의 영역이 아닌데 왜 10xxxxxx로 한걸까요?
그리고 문자열 문자열 매칭의 경우 확실히 1xxxxxxx xxxxxxxx로 시작하는 글자의 경우 쉽게 찾을 수 있겠지만찾으려는 글자가 아스키코드0xxxxxxx일 경우 두번째 바이트와 중복될 수 있어서 분명 더 복.잡.한 알고리즘이 필요할 것입니다.
그런데 저 써야 했다!!라는 것을 보니 되긴 된다는 것이고 도대체 그놈의 알고리즘이 얼마나 복잡해 컴퓨터에 부담을 준다는 건지 모르겠네요. 90년대 일반컴퓨터는 그랬다는 것인지. 일단 아래아한글 잘 사용했으니 별 문제는 없을 것 같다는 생각이 드네요. 그리고 기존 아스키 확장의 경우도 그냥 인코딩 방식 구분도 제대로 안하고 막 쓰다보니 생기는 문제같고요.
그리고 10xxxxxx 잘만 사용하는데 2바이트 첫번째 바이트를 왜 110xxxxx으로 했는지 궁금하네요.
10xxxxxx 10xxxxxx 하면 또 이건 이거대로 헷갈릴수 있다는 건가요?
----
쓰다보니 많이 길어졌습니다.
1xxxxxxx xxxxxxxx 만 된다면 utf-8에서 현대에 사용한는 한중일 문자를 2바이트로 내로 전부 집어넣는게 가능할 것 같은데 왜 저런 방식을 사용해서 3바이트를 사용하게 하는게 궁금해서 질문드렸습니다. 이제는 이미지나 영상에 비하면 별거 아니지만 글자들에 한해서는 평균적으로 약 1.5배로 뻥튀기 된다는 소리기도 하니까요. 개인적으로 동양권을 기준을로 생각했을때는 그냥 utf-16으로 넘어갔으면 좋겠네요.
프로그래밍도 유니코드로 많이 한다는데 영어는 아스키코드랑 utf-8쓰면 1바이트씩 소모하니 2바이트 소모하는 유니코드보다 얼마나 정확히 빠를지는 모르겠지만 어는 정도 수준인지는 몰라도 차이가 있기는 있을 것 같네요. 영어를 주로 잔뜩 많이 엄청 사용하기도 하니까요.
단, 이러한 구조를 채택한다 하더라도 첫 바이트가 꼭 110xxxxx 1110xxxx 11110zzz 이런 식으로 앞의 1이 하나씩 꼭 증가해야 하는 것은 아닌데 (예를 들어 11로 시작하고 뒤에 두 바이트를 문자열 길이를 표시하도록 구현할 수 있습니다. 예를 들어 1100이 2바이트, 1101이 3바이트, 1110이 4바이트 이런 식..) 이건 utf8를 깊게 파고 들어간 적이 없어 확신하고 답할수는 없으나 일종의 무손실 압축 기법으로 보입니다. 허프만 코딩과 비슷한 개념요. 발생빈도가 상대적으로 높은 문자들을 되도록 2바이트에 배치하기 위해 가능한한 2바이트에 저장할 수 있는 공간을 키우기 위해서라고 볼 수 있겠습니다.
사실 utf8자체가 일종의 무손실 압축 방식으로 보실 수 있습니다. 가장 흔히 쓰는 아스키 캐릭터에 1바이트를 배당하고 덜 쓰는 문자일 수록 더 높은 바이트를 배당해 모든 언어를 다 표현할 수 있으면서도 동시에 용량이 적게 나오도록 최적화한 것이지요.. 단, 이것은 해당 글자의 사용 통계에 근거를 두고 있어야 하기 때문에 전세계적으로는 맞는 결과일 수 있어도 비영어권 국가 - 특히 CJK쪽에선 경우에 따라서는 효율이 떨어질 수도 있습니다. 이것도 꼭 그런 것은 아니고 html같은 경우 영어가 많기 때문에 오히려 생각보다 용량에 차이가 없거나 용량 효율이 좋을 수도 있습니다만.. 예를 들어 워드프로세서가 지원하는 문서 포맷들도 상당수가 까보면 xml기반이라 한글보다 아스키문자가 더 많이 나옵니다. 순수 한글 문자 위주로 작성된 파일이 아니면 생각보다 오히려 기본 16비트부터 시작하는 인코딩보다 utf8의 효율이 좋을 수가 있습니다. 뭐 대부분의 요즘 문서파일의 경우 여기서 한번 더 압축을 거치니 좀 더 계산이 까다롭기는 합니다만..
참고로 이건 중요한 것은 아니지만 utf8가 아닌 본디 8비트 아스키에서는 첫 비트는 패리티 비트로 쓸 수 있습니다. (=8비트 ascii에서 무조건 첫 비트가 0이라는 보장은 없습니다. 뭐 일반적으로 요즘 컴퓨터에서 쓰는 프로그램들이 그런 걸 구현하지는 않습니다만.. 물론 utf8에선 그러면 안 됩니다.)
0xxxxxxx
10xxxxxx 1xxxxxxx
110xxxxx 1xxxxxxx 1xxxxxxx
..이런식으로 구분해도 되었을 텐데 아스키확장 말고는 겹칠것도 없는데 10xxxxxx을 쓴걸까요? 1xxxxxxx을 사용하면 혼동이 올 이유가 있는 건가요? 그리고 2바이트 시작도 왜 10xxxxxx이나 11xxxxx이 아닌 110xxxxx으로 했는지 잘 이해가 되지 않네요.
구분이 문제라면 더 적은 바이트에서 많은 양을 담을 수 있게 위에 식대로만 해도 몇배로 늘어날 것 같은데 말이죠.
유니코드 8.0 기준으로 기본 평면은 0x0000~0xFFFF의 16비트 범위를 사용합니다만, 보조 평면까지 포함하면 0x10FFFF까지 총 21비트를 사용하게 됩니다.
현재 유니코드 시스템은 UTF-8 인코딩으로 4바이트를 사용하면 21비트를 모두 표현할 수 있습니다.
UTF-8의 멀티바이트 시퀀스가 어째서 110xxxxx 10xxxxxx 식으로 구성되냐면 prefix code를 만들기 위함입니다.
이를테면, 모든 UTF-8 문자열의 바이트는 {0, 10, 110, 1110, 11110}의 5개 중 하나를 코드로 갖게 됩니다.
반면 본문에서 제안한 방식은 prefix code를 만족할 수 없습니다. 예를 들어보죠.
0xxxxxxx
1?xxxxxx xxxxxxxx
10xxxxxx xxxxxxxx xxxxxxxx
11xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
여기에서 코드를 뽑아보면 다음과 같습니다. {0, 1, 10, 11}
여기에서 1은 다른 코드인 10, 11의 접두사(prefix)가 됩니다.
따라서, 1로 시작하는 멀티바이트 시퀀스는, 해당 코드는 한 번에 해석할 수 없으며, 4바이트를 모두 읽어들여서 추론하는 수밖에 없습니다.
당연하지만 문자열 분석이 더욱 어려워지게 되는 것입니다.
또한 멀티바이트 시퀀스의 후속 바이트의 접두사를 10으로 고정함으로써, 멀티바이트 시퀀스의 동기가 깨졌을 때에도 손쉽게 재동기화가 가능하게 됩니다.
예를 들어, 네트워크 오류로 일부 바이트가 손실되어 다음과 같은 UTF-8 시퀀스가 들어왔다고 합시다.
10xxxxxx 10xxxxxx 110xxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx
이 경우 처음 2바이트는 별 쓸모없는 데이터임을 바로 알 수 있습니다. 시작바이트 없는 멀티바이트이기 때문입니다.
따라서 이를 무시하고, 3번째 바이트부터 정상적으로 해석을 시작하면 기껏해야 1개 문자열만 파손되고 나머지 글자는 살릴 수 있습니다.
반면 본문에서 제안한 방식을 예로 들어보면...
1xxxxxxx 1xxxxxxx 0xxxxxxx 0xxxxxxx 1xxxxxxx
몇 바이트가 손실되었는지 모르는 상황에서, 이와 같은 시퀀스는 어디에서 재동기화를 하면 좋을까요.
첫 번째 바이트? 두 번째 바이트? 최악의 경우 네 번째 바이트부터 재동기화 해야 할 수도 있습니다.
역시 일단 무차별적으로 해석해보고, 글자가 정상적으로 복원되는 케이스를 선택해야만 합니다. 이 경우 7바이트를 모두 해석해서 추론해야 합니다.
즉, UTF-8 인코딩은 비트를 우겨넣는 것 외에도, 문자열 해석에 있어서 용이함, 오류내성까지 고려하여 설계된 것입니다.
그런데..
0xxxxxxx
10xxxxxx 1xxxxxxx
110xxxxx 1xxxxxxx 1xxxxxxx
1110xxxx 1xxxxxxx 1xxxxxxx 1xxxxxxx
..도 가능하지 않나요? 이렇게 해도 뒤에 몇 바이트가 남았는지 또는 앞에 필요한 바이트가 있다는 사실을 알 수 있을 텐데요.
아스키 확장과도 관련이 있는걸까요? 유니코드니 완전 별개라고는 생각하는데 왜 굳이 2바이트 코드의 시작을 110xxxxx부터 햇ㄴ는지 모르겠네요. 단순 구분 문제라면 말이죠.
1111xxxx 10xxxxxx 10xxxxxx.
그리고 111xxxxx 10xxxxxx 10xxxxxx를 생각해 봅시다. 111xxxxx의 네번째 비트를 1로 줘봅시다.
1111xxxx 10xxxxxx 10xxxxxx. 같습니다.
이 경우 그래도 신뢰도가 꽤 개선되었지만 (=여러 글자를 깨먹을 가능성은 많이 줄어들었고 좀 더 현실적이 되었지만) 첫 바이트만으로 문자의 바이트 길이를 정확히 예측할 수 없기 때문에 utf-8에 비해선 여전히 신뢰도나 처리 효율성이 떨어집니다. 그리고 utf8대비 1비트밖에 차이나지 않아 저장효율 차이도 많이 줄어버렸고요.
0xxxxxxx
10xxxxxx 11xxxxxx
101xxxxx 11xxxxxx 11xxxxxx
1001xxxx 11xxxxxx 11xxxxxx 11xxxxxx
이것도 그런가요?
1001xxxx 11xxxxxx 11xxxxxx / 11xxxxxx...어?
뭐든 손으로 그려봐야? 쉽게 알수있어서 하다보니 이것도 10xxxxxx 11xxxxxx이 1001xxxx 11xxxxxx이 되어버리면 제대로 구분을 할 수가 없어지네요.
UTF-8이 정말 최선을 다한거였군요. 답변 정말 감사합니다. 오늘 많이 알아가네요^^
C/C++ 같이 고전적 legacy 를 가지는 언어들은 문자를 byte 배열로 다뤄버리는 경우가 많아서 완전히 예외적인 면모가 많고, Java, C# 수준에서는 UTF-16 이 기본이죠. Python 은 UTF-32 를 쓰는 것으로 알고 있습니다. UTF-8 을 쓰는 것은 stream 으로 다뤄지는 입출력이 대부분이죠.
그런데 '한글' 이 3 bytes 조합형을 쓴다고 오래 전에 듣기는 했습니다만 그게 특별히 우수한지는 잘 모르겠네요. 혹시 관련자료가 있으면 link 부탁드립니다. 그래봐야 국제화시대에서 대세인 Unicode, UTF 에서 많이 벗어나는 것은 왠만하면 받아들이기 어렵겠습니다만...