2015년 6월 27일 토요일

바이트 순서와 BOM이 이렇게 복잡해진 이유 (11)

오늘은 BOM(Byte Order Mark)에 대해서 알아보려고 한다. BOM은 바이트 순서를 나타내는 표식이고 바이트 순서뿐만 아니라 이 파일이 어떤 인코딩을 사용했는지도 나타낸다. BOM이 생겨난 사연에 대해서 알아보자. 

개발자라면 모두 알고 있겠지만 CPU마다 바이트 순서가 다르다. 바이트 순서를 선택할 수 있는 CPU도 있다. 0x1234는 0x12, 0x34라고 표시하는 것이 자연스럽다. 이런 순서를 빅 엔디언(Big-endian)이라고 부르며 초창기 대부분의 CPU는 이런 아키텍처를 사용했다. 하지만 인텔에서 x86 CPU를 만들면서 적용한 아키텍처는 이와는 정반대다. 0x1234를 0x34, 0x12라고 표시를 했다. 이를 리틀 엔디언(little-endian)이라고 한다. 

인텔이 x86 CPU에 이런 아키텍처를 적용한 이유는 덧셈을 조금 더 단순하게 해서 덧셈 회로를 조금이라도 작게 만들기 위한 것이라고 전해지고 있다. 숫자를 반대로 하면 앞쪽 바이트부터 더해서 순서대로 자리 올림을 하면 된다. 우리가 현실에서 덧셈을 할 때 1의 자리부터 거꾸로 더해나가는 것이 더 간단하다는 것을 생각해보면 숫자를 반대로 표시하는 것이 왜 덧셈을 할 때 간단하지 알 수 있다. 지금은 CPU가 한 바이트씩 더하지를 않기 때문에 별 차이도 없어져버렸다.





이렇게 인텔은 주로 리틀 엔디언 아키텍처를 사용하고 대부분은 빅 엔디언을 사용하지만 우리가 사용하는 PC는 거의 인텔 계열 CPU를 사용하기 때문이 어떻게 보면 리틀 엔디언 환경에 접할 가능성이 더 높다. 

이렇게 세상에는 두 가지 바이트 순서가 존재하게 되어서 파일을 저장한 후에 다른 컴퓨터에서 읽으면 제대로 읽혀지지 않는 일이 빈번하게 발생하게 되었다. 또한 네트워크를 통해서 데이터를 전송할 때도 문제가 발생했다. 그래서 바이트 순서 규칙을 하나로 정해놓고 자료를 전달한 후에 현재 시스템과 반대로 된 바이트 순서 데이터를 읽을 때는 바이트 순서를 다시 반대로 바꿔줘야 했다. 

개발자들이 흔히 하는 실수는 바이트 순서를 현재의 시스템에서 바이트 순서를 보고 짐작하여 메모리를 직접 접근하도록 개발을 해서 다른 시스템에서 동작하지 않게 되는 것이다. 또 시스템간에 파일을 교환할 때바이트 순서를 고려하지 않는 것이다. 소프트웨어를 설계할 때 신경을 써야 할 부분이다.

이런 현상은 인코딩과 관련해서도 똑같이 일어난다. 현재 데이터의 문자가 어떤 인코딩을 사용하고 있는지도 헷갈리는 것이다. 앞부분 몇 바이트를 보니 ASCII 같은데 실제로는 UTF-8 일수도 있고 EUC-KR 문서일 수도 있다.





그래서 문서나 데이터는 자신이 사용한 인코딩과 바이트 순서를 스스로 알려주는 것이 좋다. 이를 Self-Identification이라고 한다. 이런 용도로 사용하기 위해 표준을 정한 것을 BOM이라고 한다. BOM은 파일의 맨 앞에 붙으며 기존의 정상적인 문자에서는 나올 수 없는 문자로 구성이 되어 있다. 그럼 BOM은 어떤 것이 있는지 보자.

EF BB BF : UTF-8
FE FF : UTF-16 빅 엔디언
FF FE : UTF-16 리틀 엔디언
00 00 FE FF : UTF-32 빅 엔디언
FF FE 00 00 : UTF-32 리틀 엔디언

UTF-16과 UTF-32는 엔디언에 따라서 글자 내의 바이트 순서가 반대이다. UTF-8은 바이트 순서가 고정이므로 BOM이 한가지 밖에 없다. 여기서 가장 문제가 되는 것은 UTF-8의 BOM이다. 가장 널이 쓰이는 유니코드 파일의 인코딩이 UTF-8인데 UTF-8의 BOM은 개발자를 여간 괴롭히는 것이 아니다. 

UTF-8의 BOM은 BOM 자체가 문제라기 보다는 시스템 또는 어플리케이션에 따라서 BOM를 사용하기도 하고 사용하지 않기도 한다. 또는 두 경우를 모두 지원하기도 한다. 그 조합이 워낙 많아서 헷갈리곤 한다. UTF-8 문서에는 BOM이 없어도 대부분의 경우 UTF-8 문서인지 알 수가 있다. 그래서인지 Unix나 Linux 시스템에서는 BOM이 없는 UTF-8을 이용한다. 반대로 마이크로소프트는 철저히 BOM을 사용한다. 





Unix나 Linux에서 또는 오픈소스 개발툴에서는 소스코드에 BOM이 있어서 제대로 읽지 못하기도 하고 HTML 파일에 BOM이 있을 경우 웹서버에 따라서 BOM이 웹브라우져 화면에 출력되기도 한다. 특히, 윈도우의 노트패드는 UTF-8 파일에 BOM을 붙여버려서 종종 곤란한 일을 겪게 된다. 이런 혼란을 문제없이 해결하려면 노하우가 꽤 필요하다.

나는 개발을 할 때 부득이한 경우가 아니라면 BOM이 없는 UTF-8 파일을 표준으로 삼고 있다. 그리고 필요한 경우에만 예외적으로 BOM을 붙이는 것이 수월하다. 그러기 위해서는 언제 BOM이 필요하고 어디서는 BOM이 없어야 되는지 잘 알고 있어야 한다.

UTF-8에 BOM을 붙여야 하느냐, 필요 없느냐는 논쟁은 쉽게 결론이 나지 않을 것이다. 개발자가 상황에 알맞게 알아서 BOM을 붙이기도 제거하기도 해야 한다. 그래서 개발용 편집기는 BOM을 자유롭게 다룰 수 있어야 한다. 나는 개발언어나 환경에 따라서 수많은 편집기를 사용하지만 대표적인 텍스트 편집기로는 Notepad++를 사용한다. 수많은 인코딩과 BOM을 자유롭게 다루고 변환할 수 있다.

윈도우에 내장된 Notepad는 사용하지 않을 것을 추천한다. 본의 아니게 BOM이 붙어서 고생을 할 수가 있다.

추가로 빅 엔디언과 리틀 엔디언이란 용어의 유래에 대해서 알아보자.걸리버여행기를 보면 달걀 깨는 법으로 대립 중인 두 나라가 나온다. 달걀을 뾰족한 곳을 깨느냐 뭉툭한 곳을 깨느냐 논쟁을 하며 나중에는 전쟁도 하는데 여기서 뭉툭한 곳을 깨는 사람을 빅 엔디언이라고 하고 뾰족한 곳을 깨는 사람을 리틀 엔디언이라고 한다. 

소프트웨어 세계도 전쟁은 하지 않았지만 두 표준이 쉽게 통일되지 않고 있다.

개발자를 조삼모사식 원숭이 취급하기

소프트웨어 회사에서 성과 위주의 치열한 내부 경쟁 강요는 단기적인 성과는 낼 수 있을지 몰라도 개발 문화와 기업 문화를 망친다. 그 결과 장기적으로는 오히려 혁신을 못하고 경쟁력이 약화된다.

많은 회사의 경영진은 성과에 따른 차등 보상을 통해서 개발자들의 의욕을 고취하려고 한다. 명백한 차등 보상을 통해서 너도 높은 성과를 내서 보상을 받으라고 독려하곤 한다. 이를 위해서 기본 연봉을 줄이고 성과급을 높임으로써 보상이 진정한 보상이 아니라 보상을 받지 못하면 평균 이하의 대접을 받게 되기도 한다.

개발자들이 받아야 할 적절한 연봉을 제대로 못 받고 선심 쓰듯 지급하는 성과급을 통해서 개발자들의 의욕을 고취하려는 방법은 조삼모사와 크게 다를 바 없다.

하지만 이런 방법은 탄광 같은 육체노동에서는 통할지 몰라도 정신노동에서는 잘 통하지 않는다. 특히 지식 산업인 소프트웨어는 단기 성과를 위해서 쉽게 미래를 갉아 먹을 수 있기 때문에 성과 드라이브는 약이 아니라 독이 되는 경우가 많다. 소프트웨어는 8시간 일하는 사람이 10시간 일한다고 성과가 더 나아 지는 것은 아니다. 그렇게 생각한다면 소프트웨어 개발을 육체 노동으로 생각하고 있는 것이다.

더 큰 문제는 평가의 불공정성이다. 평가의 기준을 무엇으로 삼을 것인가? 매출? 코딩 라인 수? 버그 해결 개수? 무엇으로 따져도 불공평하다.

매출은 사실 개발자들의 노력의 정도를 말해주지 않는다. 매출을 기준으로 보상을 한다면 개발자는 운에 따라서 연봉이 달라지는 결과를 가져온다. 단기적인 매출에 미치는 개발자 노력의 비중은 크지 않다. 분야에 따라서 천차만별이지만 환율이나 경쟁사 관계가 매출에 더 영향을 미치기도 한다. 억지로 단기 매출에 집착하다가 소프트웨어 아키텍처를 망치기도 한다. 소프트웨어의 어려운 점은 속으로 썩어도 당장 눈에 잘 안 보이는 것이다.

단기적인 특수 과제를 성공했을 때 보상하는 것도 불공평하기는 마찬가지다. 약간 선물식의 보상은 큰 문제는 없어도 단기 프로젝트 성공 팀에게 큰 보상을 하는 것은 다른 개발자들에게 상대적인 박탈감을 가져온다. 열심히 해도 운에 따라서 보상을 받지 못할 바에는 슬슬 어려운 일은 피하고 자기 시간을 가지며 여유롭게 생활하려고 한다. 개발 문화와 회사에 기여는 생각하지 않고 폭탄만 피하자는 무사안일이 판칠 수도 있다.

성과에 대해서 보상을 노골적으로 하면 보통 성과 위주로 일을 한다. 이게 무슨 잘못인가 생각하겠지만 소프트웨어는 지식산업이기 때문에 큰 문제다. 예를 들어서 매출에 비례해서 평가를 한다면 매출이 많은 부서로 옮기는 것이 가장 좋은 방법이다. 버그를 많이 고치는 것을 평가 기준으로 삼으면 일부러 사소한 버그를 많이 만들어내거나 버그 보고를 더 많이 하고 버그도 근본 원인을 해결하기 보다는 대충 증상만 잡을 수가 있다. 버그를 하나라도 더 고치기 위해서 애를 쓴다. 거짓말이 아니다. 개발자가 나쁜 사람이 이라서가 아니라 이런 정책에 길들여진 결과다.

개발자의 활동은 지극히 창의적인 활동이라서 성과 드라이브로 밀어 붙이면 당장은 성과가 좋은 것 같아도 장기적으로 대부분 더 손해가 난다. 소스코드를 잘 정리해야 하는데 당장은 문제가 안되니 엉망진창으로 놔두던가, 소스코드에 주석을 달 시간이 아까워서 주석을 안단다. 문서도 엉터리로 작성하고 당장에 단기 성과에 직접적인 영향이 있는 활동에만 집중한다.

그렇다고 성과가 중요하지 않는 것은 아니다. 성과가 중요하다는 것은 개발자들도 잘 알고 있다. 하지만 너무 성과로만 밀어 붙이고 이에 대해서 큰 차별적인 보상을 하면 더 큰 문제가 발생한다는 것이다.

소스코드가 망가지고 개발문화를 해치며 보상에서 소외된 90%의 사기에 저하된다. 보상을 받은 사람도 다음해에는 보상에서 제외될 수 있다.
몇몇 회사는 개발에게 원래 100을 주는 것이 적당한데 70만 주고 30은 성과에 따라서 보상하겠다고 한다. 이는 영업직에게나 적당한 정책이다.
인생이 운칠기삼이라고 해서 운이 매우 중요하고 보상을 받는 팀이나 회사에 속하는 것은 운이 좋아서라고 생각할 수는 있지만 개발자들을 성과에 따른 보상으로 움직이는 꼭두각시로 만들면 회사의 소프트웨어 개발역량은 점점 나빠질 것이다.

경영자들은 개발자들이 조삼모사 원숭이처럼 다룰 생각을 하면 안된다. 개발자를 움직이는 것은 여러 가지가 있지만 그 중에서 가장 중요한 것 중 하나가 연봉이다. 70주고 잘하면 30을 준다고 하지 말고 100을 모두 연봉으로 주는 것이 좋다.

개발자는 보통 일 자체를 즐긴다. 즐길 때 성과가 가장 높다. 그렇다고 개발자들이 노는 것은 아니다. 개발자들도 성과를 내야 한다는 것을 잘 알고 있다. 개발문화, 공유, 문서, 성과 사이에서 균형을 잘 잡아야 장기적으로 성과가 증가한다. 단기적인 성과를 절대로 무시할 수 없지만 그만큼 장기적으로 개발문화를 잘 발전시키는 것도 중요하다.

2015년 6월 26일 금요일

123.456이 무엇으로 보이는가? (10)

123.456 숫자를 보면 우리나라 사람들은 대부분은 123에 소수점을 찍은 후 0.456이 추가된 것으로 생각을 할 것이다. 하지만 독일사람에게 123.456을 보여주면 뭐라고 생각할까? 독일에서 ‘.’은 천단위 구분자다. 그래서 123.456은 123456과 같은 숫자다. 만약에 123.456톤 원자재를 주문하면 어떻게 될까? 원래 의도보다 1000배많은 물량을 주문한 결과가 된다. 이런 것이 처리가 안된 소프트웨어를 과연 독일에 팔 수 있을까?

그럼 독일이 이런 것을 알았으니 독일에 맞춰서 소프트웨어를 개발한다고 하면 매번 새로운 나라가 나올 때마다 조사하고 연구해서 지원을 해줘야 한다. 그런 식으로는 끝이 없다. 나라별로 소숫점과 천단위 구분자는 천차만별이다. 게다가 아랍은 아라비아 숫자가 아닌 별도의 숫자를 사용하고 있고, 중국과 일본은 과거 천이 아닌 만단위 구분자를 사용했었다. 

Application 개발자가 매번 숫자를 출력할 때마다 이런 고민을 할 수는 없다. 국제화 라이브러리를 만드는 개발자가 이를 고민해야 하고 Application 개발자는 숫자를 출력하기 위해서 마음대로 개발을 하면 안되고 꼭 국제화 라이브러리를 사용해야 한다. 국제화 라이브러리 개발자는 내용은 나중에 채우더라도 Application 개발자가 쓸 수 있는 함수 정의를 먼저 제공해야 한다. 처음에는 한국의 숫자 형식으로 출력이 되겠지만 국제화 라이브러리 개발자가 로케일별 숫자 형식을 지원하는 라이브러리를 완성하면 숫자가 로케일별로 다른 형식으로 출력되게 된다. 


(소수점 사용 지도)

그럼 나라별로 어떤 형식의 숫자를 사용하는지 먼저 좀 알아야 한다. 또한 자신이 개발하고 있는 OS와 사용하고 있는 개발툴, 프레임워크가 어떤 국제화 함수들을 지원하는지도 잘 알아야 한다. 먼저 나라별 숫자 형식을 살펴보자.

1,234,567.89와 같은 숫자를 쓰는 나라는 한국, 미국, 캐나다, 중국, 일본, 영국, 호주 등이 있다. 물론 소프트웨어 내에서는 국가가 아니고 로케일로 지정을 해야 한다.

이와 반대로 1.234.567,89 형식의 숫자를 쓰는 나라는 독일, 그리스, 덴마크, 이탈리아, 인도네시아, 러시아 등이 있다. 네덜란드는 통화표시 때는 이 형식을 사용한다. 인도네시아는 과거 네덜란드의 식민지여서 이 숫자 형식을 쓰게 된 것으로 보인다. 아시아 나라들의 국제화 표준은 식민지 역사와 관련이 있는 것이 씁쓸하다.

특이하게 스위스에서는 1'234'567.89 형식으로 숫자를 사용한다. 

1 234 567,89와 같이 천단위 구분자로 띄어쓰기를 하고 소수점으로 콤마를 쓰는 나라로는 벨기에, 프랑스, 네덜란드(비 통화표시) 등이 있다.




사우디아라비아에서는 ١٬٢٣٤٬٥٦٧٫٨٩와 같이 표기한다. 천단위 구분자도 다르고 소수점도 다르다. 아리비아숫자도 쓰지만 아랍어의 숫자도 쓴다. 아랍권도 로케일마다 숫자표기가 다르다. 천단위 구분자는 뒤집힌 콤마인데 폰트 때문인지 제대로 안나온다. 특이한 점은 아랍어는 오른쪽에서 왼쪽으로 쓰지만 숫자는 왼쪽에서 오른쪽으로 쓴다는 점이다.

아라비아숫자는 최초에 인도에서 만들어졌다는데 대다수 역사가들이 동의를 하다. 하지만 인도숫자가 아니고 아라비아숫자라는 명칭을 얻게 된 이유는 인도에서 만들어졌지만 이슬람세계를 거쳐 점점 변형이 되면서 유럽으로 전파가 되었기 때문에 유럽 사람들은 아랍에서 온 숫자로생각했다. 

이외에도 여러가지 숫자 표기 형식이 더 있다. 하지만 개발자가 이 모든 것을 다 알 필요는 없다.  이렇게 다양하다는 것 정도와 나중에 버그가 보고 될 때 버그를 고치기 위해서 필요한 정도만 알면 된다.

우리나라 및 한자권의 나라들은 숫자가 만 단위로 되어 있어서 만단위구분자를 찍는것이 읽기는 더 편하다. 하지만 숫자표기 표준화에 따라서 우리나라에서도 천단위 구분자를 사용하는데 불편하다. 또한 관습적으로 백단위와 천단위 구분자를 섞어서 쓰는 나라도 있지만 지금은 거의 천단위 구분자를 사용하는 것으로 통일되고 있다.

숫자를 위한 국제화 라이브러리를 설계할 때는 다음 순서를 따르면 된다.

첫째, 지원 범위를 결정한다.
얼마나 많은 로케일을 지원해야 하나? 현재는? 미래에는?
지원할 숫자의 종류는? 정수? 실수? 숫자의 길이는?
천단위 구분자를 지원할 것인가?
출력만 지원할 것인가? 입력, 출력 모두를 지원할 것인가? 입출력별 지원할 숫자 형식은?
Application 종류마다 지원 범위가 다르므로 미래 요구사항까지 고려하여 스펙을 정해야 한다.

둘째, 함수 프로토타입을 정의한다.
함수가 정의되어야 국제화 라이브러리가 완성되지 않아도 Application 개발자가 사용할 수 있으므로 프로젝트 초기에 정해야 하며 나중에 바뀌지 않도록 신중하게 결정해야 한다.
보통 정수를 문자열로, 문자열을 정수로 바꿔주는 함수와 실수를 문자열로, 문자열을 실수로 변환하는 함수를 정의한다. 천단위 구분자를 옵션으로 주기도 하고 로케일에 영향을 받지 않는 옵션을 제공하기도 한다.

셋째, 함수 내부를 구현한다.
숫자 함수는 보통 L10n 라이브러리를 로케일별로 각각 개발하지 않고 시스템에서 제공하는 국제화 함수들을 사용한다. 그만큼 숫자는 일반적인 국제화 항목이라서 대부분의 시스템에서 제공한다. C언어를 사용한다면 atof(), atoi(), atoll(), strtod(), strtol(), printf(), sprintf() 등의 함수가 로케일을 바꿔주면 로케일에 따라서 다르게 동작한다. 물론 각 함수들은 와이드캐릭터(wchar_t) 버전이 따로 있으니 사용하는 문자의 데이터형에 따라서 알맞은 함수를 사용해야 한다. printf() 함수의 국제화 버전을 사용하려면 libintl라이브러리를 포함해야 한다.위 함수들이 로케일에 따라 다르게 동작하게 하려면 setlocale(LC_NUMERIC, "ko_KR")과 같이 숫자형식의 로케일을 바꿔줘야 한다. LC_ALL을 이용해서 모든 카테고리를 다 바꿔도 동작한다.

그 외에 어떤 개발언어, 라이브러리, 프레임워크를 사용하냐에 따라서 숫자함수들의 사용법이 다르니 환경에 알맞게 구현을 해야 한다. 
자세한 시스템 국제화 숫자 관련 함수의 사용법은 환경에 따라서 매우 다양하여 이 글에서 다 소개하기는 어렵다. 추가로 궁금한 것은 스스로 조사를 하던가 필자에게 직접 문의를 하는 것이 좋겠다. 
입력함수는 출력함수와는 다르게 엄격하지 않게 구현하는 것이 일반적이다. 천단위 구분자를 포함해서 입력하는 함수라도 천단위 구분자가 입력되지 않은 경우에도 처리를 하는 등 유연성을 발휘하는 것이다.

추가로 35%, 10mm 등 뒤에 단위가 붙은 숫자들도 국제화 함수로 미리 정의를 해 놓는 것이 좋다. 이또한 나라별로 국가별로 어떤 표기법으로 바꿔야 할지 알 수 없기 때문이다.

이렇게 숫자를 나라별, 로케일별로 제대로 표현하는 것은 소프트웨어 국제화의 시작이다.

이 글은 네이버 포스트에 게재한  입니다.

유니코드 인코딩이 이렇게 복잡하게 된 사연 (9)




유니코드 인코딩의 종류는 UTF-1, UTF-5, UTF-6, UTF-7, UTF-8, UTF-9, UTF-16, UTF-18, UTF-32, UTF-EBCDIC, UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE와 같이 다양하다. 지금은 사장된 것들도 있지만 초급 개발자에게는 여간 헷갈리는 것이 아니다. 

애초에 ASCII가 아니고 유니코드를 사용했다면, 특히 4바이트 유니코드를 사용했다면 지금과 같이 수많은 유니코드 인코딩 때문에 헷갈거나 변환을 해야 하는 번거로움은 없었을 것이다. 현재와 고대 문자만 표현한다면 3바이트로도 충분하지만 4바이트가 컴퓨터 연산에 편리하고 먼 미래에 어떻게 될지 모르기 때문에 4바이트가 안전할 것이다.  하지만 뒤늦게 나온 유니코드는 ASCII와 호환성 등 여러 가지 이유로 인해서 수많은 인코딩이 존재한다. 

인류가 유일한 표준 문자세트로 4바이트 유니코드를 사용하고 있다면 영어 문서를 저장하기 위해서 지금의 4배 용량의 저장장치가 필요하다. 지금은 별일 아니지만 과거에는 저장 용량은 매우 큰 이슈였다. 필자가 대학생 때 샀던 PC에는 40만원 주고 산 40MB짜리 그 당시로는 대용량 HDD가 달려 있었다. 지금은 2바이트, 4바이트 유니코드가 있지만 여전히 저장 용량 절약 이슈가 존재한다. 언제가 될지 기약할 수 없지만 모든 시스템과 파일이 4바이트 유니코드 단일 체계를 사용하는 날이 올 것이다. 몇십년 후일지 몇백년 후일지는 모르겠다.
(유니코드 인코딩과 타 인코딩 간의 관계)

그럼 유니코드 관련 주요 인코딩의 특징 및 관계, 사연에 대해서 알아보자. 지금은 거의 쓰지 않는 인코딩은 알 필요가 없다.

1. MBCS (Multi Byte Character Set)

유니코드 인코딩이 아니며 ASCII에서 표현하지 못하는 각국의 문자를 표현하기 위해서 사용하는 수많은 문자세트와 인코딩이다. 보통 1,2바이트를 사용하며 ASCII를 포함한다. 나라별로 로케일별로 제각각 이어서 서로 호환이 안 된다. 아직도 수많은 Application이 유니코드 기반이 아니고 MBCS 기반이며 소프트웨어 국제화의 발목을 잡고 있다. Application이 MBCS 기반이어도 Windows는 내부적으로 유니코드(UCS-2)를 쓰기 때문에 Application과 OS를 오가며 문자열들은 계속 변환이 일어나고 이런 과정에서 예상치 못하게 깨지는 문자들이 나오게 된다. 최근의 새로운 개발 환경이나 플랫폼들은 아예 MBCS를 지원하지 않고 유니코드 Application만 제작할 수 있는 경우도 많다. 이런 환경에서만 개발하는 개발자는 MBCS가 뭔지도 모르고 자연스럽게 유니코드 Application을 만들 것이다.

2. UCS-2 (Unicode Character Set-2)

유니코드 문자세트이며 인코딩이다. Windows는 내부에서 UCS-2를 사용한다. C언어에서는 wchar_t(유니코드문자 자료형)로 표현한다. Unix나 Linux에서는 wchar_t는 UCS-4이다. Unix나 Linux에서는 한 글자에 4바이트이니 주의를 하자. 모든 문자가 깔끔하게 2바이트라서 연산이 매우 빠르다. 100바이트에는 딱 50글자가 들어간다. 하지만 ASCII 문자를 표현하기 위해서 NULL문자가 포함되어 있어서 기존의 char * 자료형에는 담을 수가 없다. 또한 문자열 관련된 수많은 함수를 다시 만들어야 했다. 파일에 그대로 기록할 경우 NULL문자로 인해서 기존의 Application이 텍스트 파일이 아닌 바이너리 파일로 인식하는 문제가 있다. 주로 Application 내부 데이터 용도로 사용한다고 보면 된다.

3. UTF-8 (Unicode Transformation Format-8)

유니코드 중에서 가장 널리 쓰이는 인코딩이다. 1~6바이트를 사용한다. BMP만 표현한다면 4바이트까지만 사용하면 된다. 특히 파일저장, 데이터 전송 등에 많이 사용한다. UTF-8의 가장 큰 장점은 위 그림에서도 보이듯이 ASCII, ISO8859-1과 호환된다는 것이다. 즉, UTF-8 문서를 처리하는 소프트웨어는 ASCII와 ISO-8859 문서를 변환 없이 그대로 처리할 수 있다. 반대로 UTF-8 인코딩으로 저장한 문서가 과거 미국이나 독일 Application에서 읽힌다. 물론 지원하지 않는 문자는 깨져서 보일 것이지만 열리기는 할 것이다. 미국과 유럽, 특히 영어권에 매우 유리한 인코딩이다. 이는 UTF-8이 가장 널리 쓰이는 이유이기도 하다.

영어로 된 문서를 저장할 때 용량이 매우 작다. 하지만 한국어(한글)는 한 글자당 3바이트로 용량이 낭비된다. 이또한 UTF-8이 인기있는 이유중 하나다.



그 외에도 UTF-8은 여러 장점이 있다. 데이터를 중간부터 보더라도 몇 바이트만 보면 UTF-8 인코딩인지 알 수 있고 데이터 손실도 거의 없다. UTF-8 데이터 구조는 처음부터 이런 장점을 가지도록 설계가 되어 있는데 이를 설계한 사람의 천재성을 엿볼 수 있다.

또 하나의 장점은 문자열 중간에 NULL(0x00)문자가 나오지 않는다는 것이다. 따라서 파일에 저장해도 바이너리 파일로 인식하는 오류가 없고, C언어와 같이 Null terminated string의 자료형에 담을 수가 있다. 이는 하위호환성에서는 큰 장점이 된다.

단점으로는 가변 바이트를 사용하여 글자 개수를 세려면 한 글자씩 개수를 세야 하는 등의 연산 오버헤드가 있다.

4. UTF-16 (Unicode Transformation Format-16)

유니코드 컨소시엄에서 제안한 인코딩이다. UCS-2와 거의 동일하기 때문에 혼동해서 사용하는 경우가 많다. 하지만 UTF-16은 가변 길이이기 때문에 2바이트 고정 길이를 지칭한 경우라면 UCS-2를 말하는 것을 UTF-16이라고 잘못 부르는 것이다. 최초의 UTF-16은 UCS-2와 동일 했기 때문에 그 잔재가 아직도 남아 있는 것 같다.
Java에서는 문자열의 인코딩에 기본적으로 UTF-16(BE)를 사용한다. Java로 개발을 할 때도 문자 인코딩을 제대로 이해하고 있어야 깨지지 않고 제대로 처리를 할 수 있다.

5. UTF-32 (Unicode Transformation Format-32)

4바이트로 고정된 UCS-4와 동일한 인코딩이다. (현재까지는) 모든 문자들이 UTF-32로 통일된다면 인코딩 변환이 필요 없는 세상이 올 것이다. 하지만 저장 공간 낭비가 심해서 인류가 저장 용량에서 완전히 해방되는 날이 되기 전까지는 유일한 표준이 되기는 어려울 것이다. 또한 UTF-8과는 다르게 데이터를 중간부터 보면 어디가 글자의 시작인지 알 수가 없는 단점이 있다.

이 외에도 수많은 유니코드 인코딩이 생겨났지만 거의 사장되다시피 했다.

Byte Order와 BOM 이슈는 나중에 다루도록 하겠다. 

국제화를 이해하기 위한 기초를 다루다 보니 여러 회가 지났는데 다음부터는 본격적인 내용과 기초를 교대로 다뤄볼까 한다.

이 글은 네이버 포스트에 게재할 입니다.

2015년 6월 23일 화요일

한국어(한글) 코드 이야기 (8)


유니코드에 대해서 좀더 알아보기 전에 한국어 코드(문자세트)와 인코딩에 대해서 좀더 알아보자. 

1991년에 유니코드가 탄생한 후에 유니코드는 점점 많은 개발자들이 사용하기 시작해서 영역을 넓혀가고 있다. 물론 언젠가는 유니코드로 통일되는 시기가 올 수도 있겠지만 아직까지는 한국어만 해도 여러 문자세트와 인코딩이 공존을 하고 있고 소프트웨어 개발자에게는 여간 귀찮은 일이 아닐 수 없다. 이에 대해서 잘 알고 있고 경험이 많은 개발자라면 별 문제가 없지만 한국어 문자세트와 인코딩에 대해서 조금 헷갈리는 경우라면 소프트웨어를 개발하면서 수많은 지뢰를 만나게 된다. 그나마 한국어는 중국이나 일본에 비해서는 문자세트와 인코딩의 표준화가 잘되어 있어서 훨씬 수월한 편이라고 할 수 있다.

이제 유니코드가 대세라고 Application을 유니코드를 지원하도록 만든다고 다 해결되는 것은 아니다. 네트워크를 통해서 다른 시스템과 통신을 할 때 다른 인코딩을 사용할 수도 있고, 파일로 저장할 때 다른 형식을 써야 할 때도 많다. 왜냐하면 아직도 수많은 시스템이 유니코드를 지원하지 않고 있고, 우리는 그런 시스템과도 연동을 해야 하기 때문이다.

데이터베이스의 인코딩을 어떤 것을 선택할지도 큰 이슈다. 지금은 누구나 유니코드의 인코딩을 선택하지만 몇 년 전까지만 해도 EUC-KR이나 다른 인코딩을 선택하기도 했다. 이런 경우 시스템의 국제화를 추진하게 되면 심각한 문제에 봉착하게 된다. 데이터베이스 이슈는 추후 별도로 다루도록 하겠다.

우리는 Application을 개발하면서 외부 라이브러리를 사용하기도 한다. 그런데 외부 라이브러리가 유니코드 기반이 아닌 경우도 많다. 그럴 때는 라이브러리를 사용하기 위해서 문자열을 변환해야 하는 복잡한 문제에 봉착하기도 한다. 상황에 따라 다르지만 유니코드와 여러 가지 인코딩이 공존하는 세상에서 개발해야 하는 개발자들은 이런 문제를 겪기도 하고 해결해야 한다는 것을 알아야 한다.

그래서 현재 한국어를 표현하기 위해서 사용되고 있는 문자세트와 인코딩에 대해서 간단하게 집고 가려고 한다. 물론 문자가 깨지는 경우에 이를 해결하는 데는 경험이 훨씬 중요하다. 하지만 문자세트와 인코딩의 지식은 기본으로 필요하다.
(한국어 문자세트와 인코딩 포함 관계)


1. KSC 5601 (문자세트)

한국산업규격 한국어문자집합이다. 공식 명칭은 KS X 1001이다. 1974년 처음 제정되었고 2004년에 마지막 개정이 있었다. 2바이트 부호체계이며 8,836문자를 규정하고 있다. 한글, 숫자, 특수문자, 한자, 로마자, 그리스문자, 키릴문자 등으로 구성되어 있다. 
한글 2,350자와 한자 4,888자를 정의하고 있는데 실제 사용하는 한글과 한자에 비해서는 턱없이 부족한 문제가 있다.

2. EUC-KR (인코딩)

문자세트는 실제 컴퓨터에서 사용하는 코드는 아니다. 컴퓨터에서는 인코딩을 사용하며 EUC-KR은 널리 사용되는 한국어 인코딩 중 하나다. KSC 5601을 그대로 포함하고 있으며 영어 영역은 ASCII를 사용한다. 정확하게는 KS X 1003인데 ASCII라고 알고 있어도 무방하다. EUC-KR은 KSC 5601이 가지고 있는 문제를 그대로 가지고 있다.

3. ISO-2022-KR (인코딩)

지금은 쓸 이유도 없는 인코딩이지만 하위 호환 때문에 가끔 만나게 된다. ISO2022는 원래 둘 이상의 문자집합을 한꺼번에 표현하기 위한 국제 규약이었는데 여기에 한국어를 추가했다. 지금은 상상이 잘 안되지만 과거에는 Email을 전송할 때 7bit로만 전송하던 시절이 있었다. ISO-2022-KR은 8bit를 사용하는 KSC 5601 문자를 7bit로 전환해서 Email를 전송할 수 있게 하였다. 그럼으로써 ISO-2022-KR을 지원하는 Email 클라이언트에서 한국어 Email을 볼 수 있게 되었다. 코드 중간에 0x0e 문자를 만나면 이제부터 한국어라는 뜻이고 0x0f를 만나면 영어로 간주를 한다. 


지금은 Email을 8bit로 전송할 수도 있고, Base64로 인코딩해서 보내는 방법도 있어서 ISO-2022-KR은 쓸 이유가 점점 없어지고 있다. 또한, Email을 아예 유니코드(UTF-8)로 보내는 비율이 점점 늘고 있다. 유니코드로 Email을 보낸다는 의미는 내가 보낸 Email이 전세계 어디서나 문제없이 볼 수 있다는 의미다. 물론 Email 클라이언트가 유니코드를 지원해야 하며 유니코드 폰트가 존재해야 한다. 아직은 모든 시스템이 유니코드의 모든 문자의 폰트를 포함하고 있지 못하다. 이 이슈는 추후 폰트 관련 글에서 따로 다루겠다.

4. CP949 (인코딩)

마이크로소프트에서 사용하는 한국어 코드페이지다. 원래는 KSC5601을 표현하는 코드페이지였으나 KSC5601에 없는 한글 문자가 많아서 8822자의 현대한글을 추가하였다. 이렇게 문자를 새로 추가하다 보니 한국어 코드의 순서가 가나다 순서와는 다르게 뒤죽박죽이 되는 문제가 있다. 하지만 EUC-KR에서는 표현할 수 없는 많은 문자를 표현 할 수 있다. Windows에서 파일로 “똠방각하”를 저장할 때 ANSI 형식 선택해도 KSC5601에 없는 “똠”라를 저장할 수 있는 이유는 Windows는 EUC-KR 대신 CP949를 선택하기 때문이다. 따라서 CP949의 모든 문자를 EUC-KR 인코딩으로 변환할 수는 없다. 이런 변환 과정을 거치면 몇몇 글자는 깨질 수가 있다. OS에 따라서 한국어 문자가 깨지는 상황이 발생했을 때 OS가 어떠한 인코딩을 사용하는지 살펴볼 필요가 있다.



지금은 위 4가지가 아니라면 거의 유니코드일 것이다. 유니코드에 대해서는 추후 자세히 다룰 것이다.

2015년 6월 16일 화요일

유니코드 영토 전쟁의 승리자는? (7)



이번에는 유니코드의 코드 체계에 대해서 간단하게 알아보고자 한다. 소프트웨어 국제화를 필요로 하는 개발자라면 자주는 아니지만 유니코드 내부 코드 체계를 알아야 할 때가 있다. 다양한 플랫폼에서 개발을 할 때 폰트 등과 관련하여 문자가 깨지는 등 복잡한 문제에 봉착할 수 있다. 이때 유니코드의 체계의 원리를 아는 것은 문제를 해결하는데 도움이 될 것이다.

지구상의 문자를 모두 하나의 문자 코드에 집어 넣는 유니코드를 만드는 작업은 쉬울 수가 없었다. 고서적에 쓰는 문자들도 코드화를 해야 하기 때문에 유사이래 탄생한 모든 문자를 포함해야 했다. 그 중에서 압권은 중국어 즉, 한자다. 현재까지 알려진 한자만 10만자가 넘는다는 설이 있고 공식 한자만 8만자가 넘는다. 그러니 2바이트 유니코드 65,536 글자에는 중국의 한자도 다 들어 갈 수 없었다. 

두번째로 문자가 많은 나라는 한국이다. 현대 한국어 글자는 조합 가능한 문자가 1만자가 넘고 한자도 1만5천자는 사용을 한다. 물론 KSC5601에서는 한글 2350자, 한자 4888자를 정의하고 있지만 모든 글자를 표현하기에는 턱없이 모자란 숫자다. 게다가 고어까지 모두 포함하면 조합 가능한 글자는 백만자가 넘는다고 한다. 물론 실제 사용하는 글자는 훨씬 적다.

이런 상황이라면 유니코드 65,536 글자 안에 어느 나라 글자가 많이 포함되느냐가 관건이 될 수 있다. 

1991년 유니코드 1.0에서는 한국어는 완성형코드가 포함되어 있었고 표현 못하는 글자가 수두룩하고 배열도 엉망이었다. 하지만 1996년에개정된 유니코드 2.0에는 한글 조합형의 모든 글자와 옛한글을 표현할 수 있는 코드 11,172개와 한글 자모가 포함되었다. 그 과정에서 유니코드 1.0에 포함된 한글코드는 사장시키고 새로운 코드영역으로 이동을 했는데 이런 대규모 이동은 유니코드 역사상 획기적인 일이었다. 유니코드 2.0부터는 한국어 표기 문제가 거의 해결되었다고 볼 수 있다. 그로인해 유니코드 1.0을 지원하는 소프트웨어가 유니코드2.0과 호환이 안되서 초기에는 불평이 많았지만 이제는 옛날 얘기가 되었다.


한자는 한국, 중국, 대만, 일본, 베트남 등에서 공통으로 많이 쓰는 한자를 통합하여 약 2만7천자를 할당하였다. 그 외의 한자는 다른 Plane에 포함되었다. BMP에 포함된 2만7천자의 한자는 2바이트로 표현이 가능하지만 나머지 한자는 4바이트를 사용해야 표현할 수 있다. 중국의 고서적을 표현할 때는 4바이트 코드를 써야 하며 한국의 옛한글도 코드는 있지만 전용 소프트웨어가 필요하다.

유니코드의 역사는 훨씬 더 복잡하지만 이 정도로 간단히 알아보고 유니코드 안에는 어떠한 글자들이 있는지 구경이나 한번 해보자. 대표적인 코드 영역을 몇가지 소개한다. 굳이 암기할 필요도 없고 미래에 문자가 깨지는 상황이 발생할 때 약간의 도움이 될 때가 있을 것이다.

문자들은 환경에 따라서 폰트의 지원여부 때문에 깨져 보일 수가 있으니 이미지로 표시를 했다.
 

다음 시간에는 한국어의 코드체계와 유니코드 인코딩에 대해서 알아보도록 하겠다.

2015년 6월 9일 화요일

유니코드는 어떻게 탄생했을까? (6)


소프트웨어 국제화를 이해하기 위해서는 유니코드에 대해서 필수적으로 잘 알아야 한다. 유니코드란 말을 들어보지 않은 개발자는 없지만 관련 용어가 매우 많아서 종종 헷갈린다.  게다가 유니코드가 탄생한지 20년도 더 넘었지만 아직도 세상은 유니코드로 통일이 되지 못하고 수많은 문자세트가 넘쳐나고 소프트웨어를 개발하다보면 수시로 문자가 깨지고 문제가 발생한다. 그래서 유니코드를 비롯해서 문자세트와 인코딩에 대해서 간단하게 알아볼 기회를 가지려고 한다. 

먼저, 유니코드는 언제, 왜 탄생했는지 그 역사를 간략하게 알아보자. 필자는 유니코드 탄생 이전부터 개발을 했기 때문에 그 역사를 보아 왔다고 할 수 있다. 유니코드의 역사를 알아보기 위해서 그 이전의 문자세트, 문자인코딩의 역사를 거슬러 올라가보자. 

유니코드를 설명하려면 문자세트 문자인코딩이라는 용어를 구분해야 한다. 흔히 헷갈려 하는 용어다. 문자세트는 그야말로 문자들의 집합이다. 문자들의 집합에 각 문자에 번호를 부여한 것이다. 문자인코딩은 그런 문자들을 어떻게 코드를 할당하느냐를 나타낸 것이다. 문자세트를 특별한 변화 없이 그대로 1:1로 나타내는 문자인코딩도 있고, 별도의 규칙에 의해 변경해서 표기하는 문자인코딩도 있다. KSC5601은 문자세트고 이를 영문자와 합쳐서 그대로 인코딩 한것은 EUC-KR이다. 앞으로 문자세트와 인코딩을 마구 섞어서 사용할 것인데 혼동하지는 말자.

1950년대 최초로 컴퓨터가 탄생하고 초창기 컴퓨터에는 표준 문자세트이라는 것이 없었다. 즉, 컴퓨터마다 다른 문자세트를 사용하고 있었다. 그래서 1967년 미국에서 표준 문자세트를 제정한 것이 ASCII다. 미국에서 만들었기 때문에 알파벳과 숫자 등의 글자로 이루어졌다.

ASCII는 7비트 128글자를 사용하며 거의 모든 문자세트의 기본이 된다. 하지만 ASCII는 유럽글자를 표현 할 수 없었다. 그래서 유럽 사람들은 1980년대 중반 ASCII를 확장하여 ISO-8859를 만들게 된다. ISO-8859의 특징은 기존 ASCII 영역을 건들지 않고 8비트 128글자 영역을 사용하여 미국에서 작성한 문서도 그대로 볼 수 있게 하였다.

ISO-8859-1은 네델란드어, 노르웨이어, 독일어 등 주로 서유럽의 언어를 지원한다.
ISO-8859-2은 체코어, 폴란드어, 헝가리어 등 주로 중앙유럽의 언어를 지원한다.
ISO-8859-3은 터키어 등 주로 남유럽의 언어를 지원한다. 이런 식으로 ISO-8859-16까지 추가되었는데 암기할 필요는 없다. ISO-8859를 사용해도 여러 유럽어를 동시에 표현할 수는 없었다.

그 무렵 아시아에서는 문자세트 혼란의 시기가 도래하였다.

한국에서는 1980년대 초부터 여러 가지 한글 조합형 인코딩을 사용했다. 1987년 KSC5601(KSX1001)이라는 한글(한국어) 완성형 문자세트가 제정된 후 조합형과 완성형은 공존을 하다 조합형은 사라지게 된다. 조합형과 완성형의 팽팽한 균형이 무너진 시점은 윈도우95가 나오면서부터다. 그럼에도 불구하고 그 당시 똠방박하의 "똠"자를 윈도우에서 쓸 수 없다는 것은 많은 이슈가 되었다.


중국과 일본도 제 각각의 문자세트와 인코딩을 정의해서 전세계, 특히 아시아는 문자세트 춘추 전국시대가 되었다. 한나라 안에서도 수많은 문자세트와 인코딩이 넘쳐나고 있었다. 이는 전세계 컴퓨터, 소프트웨어가 서로 호환되지 않는다는 의미를 얘기한다. 알파벳과 숫자를 제외하고는 깨져버리기 일쑤였다.

하나의 인코딩으로 영어와 한국어는 표시할 수 있고, 영어와 일본어도 표현을 할 수 있다. 하지만 영어, 한국어, 일본어, 중국어 이렇게 다양한 언어를 한꺼번에 표현할 수는 없었다. 그래서 탄생한 것이 ISO2022다. 중간에 특수한 문자를 만나면 문자세트가 바뀌는 것이다. ISO2022 인코딩의 문자열은 중간부터 읽을 경우 무슨 문자인지 알 수 없는 약점이 있었다.

80, 90년대 이런 춘추전국 시대에 개발을 해본 개발자라면 이런 혼란을 잘 알고 있을 것이다. 근래에 개발을 시작한 개발자들에게는 먼 옛날 얘기일 것이다.

그 당시에는 대부분의 소프트웨어가 나라별 버전을 따로 만들곤 했다.

이러한 혼동 속에서 하나의 문자세트로 전세계 문자를 모두 표현하려는 움직임이 있었고, 썬마이크로시스템즈, 애플, MS, IBM, 볼랜드 등의 회사들이 유니코드컨소시엄을 만들어서 전세계 문자를 통합한 유니코드(Unicode)를 만들기 시작했다. 참여한 회사들을 보면 거의 미국 회사인 것을 알 수 있다. 미국 회사들이 전세계에 소프트웨어를 팔다보니 본인들이 힘들어서 통합의 필요성을 느낀 것이다. 그렇게 미국이 주도하여 1991년 유니코드 1.0이 탄생한다. 

(유니코드에 포함된 나라별로 다른 문자들)

이렇게 제정된 유니코드의 문자세트는 UCS-2(Universal Character Set 2)라고 불린다. UCS-2를 가지고는 고어를 포함한 전세계 모든 문자를 표현하는데는 한계가 있다. 사실 중국어만 해도 10만 글자가 넘는다. 그래서 UCS-4에는 고대 언어를 포함한 모든 언어가 포함된다. 하지만 우리는 대부분 UCS-2를 사용하며 유니코드라고 말하면 UCS-2를 의미하는 경우가 많다. 단, Unix계열의 OS에서는 4바이트 문자세트인 UCS-4를 기본으로 사용한다.


향후 소개될 소프트웨어 국제화의 내용을 쉽게 이해하기 위해서는 문자세트, 인코딩 그리고 유니코드에 대해서 잘 알아야 한다. 그래서 본 글에서 간략히 소개를 했다. 다음에는 유니코드의 내부를 좀 살펴보고 인코딩 등 유니코드에 대해서 조금 더 알아보자.

이글은 네이버포스트에 게재한 글입니다.