본문 바로가기

진리는어디에

[C++] UTF-8 문자열 길이

C++은 강력한 프로그래밍 언어지만 그에 반해 부족한 부분도 많다. 특히 문자열 관련해서는 타의 추종을 불허할 정도로 불편하다. 이번 포스트에서는 C++에서 UTF-8 문자열의 개수를 세어 보도록 하겠다.

UTF-8 인코딩 규칙

보통 UTF-8관련 포스팅이라면, UTF-8의 인코딩 역사 부터, 유니코드와의 관계 등등 장황한 이야기가 펼쳐지지만 여기서는 코드를 작성 할 수 있을 정도의 짧고 얇은 지식만 요약하고 넘어 간다. 밑줄 그어진 부분을 집중해서 보자.

UTF-8인코딩은,
유니코드 한 문자를 나타내기 위해
1바이트에서 부터 4바이트까지 사용하는
"가변 길이" 문자열 인코딩 방식이다.
  • 문자에 따라 문자를 표현하기 위해 1바이트에서 4바이트까지 다양한 길이를 가진다.
  • 1바이트로 표시된 문자의 최상위 비트는 항상 0이다
  • 2바이트 이상으로 표시된 문자의 경우, 첫바이트의 상위 비트들이 그 문자를 표시하는데 필요한 바이트 수를 결정한다.
    2바이트는 110으로 시작
    3바이트는 1110으로 시작
    4바이트는 11110으로 시작
  • 첫 바이트가 아닌 나머지 바이트들은 상위 2비트가 항상 10이다.
바이트수 UTF8 표현(이진법) 설명
1바이트 문자 0xxxxxxx 최상위 비트가 0이다
2바이트 문자 110xxxxx
10xxxxxx
첫바이트는 110으로 시작
나머지 바이트들은 10으로 시작
3바이트 문자 1110xxxx
10xxxxxx
10xxxxxx
첫바이트는 1110으로 시작
나머지 바이트들은 10으로 시작
4바이트 문자 11110xxx
10xxxxxx
10xxxxxx
10xxxxxx
첫바이트는 11110으로 시작
나머지 바이트들은 10으로 시작

Example

위 규칙을 기반으로 바이트스트림(여기서는 std::string)에 들어 있는 UTF-8문자열의 글자 개수를 카운팅하는 예제를 만들어 보았다. 예제 파일과 예제에서 사용된 utf8.txt는 본 포스트의 부록 2항목에서 다운받을 수 있다.

코드 자체는 어렵지 않으므로 코드 설명은 주석으로 대체한다.

#include <iostream>
#include <fstream>
#include <string>
#ifdef _WIN32
#include <Windows.h>
#endif

size_t UTF8Length(const std::string& str)
{
    size_t utf8_char_count = 0;
    for (int i = 0; i < str.length();)
    {
        // 4바이트 문자인지 확인
        // 0xF0 = 1111 0000
        if (0xF0 == (0xF0 & str[i]))
        {
            // 나머지 3바이트 확인
            // 0x80 = 1000 0000
            if (0x80 != (0x80 & str[i + 1]) || 0x80 != (0x80 & str[i + 2]) || 0x80 != (0x80 & str[i + 3]))
            {
                throw std::exception("not utf-8 encoded string");
            }
            i += 4;
            utf8_char_count++;
            continue;
        }
        // 3바이트 문자인지 확인
        // 0xE0 = 1110 0000
        else if (0xE0 == (0xE0 & str[i]))
        {
            // 나머지 2바이트 확인
            // 0x80 = 1000 0000
            if (0x80 != (0x80 & str[i + 1]) || 0x80 != (0x80 & str[i + 2]))
            {
                throw std::exception("not utf-8 encoded string");
            }
            i += 3;
            utf8_char_count++;
            continue;
        }
        // 2바이트 문자인지 확인
        // 0xC0 = 1100 0000
        else if (0xC0 == (0xC0 & str[i]))
        {
            // 나머지 1바이트 확인
            // 0x80 = 1000 0000
            if (0x80 != (0x80 & str[i + 1]))
            {
                throw std::exception("not utf-8 encoded string");
            }
            i += 2;
            utf8_char_count++;
            continue;
        }
        // 최상위 비트가 0인지 확인
        else if(0 == (str[i] >> 7))
        {
            i += 1;
            utf8_char_count++;
        }
        else
        {
            throw std::exception("not utf-8 encoded string");
        }
    }
    return utf8_char_count;
}

int main()
{
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
#endif

    const std::string file_path = "utf8.txt";

    std::ifstream is(file_path);
    if (true == is.is_open())
    {
        std::string line;
        while (std::getline(is, line))
        {
            try {
                std::cout << line << ", count:" << UTF8Length(line) << ", byte length:" << line.length() << std::endl;
            }
            catch(const std::exception& e)
            {
                std::cout << e.what() << std::endl;
            }
        }
        is.close();
    }
}

Output

마치며

이해가 필요한 것이 아닌, 규칙대로 따라 만들기만 하면 되는 내용이라 설명이 부족하다고 느낄 수 있다. 추가 문의 사항이 있다면 아래 댓글로 남겨 주길 바란다. 최대한 빨리 답변 할 수 있도록 하겠다.

부록 1. 같이 보면 좋은 글

부록 2. 첨부 파일

main.cpp
0.00MB
utf8.txt
0.00MB

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!