본문 바로가기

컴퓨터 기본 지식

문자 인코딩에 대한 이해

728x90
반응형

Goal

  • 문자열 인코딩 방식에 대한 이해
  • 유니 코드에 대한 이해
  • C++환경에서 인코딩 방식 이해

우선 컴퓨터가 문자를 어떻게 저장하고 표현하는지에 대해 알아보자

 

그래픽 문자(graphic character) : 컴퓨터 상에서 시각적으로 표현 되는 문자

  • 화면상 픽셀로 표현되서 우리 눈에 보이는 문자를 의미한다.

문자(character) : 단일 문자를 표현하기 위한 비트의 나열

  • 문자는 하나의 그래픽 문자와 맵핑되어 표현될 수 있다.
  • 예를 들어 0110 0001 (숫자 97) 이진수는 그래픽 문자 'a'로 표현될 수 있다.

문자열(string) : 연속적인 문자의 나열

  • 메모리 상에 문자를 연속적으로 배치한 것으로, 마찬가지로 비트의 나열이다.

문자는 이진수로 저장되는 값일 뿐이다. 그렇다면 문자는 몇바이트의 이진수로 저장될까? 1byte?, 2byte?

결론부터 말하자면 이 포스트의 핵심 주제인 문자열 인코딩에 따라 달라진다.

1byte로 저장될 수도 있고, 2,3,4 byte로 저장 될 수도 있다.

ex)

  • 'a'를 utf-8로 저장하면 1바이트, utf-16으로 저장하면 2바이트이다.
  • '가'는 utf-8로 저장하면 3byte이고, utf-16로 저장하면 2byte이다.
  • 또한, 같은 byte를 갖는 문자라고 하더라도 내부적인 비트 배열이 다를 수 있다. 예를 들어 한글 '가'를 cp949로 인코딩 하면 0xb0, 0xa1로 표현되고, utf-16으로 인코딩 하면 0xac, 0x00로 표현 된다.

인코딩에 대한 이해가 아직 부족하다면 아니 이게 무소린가? 할 수 있다. (나도 그랬다..)

 

그렇다면 인코딩이 무엇이고 왜 다르게 저장되는지 알아보도록 하자.

 

문자 인코딩 관련 용어

문자셋(character-set) : 그래픽 문자의 집합으로 하나의 그래픽 문자는 그 문자를 나타내는 숫자에 대응된다.

문자셋은 ASCII 코드표와 같이 숫자(문자)와 그래픽 문자가 대응되는 하나의 테이블이다.

 

(문자)인코딩(encoding) : 문자를 다른 문자셋에 있는 코드 값으로 변환 하는 방법

인코딩이란 단어는 원래 기존 형태를 어떤 처리 과정을 통해 다른 형태로 변환 시키는 과정을 의미하는데, 여기서도 같은 의미로 사용되었다. 기존 문자를 어떤 변환 과정을 거쳐 다른 문자셋에 있는 문자로 변환한다는 의미이다. 

 

디코딩(decoding) : 사용자가 사용하는 인코딩 형태(encoding form)로 변환하는 방법

(영어 -> 한국어)과 같이, 번역의 의미로 생각하면 될 것 같다.

 

문자 인코딩 형태(character encoding form, CEF)

utf-8, utf-16과 같이 인코딩 하는 특정 방법을 의미 한다.

인코딩은 변환하는 과정 자체를 의미한다면 문자 인코딩 형태는 인코딩하는 기법을 의미한다.

 

코드 페이지(code page) : 인코딩 방식을 의미하며, 사용하는 문자 셋이나 플렛폼, 인코딩 방식에 따라 다른 번호가 부여 되어 있다.

ex)

  • cp 949 : 한국어로 설치한 윈도우에서 기본적으로 제공하는 인코딩 방식으로 ASCII range값(숫자나 알파벳 등)은 1byte로 표현되고 한글은 2byte로 표현된다.
  • cp1200 : utf-16 little endian 인코딩
  • cp1201: utf-16 big endian 인코딩

 

인코딩과 문자셋

더보기

문자셋 종류 : ASCII CODE, UNICODE, KS X 1001, KS X 1003 등

인코딩 종류 : EUC-KR, CP949 등

 

1. 인코딩은 하나 이상의 문자셋을 사용할 수 있다.

예를들어 EUC-KR은 KS X 1001, KS X 1003문자셋 두개를 사용한다. (한글 지원 문자셋, 로마 문자셋)

 

- EUC-KR : KS X 1001와 KS X 1003을 사용하는 8비트 문자 인코딩, EUC의 일종이며 대표적인 한글 완성형 인코딩이기 때문에 보통 완성형이라고 불린다.

=> 여기서 EUC는 인코딩 방식중 하나이고 EUC-KR은 한국어 문자셋을 EUC방식으로 인코딩하는 인코딩 방식이다.

 

2. 동일한 문자셋을 사용하더라도 다른 인코딩 사용 방식을 사용할 수 있다.

예를 들어 KS X 1001 문자셋은 EUC-KR, CP-949 인코딩에서 사용될 수 있는 문자 셋이다.

 

3. 동일한 인코딩 방식을 사용하더라도 문자마다 저장되는 byte 수가 다르다.

CP-949의 경우 한글지원 문자셋 KS X 1001과, 로마 문자셋 KS X 1003을 사용한다.(이하 1001, 1003) 이때, 한글은 1001 문자셋에 있는 코드와 연결되어 변환되는데 이 코드 값이 2byte로 표현된다. 반면, 1003의 경우 ASCII 문자를 기반으로 한 문자셋이기 때문에 1byte로 표현된다.

Visual Studio 콘솔창에서 한글과 영어를 동시에 입력한 문자열을 char 배열에 저장하면 한글은 2byte, 영어는 1byte로 저장되는 이유이다.  

 

SBCS vs DBCS vs MBCS vs UCS

더보기

SBCS (Single Byte Character Set) : 개별 문자를 1byte로 표현한 문자셋

ex) ASCII코드

 

DBCS (Double Byte Character Set) : 개별 문자를 2byte로 표현한 문자셋

ex) KS X 1001 (한글 지원 문자셋 - 2byte)

 

MBCS (Multi Byte Character Set) :  개별 문자를 1 또는 2 byte 로 표현한 문자셋

이름은 멀티지만 1,2 바이트에 대해서만 다룬다.

0x00~0x7F는 ASCII 코드값을 공유(1byte표현)하고 나머지는 각 나라마다(1byte or 2byte) 다름

(MBCS는 SBCS와 DBCS를 다룬다)

 

UCS (Universal Character Set) : 국제 표준 문자 집합. 전 세계의 문자를 모두 표현할 목적으로 만들어진 문자셋

I18N? i18n?

더보기

국제화와 지역화는 제품을 언어 및 문화권 등이 다른 여러 환경에 대해 사용할 수 있도록 지원하는 것을 의미한다. 이때 국제화는 제품 자체가 여러 환경을 지원할 수 있도록 제품을 설계하는 것을 의미하며, 지역화는 제품을 각 환경에 대해 지원하는 것을 의미한다.

국제화(internationalization)를 i18n으로, 지역화(localization)를 l10n 등으로 표기하기도 한다.

이것은 두 단어의 영어 철자에서 첫 글자와 마지막 글자만 뺀 나머지 글자들을 그 숫자로 표시한 것이다.

 

다국어화(multilingualization, m17n)는 국제와(i18n)과 동일한 의미이다.

ex) Unicode : i18n을 위해 만든 character set

 

국제화 또는 지역화는 다음과 같은 것들에 초점이 맞춰져있다.

  • 언어 (문자, 음성, 자막 등)
  • 시간대
  • 통화(currency)
  • etc...

 

BOM 이란?

더보기

BOM (Byte Order Mark) : 유니코드 문자, U+FEFF 바이트 순서 표식(BOM)

 

이 표식은, 문서의 시작 부분에 표시되며 다음과 같은 것들을 알 수 있게 해준다.

  • 유니코드 사용 문서이다.
  • 문서의 바이트 순서를 알려준다. (big endian, little endian)
  • 유니코드 인코딩 방식 중 어떤 인코딩이 사용 되었는지 (utf-8, utf-16 등)
인코딩 BOM
UTF-8 EF BB BF
UTF-16 Big Endian FE FF
UTF-16 Little Endian FF FE
UTF-32 Big Endian 00 00 FE FF
UTF-32 Little Endian FF FE 00 00

BOM이 있을 경우 위와 같은 정보를 알 수 있기 때문에, Decoding 하는데 유리하다.

일반적으로 BOM은 보이지 않고, Hex Editor로 열어야 확인이 가능하다.

 

참고)

유니코드 표준은 UTF-8의 BOM을 허용하지만, 그것의 사용은 필수가 아니다. UTF-8에서 바이트 순서는 어떤 의미도 없어서(2byte 이상일 경우에만 little, big endian 의미가 있다), UTF-8에서 사용되는 경우 텍스트 스트림이 UTF-8로 인코딩되었거나, 혹은 다른 BOM을 포함한 또 다른 스트림에서 변환되었다는 것을 알리기 위함이 유일한 용도이다.

 

일반으로 편집기에서 UTF-8파일을 열 때 BOM이 없을 경우 추가한다.

이경우 기존 파일과 달라질 수 있어 문제가 될 소지가 있다. 만약 문제가 되는 상황이라면 BOM을 추가 하지 않도록 해야한다. 


유니코드(Unicode)의 등장 배경

유니 코드가 등장하기 이전에는 여러언어를 포함하는 하나의 단일 character set이 없었다. 그래서 각 소프트웨어 회사마다 내부적으로 character set을 만들어 사용했다. 그러다보니 호환에 어려움이 있었고, 결국 유니코드 컨소시엄(Unicode Consortium)을 결성하여 이를 통합하는 문자셋을 만들게 되었다.

 

유니코드(Unicode)

전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준

 

유니코드 협회(Unicode Consortium)가 제정한 표준으로, 단순히 character set 또는 인코딩 방식만을 의미하는 것이 아니다. ISO 10646 문자 집합, 문자 인코딩, 문자 정보 데이터베이스, 문자들을 다루기 위한 알고리즘 등과 같은 모든 것들을 다 포함한 개념이다.

 

대표적인 유니코드 인코딩 형태(encoding form)

  • UTF-8 : 문자를 최소 1 부터 최대 4 바이트로 표현한다. (즉 문자마다 길이가 다르다!)
  • UTF-16 : 문자를 2 혹은 4 바이트로 표현한다. 
  • UTF-32 : 문자를 4 바이트로 표현한다. (공간 낭비로 인해 잘 사용하진 않는다)

*UTF : Unicode Transformation Format - Universal Coded Character Set + Transformation Format – N-bit

 

UTF-8

  • 코드 단위가 8bit(1byte)이다. byte 단위 연산이 가능
  • 가변 길이 인코딩 방식 (대부부분 3byte이내의 문자이며, 아직 까진 4byte 이내로 모든 문자로 표현 가능하다)
  • 인터넷에서 상에서 가장 많이 사용되고 있는 유니코드 인코딩 폼이다.

U+0000부터 U+007F 범위에 있는 ASCII 문자들은 1바이트만으로 표시 => 기존 ASCII 기반 문서와 호환 가능

 

다음 그림은 UTF-8 변환 구조이다.

위키 UTF-8 참고

7bit range는 ASCII 범위와 동일하고, 그 이후부터는 위 구조에 맞게 변환됩니다. ASCII와 구분하기 위해 8번째 비트를 1로 사용한다.

 

UTF-16

  • 코드 단위가 16bit(2byte)이다.
  • 한 문자를 나타내기 위해 2byte 또는 4byte를 사용한다.
    • 주로 사용 되는 기본 다국어 평면(BMP, Basic Multilingual Plane)에 속하는 문자(U+0000~U+FFFF 범위)는 2byte로 표현된다.
    • 그 이후 문자에 대해서는 4byte(2byte + 2byte)로 표현된다.

Byte Order를 big endian으로 할지 little endian으로 할 지에 따라서 바이트 배열이 달라 질 수 있고,

BOM을 사용할 경우 문서의 시작 위치에 BOM이 표시될 수 있다.

UTF-16 위키 참고

BMP (참고)

더보기

유니코드 평면(Plane) : 유니코드를 논리적으로 나눈 구분

0번(다국어 기본 평면)에서부터 16번까지 모두 17개로 나뉘며, 각 평면은 65536($2^{16}$)개의 코드로 구성된다.

 

다국어 기본 평면(영어: Basic multilingual plane, BMP)

유니코드의 첫째(0번) 평면으로, U+0000부터 U+FFFF까지의 영역을 차지한다.

 

BMP에는 거의 모든 근대 문자와 특수 문자가 포함되어 있으며, 그 중 대부분은 한글과 한중일 통합 한자들로 이루어져 있다. 

유니코드 다국어 기본 평면을 그림으로 나타낸 것이다.

유니코드 평면(Plane) : 유니코드를 논리적으로 나눈 구분

0번(다국어 기본 평면)에서부터 16번까지 모두 17개로 나뉘며, 각 평면은 65536($2^{16}$)개의 코드로 구성된다.

 

다국어 기본 평면(영어: Basic multilingual plane, BMP)

유니코드의 첫째(0번) 평면으로, U+0000부터 U+FFFF까지의 영역을 차지한다.

 

BMP에는 거의 모든 근대 문자와 특수 문자가 포함되어 있으며, 그 중 대부분은 한글과 한중일 통합 한자들로 이루어져 있다. 

유니코드 다국어 기본 평면을 그림으로 나타낸 것이다.

Basic multilingual plane, BMP

C++ 문자열 리터럴 표현 방식

#include <string>
using namespace std::string_literals; // enables s-suffix for std::string literals

int main()
{
    // Character literals
    auto c0 = 'A'; // char
    auto c1 = u8'A'; // char
    auto c2 = L'A'; // wchar_t
    auto c3 = u'A'; // char16_t
    auto c4 = U'A'; // char32_t

    auto u2 = '\101';       // octal, 'A'
    auto u3 = '\x41';       // hexadecimal, 'A'
    auto u4 = '\u0041';     // \u UCN 'A'
    auto u5 = '\U00000041'; // \U UCN 'A'

    // Multicharacter literals
    auto m0 = 'abcd'; // int, value 0x61626364

    // String literals
    auto s0 = "hello"; // const char*
    auto s1 = u8"hello"; // const char*, encoded as UTF-8
    auto s2 = L"hello"; // const wchar_t*
    auto s3 = u"hello"; // const char16_t*, encoded as UTF-16
    auto s4 = U"hello"; // const char32_t*, encoded as UTF-32

    // Raw string literals containing unescaped \ and "
    auto R0 = R"("Hello \ world")"; // const char*
    auto R1 = u8R"("Hello \ world")"; // const char*, encoded as UTF-8
    auto R2 = LR"("Hello \ world")"; // const wchar_t*
    auto R3 = uR"("Hello \ world")"; // const char16_t*, encoded as UTF-16
    auto R4 = UR"("Hello \ world")"; // const char32_t*, encoded as UTF-32

    // Combining string literals with standard s-suffix
    auto S0 = "hello"s; // std::string
    auto S1 = u8"hello"s; // std::string
    auto S2 = L"hello"s; // std::wstring
    auto S3 = u"hello"s; // std::u16string
    auto S4 = U"hello"s; // std::u32string

    // Combining raw string literals with standard s-suffix
    auto S5 = R"("Hello \ world")"s; // std::string from a raw const char*
    auto S6 = u8R"("Hello \ world")"s; // std::string from a raw const char*, encoded as UTF-8
    auto S7 = LR"("Hello \ world")"s; // std::wstring from a raw const wchar_t*
    auto S8 = uR"("Hello \ world")"s; // std::u16string from a raw const char16_t*, encoded as UTF-16
    auto S9 = UR"("Hello \ world")"s; // std::u32string from a raw const char32_t*, encoded as UTF-32
}

위 문자열 리터럴 관련 정보는 MSDN 사이트 를 참고했다.

 

C++에서 다루는 문자 자료형은 다음과 같다.

char     ch1{ 'a' };  // or { u8'a' }
wchar_t  ch2{ L'a' };
char16_t ch3{ u'a' };
char32_t ch4{ U'a' };

 

char : 1byte 자료형으로 멀티 바이트 문자(1 or 2 byte)를 저장하는 자료형이다.

 

wchar_t : wide-character를 담는 문자 자료형

유니코드 문자셋(UCS) 문자를 표현하기 위한 자료형으로, 사용 컴파일러 환경에 따라 달라 질 수 있다.

(MS 컴파일러는 UTF-16 인코딩 방식을 사용하고 있다.)

 

char8_t : 위에는 없지만 c++ 20부터 추가되는 utf-8인코딩 문자를 저장하는 자료형이다. u8'a' 형태로 표현할 수 있다.

 

char16_t : utf-16 문자를 표현하기 위한 자료형으로 개별 문자 크기가 2byte이다.

 

char32_t : utf-32 문자를 표현하기 위한 자료형으로 개별 문자 크기가 4byte이다.

 

wide-character란?

더보기

보통, 1byte 이상인 문자를 wide-charcter라고 한다.

c++에서는 이런 wide character을 표현하기 위해 wchar_t이 등장했다.

wchar_t는 컴파일러마다 다르게 정의될 수 있기 때문에 고정된 크기가 아니다.

 

그래서 등장한 것이 char16_t, char32_t wide character 문자 자료형이다. 이는 명확하게 크기가 각각 16bit, 32bit로 고정 되어 있으며, 유니코드 형식의 문자를 지원한다.

 

참고)

wchar_t는 컴파일러에서 정의한 자료형이기 때문에 유니코드 문자만을 표현하기 위한 자료형도 아니고 고정된 크기의 자료형도 아니다. (컴파일러마다 다르다!)

그렇기 때문에, 모든 컴파일러에서 동작하도록 하는 유니코드 문자를 사용해야 하는 경우 wchar_t 대신 char16_t, char32_t 자료형을 사용해야 한다.

 

인코딩 방식에 따라 문자열 크기, 비트배열이 다를 수 있다는 것을 다음 코드로 알아보자

#include <iostream>
#include <string>

using namespace std;

int main()
{
    // String literals
    auto s0 = "a가"; // const char*
    auto s1 = u8"a가"; // const char*, encoded as UTF-8
    auto s2 = L"a가"; // const wchar_t*
    auto s3 = u"a가"; // const char16_t*, encoded as UTF-16
    auto s4 = U"a가"; // const char32_t*, encoded as UTF-32

    cout << std::char_traits<char>::length(s0) * sizeof(char) << endl;
    cout << std::char_traits<char>::length(s1) * sizeof(char) << endl;
    cout << std::char_traits<wchar_t>::length(s2) * sizeof(wchar_t) << endl;
    cout << std::char_traits<char16_t>::length(s3) * sizeof(char16_t) << endl;
    cout << std::char_traits<char32_t>::length(s4) * sizeof(char32_t) << endl;
}

위 출력 결과는 다음과 같다.

3, 4, 4, 8

 

위 내용을 정리하면 다음과 같다.

s0

  • 인코딩 방식 : cp949
  • byte 계산 : 3 = 1 + 2 => ASCII 범위의 'a'는 1byte, 한글 2byte
  • 비트 배열 : 0x61 , 0xb0a1

s1

  • 인코딩 방식 : utf-8
  • byte 계산 : 4 = 1 + 3 => ASCII 범위 'a'는 1byte, 한글 3byte
  • 비트 배열 : 0x61 , 0xeab080

s2

  • 인코딩 방식 : utf-16 (환경 : MS 컴파일러 + Visual Studio 한국어 설치)
  • byte 계산 : 4 = 2 + 2 => 'a', '가' 모두 기본 다국어 평면(BMP)에 속하므로 2byte로 계산
  • 비트 배열 : 0x0061 , 0xac00 (한글은 0xac00 부터 시작!)

 

s3

  • 인코딩 방식 : utf-16
  • byte 계산 : 4 = 2 + 2 => s2와 같은 인코딩 방식 사용
  • 비트 배열 : 0x61 , 0xac00

s4

  • 인코딩 방식 : utf-32
  • byte 계산 : 8 = 4 + 4 => 모든 개별 문자를 4byte로 취급
  • 비트 배열 : 0x00000061 , 0x0000ac00

정리

  • 문자를 표현 하기 위한 다양한 문자셋(character set)과 문자열 인코딩 형태(character encoding form)가 존재한다.
  • 인코딩 형태에 따라 문자의 byte 크기와 bit 배열 형태나 순서가 달라질 수 있다.
  • 유니 코드(Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다.
    • 유니코드 표(문자셋의 모음집)를 기준으로 문자를 인코딩한다.
    • utf-8 , utf16 은 유니코드의 대표적인 인코딩 방식이다.

 

[Reference]

modoocode.com/292

kskang.tistory.com/558

docs.microsoft.com/ko-kr/cpp/cpp/char-wchar-t-char16-t-char32-t?view=vs-2019

overface.tistory.com/78?category=237612

en.cppreference.com/w/cpp/language/string_literal

ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C

 

728x90
반응형

'컴퓨터 기본 지식' 카테고리의 다른 글

비트 연산에 대한 이해  (0) 2020.06.24
컴퓨터의 실수 표현 방법  (1) 2020.06.24
컴퓨터의 정수 표현 방법  (0) 2020.06.24
컴퓨터의 수 표현  (0) 2020.06.24
스트림, 버퍼에 대한 이해  (0) 2020.06.22