리눅스는 대부분 C언어로 작성되 있어서
리눅스 사용자는 자연스럽게 C에
친숙하게 되는데 대게 print "한글" 만
해도 되는 다른 고급언어들과 달리
C에서는 한글을 처리하는데
별도의 코드가 필요합니다.
요즘에는 UTF-8 인코딩이 표준이 되서
printf 함수로도 콘솔에 한글을
출력할 수는 있게 되었는데 그건 시스템콜이
내부적으로 UTF-8로 처리하기 때문에
어셈블리어도 출력 결과가 같습니다.
문제는 1바이트 char로는 유니코드의 한글을
입력할 수 없어서 별도의 데이터형이 필요합니다.
아스키 문자열이라는게 아스키 문자형의 배열이고
한글은 유니코드 문자열을 만들기 위해
유니코드 문자형이 필요한데 이를 위해
wchar_t 타입을 사용합니다. wchar_t 타입은...
시스템마다 다른지까지 모르겠습니다만
x86-64 WSL2 우분투에서는 int 형입니다.
wchar_t * 형은 int * 과 같습니다.
4바이트 int 형 배열으로 유니코드값을 저장합니다.
예를 들어 '가'의 유니코드 값은 AC00입니다.
유니코드와 UTF-8을 혼동하기 쉬운데
둘은 다른 것 입니다. 유니코드는 세상의
거의 모든 문자에 대해 컴퓨터에서 사용하기로
표준화 시켜서 약속한 값입니다.
지금은 유니코드가 표준이지만 컴퓨터의
보급이 시작된 시기에는 각자 알아서
코드 체계를 만들어서 사용했습니다.
한국의 코드 체계는 70년대에 만들었다고
하는데 유니코드 이전 가장 많이 사용된 것이
EUC-KR과 CP949 입니다. 지금도 CP949는
윈도우즈의 CMD의 표준 인코딩입니다.
복잡한 과거가 있지만 지금은 거의 유니코드를
사용하기 때문에 유니코드만 알아도
딱히 프로그래밍하는데 지장은 없습니다.
유니코드는 세상 모오든 문자를 표현할 수
있다는 아이디어로 나온건데 그건 다 좋은데
문제는 그렇게 하면 바이트를 너무 많이 씁니다.
영어는 알파벳 대소문자 다합쳐서 56자입니다.
한글은 문자 조합으로 표현할 수 있는
글자가 11,172글자입니다.
중국어의 한자는 3만6천개라고 합니다.
일본은 오십음도 히라가나 가타카나를 쓰지만
표준한자를 2,136개를 사용합니다.
이 세개 나라만 이정도이고 또 다른 나라들의
문자들을 합치면 매우 많습니다.
유니코드 콘소시엄이 정의한 글자만 14만개인데
이런 것을 모두 하나의 데이타형으로 정의한다면
일단 3바이트 이상이 필요합니다. 2바이트는
65536개의 문자를 정의할 수 있는데
엄청 애매한 상황이지요. 1바이트 때문에
전세계가 싸울 필요는 없으니까
그냥 1바이트를 더 준겁니다.
14만개니까 한 18비트면 될 것 같은데
그런 식으로 컴퓨터가 작동하지 않기 때문입니다.
그러면 알파벳 사용자들도 3바이트를 써야하느냐?
2바이트는 완전히 낭비이므로 전세계 데이터의
엄청난 낭비입니다. 그냥 봤을 때는 그깟 1바이트가지고
낭비냐 우리집 하드는 2테라다 - 라고도 할 수 있겠으나
데이터를 처리하고 저장하는 효율, 네트워크 전송비용
등을 감안하면 그러면 안됩니다.
해서 유니코드를 다시 인코딩 하는 방법이
여러개가 나왔는데 그 중에 utf-8을 가장
많이 사용하는 것은 가변길이로 효율이 좋습니다.
utf-8에서는 영어 문자가 여전히 1바이트이고
한글은 3바이트입니다. 이 두개의 문자가
호환이 안됐었는데 utf-8에서는 다됩니다.
컴퓨터 발달의 역사에서 문자 인코딩에
좌절하던 시기가 길었는데 UTF-8은
상당히 획기적인 방법으로 나왔습니다.
문제는 인코딩이 많이 복잡하다는 거구요.
한글 사용자들은 프로그래밍을 하기 위해서
필연적으로 유니코드 문제에 대해서
좀 이해할 필요가 있습니다. 영어 사용자가
제일 좋은게 그냥 영어를 쓰면 됩니다.
유니코드에 대해서는 이전에 파이썬에
대해서 올린 내용이 있지만 지금봐도
너무 잡다하게 써놓아서 읽기가 힘드네요.
암튼 문자 코드 처리는 프로그래밍에서
꼭 알아야할 중요한 주제입니다.
인코딩을 직접 하지 않아도
개념은 알고 있을 필요가 있지 않을까 -
라고 생각합니다.
유니코드와 한글 | 파이썬 유니코드 출력하기 | 한글 깨지는 문제 해결방법 | UTF-8 인코딩 하는 법
문자 인코딩은 광범위한 주제라 포스팅
하나에 담으려고 하면 너무 잡다해지는
경향이 있습니다. 적당히 C 예제
몇개를 보고 마무리하겠습니다.
#include <stdio.h>
#include <wchar.h>
int main(void)
{
wchar_t korArray[] = L"가나다라마바사아";
for (int i = 0; i < 8; i++)
{
wprintf(L"[%x], ",korArray[i]);
}
wprintf(L"Done!\n");
return 0;
}
[ac00], [b098], [b2e4], [b77c], [b9c8], [bc14], [c0ac], [c544], Done!
유니코드 값을 출력하는 코드입니다.
wchar_t 의 배열에 유니코드값을 저장합니다.
문자열 따옴표 " " 앞에 L이 들어가야 합니다.
8개의 글자가 있는데 실제로는 36바이트(4*9개)
유니코드 문자열입니다. C 문자열의 마지막에는
\0 널 값이 들어갑니다(유니코드 0번 값)
다음 예제는 locale 설정으로 한글 출력을 합니다.
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(void)
{
setlocale(LC_ALL,"");
wprintf(L"한글입니다\n");
wchar_t myArr[] = L"가각 테스트";
wprintf(L"%S\n",myArr);
wprintf(L"[%x]\n",myArr[0]);
wprintf(L"[%x]\n",myArr[1]);
return 0;
}
리눅스의 locale 명령어를 입력하면
운영체제의 기본 코드값이 있습니다.
한국 로케일의 경우 UTF-8 인코딩
ko_KR.UTF-8 로 되어 있을 겁니다.
운영체제의 로케일을 가져옵니다.
로케일 설정 없이는 출력이 안됩니다.
아래 처럼 유니코드값은 출력되는데
인코딩이 안되는데 setlocale 이 알려줍니다.
?????
?? ???
[ac00]
[ac01]
다음 코드는 wscanf 로 멀티바이트 입력을 받아봅니다.
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(void)
{
setlocale(LC_ALL,"");
wchar_t myArr[20];
wprintf(L"이름을 입력하시오.\n");
wscanf(L"%s", myArr);
wprintf(L"입력하신 이름은 \"%s\" 입니다\n", myArr);
return 0;
}
이름을 입력하시오.
박수건
입력하신 이름은 "박수건" 입니다
여기서 myArr의 값을 출력해보면 흥미롭게도
UTF-8 인코딩 값이 저장되어 있습니다.
아까 L"가각 테스트" 로 저장할 때는
유니코드값을 저장하더니 입력받을 때는
왜 UTF-8 인코딩 값을 저장할까요?
정답은 포맷에 있습니다.
유니코드는 %s가 아니라 %S로 입력받습니다.
출력은 wprintf 함수의 %s 포맷이
알아서 해주지만 입력 포맷에 따라
인코딩이 달라집니다. C는 이런 점들이
어렵습니다. 파이썬 같은 최신 언어들은
(20년이 넘었지만 업데이트는 현역이니까)
메소드 이름 자체가 알아듣기 쉬운데
C언어는 알아서 찾아서 해야 합니다.
%s와 %S의 차이점 같은 건 안해보면 모르니까요
(문자도 %c와 %C 차이가 있습니다)
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(void)
{
setlocale(LC_ALL,"");
wchar_t myArr[20];
wprintf(L"이름을 입력하시오.\n");
wscanf(L"%S", myArr);
wprintf(L"입력하신 이름은 \"%S\" 입니다\n", myArr);
for (int i = 0; i < 20; i++)
{
if(myArr[i] == 0) break;
// 유니코드로 출력
wprintf(L"[%C : %x] ", myArr[i], myArr[i]);
}
wprintf(L"\ndone!\n");
return 0;
}
이름을 입력하시오.
각가나
입력하신 이름은 "각가나" 입니다
[각 : ac01] [가 : ac00] [나 : b098]
done!
한글코드 범위가 0xac00 ~ 0xd7af 이므로
lastKor 숫자의 값을 조절하여 범위를 변경할 수 있습니다.
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(void)
{
setlocale(LC_ALL,"");
wprintf(L"[----- 한글 유니코드 문자 출력 -----]\n\n");
wchar_t korUnicode = 0xac00;
wchar_t lastKor = korUnicode + 300;
for (int i = korUnicode; i < lastKor; i++)
{
wprintf(L"%C, ",i);
if ((i-11)%20 == 0) wprintf(L"\n");
}
wprintf(L"\nDone!\n");
return 0;
}
[----- 한글 유니코드 출력 -----]
가, 각, 갂, 갃, 간, 갅, 갆, 갇, 갈, 갉, 갊, 갋, 갌, 갍, 갎, 갏, 감, 갑, 값, 갓,
갔, 강, 갖, 갗, 갘, 같, 갚, 갛, 개, 객, 갞, 갟, 갠, 갡, 갢, 갣, 갤, 갥, 갦, 갧,
갨, 갩, 갪, 갫, 갬, 갭, 갮, 갯, 갰, 갱, 갲, 갳, 갴, 갵, 갶, 갷, 갸, 갹, 갺, 갻,
갼, 갽, 갾, 갿, 걀, 걁, 걂, 걃, 걄, 걅, 걆, 걇, 걈, 걉, 걊, 걋, 걌, 걍, 걎, 걏,
걐, 걑, 걒, 걓, 걔, 걕, 걖, 걗, 걘, 걙, 걚, 걛, 걜, 걝, 걞, 걟, 걠, 걡, 걢, 걣,
걤, 걥, 걦, 걧, 걨, 걩, 걪, 걫, 걬, 걭, 걮, 걯, 거, 걱, 걲, 걳, 건, 걵, 걶, 걷,
걸, 걹, 걺, 걻, 걼, 걽, 걾, 걿, 검, 겁, 겂, 것, 겄, 겅, 겆, 겇, 겈, 겉, 겊, 겋,
게, 겍, 겎, 겏, 겐, 겑, 겒, 겓, 겔, 겕, 겖, 겗, 겘, 겙, 겚, 겛, 겜, 겝, 겞, 겟,
겠, 겡, 겢, 겣, 겤, 겥, 겦, 겧, 겨, 격, 겪, 겫, 견, 겭, 겮, 겯, 결, 겱, 겲, 겳,
겴, 겵, 겶, 겷, 겸, 겹, 겺, 겻, 겼, 경, 겾, 겿, 곀, 곁, 곂, 곃, 계, 곅, 곆, 곇,
곈, 곉, 곊, 곋, 곌, 곍, 곎, 곏, 곐, 곑, 곒, 곓, 곔, 곕, 곖, 곗, 곘, 곙, 곚, 곛,
곜, 곝, 곞, 곟, 고, 곡, 곢, 곣, 곤, 곥, 곦, 곧, 골, 곩, 곪, 곫, 곬, 곭, 곮, 곯,
곰, 곱, 곲, 곳, 곴, 공, 곶, 곷, 곸, 곹, 곺, 곻, 과, 곽, 곾, 곿, 관, 괁, 괂, 괃,
괄, 괅, 괆, 괇, 괈, 괉, 괊, 괋, 괌, 괍, 괎, 괏, 괐, 광, 괒, 괓, 괔, 괕, 괖, 괗,
괘, 괙, 괚, 괛, 괜, 괝, 괞, 괟, 괠, 괡, 괢, 괣, 괤, 괥, 괦, 괧, 괨, 괩, 괪, 괫,
Done!
문자 인코딩 문제는 단순히 프로그래밍 언어의
문제라기 보다는 운영체제나 편집기의 인코딩 등
여러 환경적 요소가 원인이 되는 경우가
많으므로 해결책을 너무 프로그래밍 자체에
맞추는 것 보다 그 프로그램의 빌드환경
실행환경, 런타임 쪽에 이해도를 높일 필요가 있습니다.
한글은 전세계 1위 클라쓰 세종대완님이 창시한
과학적인 문자이지만(국뽕적으로)...
한편으로 문자 조합이 너무 많아서 문자열 처리가
쉽지는 않습니다. 그럴 때는 대소문자 52개로
퉁치는 알파벳이 부럽기도 합니다. 하지만
그렇다고 갑자기 미국인으로 환생하는 것은
아니기 때문에 대략 인간적으로
이해할 필요가 있습니다.