유니코드는 전세계의 문자를 표현하기 위한 시스템이다.

 

0과 1로만 작동하는 컴퓨터가 문자를 표현하기 위해서는 별도의 코드를 사용해야 한다.

 

-> 인코드 : 문자 -> 바이트로 변환

    (1바이트는 8비트, 8비트는 0과1이 들어갈 수 있는 자리수(digit)가 8개 놓여있다. 256까지 범위의 수를 사용)

 

-> 디코드 : 바이트 -> 문자로 변환

    (문자는 알파벳이나 유럽어, 한글, 한자, 일본어, 중국어 혹은 제어 문자, 이모지 등의 의미를 담고 있다)

 

컴퓨터의 코드 체계는 한 마디로 0과1에 의미를 부여하여 인간과 컴퓨터가 소통하는 수단이다.

한국사람은 한글을 알아들으니 한글을 보여주고, 컴퓨터는 한글을 모르고 0과 1로만 제어하니까 바이트 코드를 보내준다. (바이트 코드 -> CPU를 제어하는 복잡한 기계어의 일종)

 

코드 체계는 하나만 있는게 아니라 국가와 언어에 따라 여러 가지가 있다.

 

초창기에 사용되던 코드가 바로 아스키 코드다. 아스키 코드는 7 비트나 8비트를 사용해서 영어 알파벳과 기호 각종 제어문제를 사용하던 코드다. 여기까지가 위키피디아에서 제공하는 정보이다.

 

여기에 왜 한글이 없는지 생각해봤다면 유니코드를 이해할 수 있을 것이다.

 

 

영어 알파벳은 대문자 26개 소문자 26개를 합해도 52개 밖에 안된다. 반면 한글의 3성(초성 중성 종성)을 다 합하면 11,172개의 조합이 나온다. 그 중에서 우리가 사용하는 조합은 6000여개 정도 된다고 한다. 1바이트의 컴퓨터 메모리에는 256개를 표현할 수 있다. 알파벳은 52개 사용해도 많이 남는다. 7비트를 8비트로 늘린 확장 아스키 코드를 만들면 유럽의 알파벳들까지 충분히 포함이 된다. 초창기 컴퓨팅 환경은 영미권, 유럽권의 언어를 사용하기가 수월했다.

 

반면 한국, 중국, 일본 같은 나라는? 한자어를 쓰는 중국은 말할 것도 없고, 일본은 오십음도지만 상용한자 2000개를 사용하니 8비트에는 안들어간다. 한글도 조합도 그렇지만 여전히 한자도 사용하는 나라다. 이 한자어 문화권 삼국의 언어가 들어가기에는 256비트는 너무 작은 크기였다. 그래서 각자의 인코딩을 개발해서 사용했다.

 

언어와 인코딩
언어와 인코딩

 

한글 같은 경우 65536 개를 표현하는 16비트 범위라면 충분히 언어를 표현할 수 있다. 그래서 개발된 것이

 

EUC-KR와 CP 949가 있다. CP 949는 MS가 개발한 한글 인코딩으로 EUC-KR의 확장형이다.(한글이 더 많이 표현된다) 16비트 시절에 사용하는 코드지만 지금도 사용한다. 윈도우10에서도 명령프롬프트(옛 도스같은 터미널)에는 기본이 CP 949로 되어있다.

 

간혹 프로그래밍을 할 때 IDE나 컴파일러 설정에 따라 한글이 깨지는 경우가 있다면 보통 이것 때문이다. 많은 사람들이 한글 인코딩 때문에 고생하는 것도 무리는 아니다. 역사를 들여다보면 언어의 특성 때문이라는 것을 알 수 있다. 특히 우리나라는 초성 중성 종성 을 합치는 완성형과 조합형 두개가 있어서 더 복잡하다. 쉽게말해 한글로 코딩을 시작하는 순간 부터 인코딩이라는 문제를 다뤄야 한다는 것이다. 외국어를 배울 생각이 없다면 영어 사용자들은 전혀 그럴필요가 없다.

 

이런 내용을 제대로 설명해주는 곳은 찾기가 어려웠는데 그래도 지금은 인코딩 관련하여 구글에서 많이 검색이 되기 때문에 어느정도 찾으면 알 수 있다.

 

 

한글 코드를 유니코드로 변경
한글 코드를 유니코드로 변경

* 한글이 깨질 때 해결방법

한글이 깨지는 이유를 생각해보자.

 

코드가 달라서 그런 것이다. 코드(code)는 인코드와 디코드가 있다. 인코드 encode 는 코드로 만드는 과정이고,(암호화) 디코드decode 는 코드를 복구하는 과정이다(복호화) 두 과정에서 코드를 일치시켜야 사용할 수 있다.

 

예를 들어 윈도우 운영체제에서 메모장에 아래와 같이 파일을 만들어 보면, UTF-8 은 유니코드고 ANSI 는 CP 949이다.

 

명령프롬프트에서 실행을 시켜보면 영어는 깨지지 않는데 한쪽의 한글은 항상 깨지는 모습을 볼 수 있다. 컴퓨터가 고장났는지 의심할 필요는 전혀 없다. 정확히 각각의 코드 테이블에 있는 값으로 디코드 decode 한 것이다. (문자열의 decode는 곧 콘솔에 출력으로 보면 된다. 문자열의 역할은 문자를 보여주는 것이니까.)

 

ANSI 949 의 코드와 65001 (유니코드 UTF-8)의 코드가 가진 테이블이 다르기 때문이다. 하지만 영어 알파벳과 기본적인 기호 (+ - ! 등) 는 같다. 처음에 아스키 코드를 설계한 7비트에 있는 그대로 놔두었기 때문이다. 이 때문에 50년전에 아스키 코드로 작성한 프로그램이 호환성을 별로 따지지 않고 굴러갈수 있는 것이다.

 

알파벳은 컴퓨터 코드시스템에 사용되는 기호로 표준화되었기 때문에 불변성을 가진 것이다. 인류 역사상 어쩌다 보니 그렇게 되었다. 한글로는 왜 코딩을 할 수 없는가? 이 같은 질문을 누군가 했던 것도 사실이다. 그리고 가능했으나 보편성을 얻지 못하고 지금은 거의 개발이 정체되있는 것으로 세간에 알려져 있다.

(사실 그런게 있었다는 사실을 아는 사람 조차 드물다고 봐야할듯;;)

 

UTF-8
ANSI 949

 

한글을 깨지지 않게 하기 위해서는 인코딩(입력)한 한글코드를 그대로 사용해서 디코딩(출력) 하면 된다. (아주 쉽다!)

 

그보다 좋은 방법은 웬만하면 앞으로의 모든 문서에 UTF-8 문서를 사용하는 것이다. 가변길이라서 확장성과 속도 둘다 뛰어나서 이제는 거의 표준으로 되어있다. 대부분 IDE 는 UTF-8을 잘 지원한다. 

 

파이참 IDE 같은 경우 하단에 표시가 되어 있다. 이것은 인코딩 표시다. 여러 국가에서 사용할 수 있도록 자유도를 주는 것인데 그냥 마음 편하게 UTF-8 을 사용하는게 좋다.

 

그래도 간혹 ANSI CP 949 를 사용해야 할 때가 있을 것이다. 오래된 시스템이나 프로그램(레가시라고 한다)을 구동해야 할 때 그럴 수 있는데 일반적으로는 흔하지 않은 경우라 본다. 메모리가 정말로 제한된 소형 컴퓨터(임베디드)같은 경우 아예 아스키 코드로만 구동하는게 더 나을 것이고 굳이 한글을 넣을 필요가 있을지 생각이 되지만 살다보면 뭐 그런 특수한 경우가 없다고는 할 수 없으니까 인코딩을 CP 949 로 한다.

 

윈도우10에서도 명령어 프롬프트에 ANSI 949를 기본으로 설정해 둔 것은 과거 소프트웨어와 호환성 때문인 것으로 보인다. 혹시라도 되게 오래된 코드를 윈도우10에서 돌리고 있는 것을 감안한 것이다.

 

어쨋든 의외로 해결법이 간단한데 원리를 몰라서 한참 헤매이게 만드는 것이 인코딩 문제다. 특히 해외에서 만들어진 프로그램 중에 한글 출력이 아예 안되는 경우도 있을 수 있다. 유니코드가 아니라 자기 나라 고유의 16비트 코드를 사용해서 한글이 들어가는 자리에 다른 코드가 들어가 있다면 어쩔 수 없다. 영어를 사용해야 한다.

 

전세계의 대부분 프로그래머들은 자신들의 프로그램의 호환성을 높이기 위해 유니코드를 사용한다. 그래야 프로그램에서 다른 나라 언어도 사용할 수 있기 때문이다.

 

이 원리를 알았다면 앞으로는 한글이 깨지면 당황할 필요없이 다음의 정보를 알아낸다.

 

1. 이 프로그램은 어떤 인코딩 방식을 사용했는가?

 

2. 현재 나의 소프트웨어 환경은 어떤 인코딩 방식을 사용하는가?

 

3. 나의 환경과 프로그램의 코드가 호환이 안된다면, 프로그램의 설정을 유니코드(UTF-8)로 바꿀 수 있는가? 

 

3번까지 해결이 안되면 아스키 코드를 사용한다.(영어만 사용함)

 

웬만하면 나부터도 인코딩을 UTF-8 로 맞추는게 호환성을 좋게 하는 길이다.

 


* 다음은 파이썬에서 유니코드를 출력하는 예제이다. 우선 인코딩 설정을 확인한다. 파이썬의 현재 버전은 utf-8 로 동작한다. 그러니까 한글 사용에 문제가 없다. sys 모듈로 확인가능하다.

 

import sys

# 시스템 인코딩 설정의 출력

print(sys.getdefaultencoding())
print(sys.getfilesystemencoding())

 

* encode 메서드는 코드를 선택하여 인코딩을 한다. 출력 창에 나온 것은 b' ' 형식인데, b는 byte를 뜻한다.

 

첫줄에 16진수 표기에 \a4\a1 이게 2바이트다. 4비트 자리수가 총 네개 있으니 16비트 (2바이트) EUC-KR 이다.

 

즉 한글 'ㄱ' 표기는 16진수 '0xA4A1' (42145) 이다. 영어 알파벳 대문자는 65~90까지라 외울수도 있는데 한글은 ㄱ부터가 42145라니 도저히 외울 수 없다. 이것이 한글 문자열을 다루는 것을 어려워 하는 첫번째 이유다.

 

# 한글 인코딩 CP949, UTF-8, UTF-16

name1 = "MR (ㄱ) (ㅏ) (나) (달) (랅)".encode("EUC-KR")
name2 = "MR (ㄱ) (ㅏ) (나) (달) (랅)".encode("CP949")
name3 = "MR (ㄱ) (ㅏ) (나) (달) (랅)".encode("UTF-8")
name4 = "MR (ㄱ) (ㅏ) (나) (달) (랅)".encode("UTF-16")
name5 = "MR ASCII CODE".encode("ASCII")

인코딩 문자 출력
인코딩 문자 출력

UTF-8에서 'ㄱ' 은 더 길다. 3바이트를 디코드 하면 ㄱ을 얻는다. 숫자값은 거의 14,910,641 이지만 그냥 3바이트로 인코딩한 조합이라고 보면 된다. 영어 알파벳에도 다 번호가 매겨져 있지만 굳이 코드로 바꾸지 않는다. 영어 알파벳은 다른 언어의 문자들과 달리 오래전부터 변하지가 않았기 때문이다.

 

b'\xe3\x84\xb1'.decode('utf-8')

 

* 디코딩은 decode 메서드를 사용한다. 만일 코드 체계가 인코드와 디코드가 다를 경우 오류를 내며 종료한다.

# 디코딩(복호화) 출력하기

print("[디코딩 문자 출력]")
print(name1.decode('EUC-KR'))
print(name2.decode('CP949'))
print(name3.decode('UTF-8'))
print(name4.decode('UTF-16'))
print(name5.decode('ASCII'))
print()

 

* 코드만 사용해서 복호화 시킬 수도 있다. EUC-KR 의 확장이 CP949 니까 같은 코드도 있지만 확장 코드에는 있고 기존 코드에 없는 문자는 깨져서 출력될 것이다.

 

이것도 초창기 한글 코드의 흔한 문제였다. 영어는 왜 깨지지 않는데 한글은 깨지느냐는 의문을 한번이라도 품어봤다면 나름의 답이 될 것이다. 바꿔말하면 영어가 깨지는 상황은 컴퓨터에 아주 심각한 오류가 발생한 상황으로 볼 수도 있다.

# 코드를 사용한 디코딩(복호화)

print(b'\xa4\xa1'.decode('EUC-KR'))
print(b'\xa4\xa1'.decode('CP949'))
print()

 

코드를 사용하면 컴퓨터는 바로 이해하겠지만 인간이 알아볼 수가 없다;;; 워드에 작성한 글을 일괄변환 시키는게 좋다.

 

* 서로 다른 코드 체계를 변환 할 수 있다. decode 와 encode 메서드를 사용하면 변환이 가능하다. 변환이 종료되면 바이트코드가 아예 다른 것을 알 수 있다. 'ㄱ' 이라는 문자의 해석 방법이 다른 것이기 때문에 'ㄱ' 을 중간에 매개체로 변환해야 한다.

# CP949를 UTF-8로 변환

decoded = 'ㄱ'.encode('CP949').decode('CP949')
encoded = decoded.encode('utf-8')
print("CP949코드 'ㄱ': ", 'ㄱ'.encode('CP949'))
print("UTF-8코드 'ㄱ': ", encoded)

 

* 유니코드에서 한글을 출력해본다. 유니코드에서 U+AC00 는 '가' 이다. 이것은 전세계에서 통용되는 약속이다. 한글 코드를 따로 사용하던 시절에는 다른 나라의 언어코드에 호환이 안됬지만 유니코드를 사용하는 지금은 가능하다.

(유니코드와 별개로 엔코딩은 같아야 한다)

# 0 ~ 65535까지 유니코드 출력하기

count = 0
for i in range(0xac00, 0xd7a4):
    if i % 15 == 0:
        print()

    print(hex(i),'|', chr(i), end=' ')

한글 유니코드
한글 유니코드

 

* 한글 유니코드 UTF-8로 엔코딩하기

 

 

UTF-8 Encoding (fileformat.info) 참고 사이트 (인코딩 방법)

 

UTF-8 Encoding

UTF-8 Encoding Summary UTF-8 is a compromise character encoding that can be as compact as ASCII (if the file is just plain English text) but can also contain any unicode characters (with some increase in file size). UTF stands for Unicode Transformation Fo

www.fileformat.info

UTF 시리즈는 유니코드에 대한 사용방식이다. 그중에서 UTF-8 은 가변길이 인코딩으로써 공간을 절약하는 방법이다. 알파벳은 고작 7비트 면 충분한데 4바이트를 사용해서 공간을 낭비할 필요가 없다. 또 네트워크를 생각하면 더 많은 자원이 낭비되고 속도만 느리게 할 것이다.

 

네크워크에서 A 하나 보낼 때 필요한 문자 공간은 7비트(1바이트)다. 4바이트를 써서 25비트를 놀게할 수는 없다. 닭 잡는데 소잡는 칼을 쓰지 않는다는 묘수다. 반면 한글은 공간이 많이 필요하다고 했다. 한글은 3바이트다.

 

아래 표와 같이 한글은 2바이트 영역에서도 끄트머리에 있기 때문이다.11000개나 되는 유니코드가 들어있기 때문이다.

print(0xd7a4 - 0xac00)

답은 11172 글자다.

표에 빨간색 안에 보면 앞쪽에 뭐가 숫자가 달려있다. 그게 이 코드의 성격을 말해주는 것이다.

 

각 바이트의 앞에 붙은 1110, 10, 10 은 3바이트임을 알려주는 것이다. 나머지 공간에 4 + 6 + 6 = 총 16비트가 남아있다. 여기에 유니코드의 비트를 넣어주면 된다. 한글은 유니코드 AC00~D7A4에 있다. 예를 들어서 하나를 인코딩 해보자.

 

U+B098 은 '나' 의 유니코드이다.

 

*아래 결과에는 UTF-8 방식으로 인코딩한 결과가 '나'를 인코딩한 결과와 같다. 다른 유니코드들도 인코딩 해보면 같은 결과가 나온다. 2바이트 영역이 필요한 한글은 UTF-8에서 3바이트로 사용된다는 것은 문자열을 다루는 일에 도움이 될 것이다.

 

[유니코드] : 나 0xb098
0xB0 :  0b10110000
0x98 :  0b10011000

[UTF-8 binary format]
1st Byte: 0b11100000, 2nd Byte: 0b10000000, 3rd Byte: 0b10000000
1st Byte: 0b1110XXXX, 2nd Byte: 0b10XXXXXX, 3rd Byte: 0b10XXXXXX
h1: 1011
h2: 0000
h3: 10
h4: 011000

[바이트 3개]
0b11101011
0b10000010
0b10011000

[UTF-8 인코딩 완료]
0xeb 0x82 0x98
b'\xeb\x82\x98'

Process finished with exit code 0

 

* 예제 코드

print('[유니코드] :', chr(0xB098), hex(0xB098))
print('0xB0 : ', bin(0xB0))
print('0x98 : ', bin(0x98))
print()

utf8byte1 = 0b11100000
utf8byte2 = 0b10000000
utf8byte3 = 0b10000000

print('[UTF-8 binary format]')
print('1st Byte:', bin(utf8byte1), end=', ')
print('2nd Byte:', bin(utf8byte2), end=', ')
print('3rd Byte:', bin(utf8byte3))
print('1st Byte: 0b1110XXXX, 2nd Byte: 0b10XXXXXX, 3rd Byte: 0b10XXXXXX')


h1 = 0xB0 >> 4
print('h1:', bin(h1)[2:].zfill(4))

h2 = 0xB0 << 2
h2 = h2 & 0b0000111100
print('h2:', bin(h2)[2:].zfill(4))

h3 = 0x98 >> 6
print('h3:', bin(h3)[2:].zfill(2))

h4 = 0x98 & 0b00111111
print('h4:', bin(h4)[2:].zfill(6))
print()

c1 = bin(utf8byte1 | h1)
c2 = bin(utf8byte2 | h2 | h3)
c3 = bin(utf8byte3 | h4)

print('[바이트 3개]')
print(c1)
print(c2)
print(c3)
print()

print('[UTF-8 인코딩 완료]')
print(hex(int(c1, 2)), end=' ')
print(hex(int(c2, 2)), end=' ')
print(hex(int(c3, 2)))

test1 = "나".encode("UTF-8")
print(test1)

 

*아래는 참고한 링크들이다. 유니코드에 관심이 있다면 유니코드 홈페이지에 방문하는 것을 권한다. 유니코드 표와 함께 1,200페이지에 달하는 스탠다드 가이드라인도 PDF파일로 제공한다.

 

 

유니코드 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 다른 뜻에 대해서는 U;Nee Code 문서를 참조하십시오. 유니코드(Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준

ko.wikipedia.org

 

C언어 2 - 2 | 자료형 | 아스키코드

C언어에 대한 내용을 다룰 때 아스키 코드에 대한 이야기를 하려고 생각하고 있었다. 다행히 요즘은 인터넷에 아스키 코드에 관해서는 쉬운 설명도 많이 있고 한글 자료도 많다. 아스키코드는

digiconfactory.tistory.com

 

완성형 - 나무위키

이 저작물은 CC BY-NC-SA 2.0 KR에 따라 이용할 수 있습니다. (단, 라이선스가 명시된 일부 문서 및 삽화 제외) 기여하신 문서의 저작권은 각 기여자에게 있으며, 각 기여자는 기여하신 부분의 저작권

namu.wiki

 

CP949 - 나무위키

CP949에서 새로이 추가된 8000여자는 다음 규칙에 따라 가나다순을 차례대로 2바이트로 할당받았다. 첫 번째 바이트는 0x81~0xC6 사이를 할당받는다.두 번째 바이트는 0x41~0x5A(대문자 A~Z), 0x61~0x7A(소문

namu.wiki

공유하기

facebook twitter kakaoTalk kakaostory naver band