Skip to main content

Command Palette

Search for a command to run...

독특한 Go lang 의 String

Updated
3 min read
독특한 Go lang 의 String

len("Hello, 월드") 의 출력값이 몇이 나올거라고 생각하는가? 파이썬과 같은 언어에서는 10이 나오기를 기대한다. 하지만 Go lang 에서는 14가 나온다. 그 이유는 무엇일까?

package main

import (
    "fmt"
)

func main() {
    hello := "Hello, 월드!"

    fmt.Println("문자열 길이: ", len(hello))

    for i := 0; i < len(hello); i++ {
        fmt.Printf("타입: %T 값:%d 문자값: %c\n", hello[i], hello[i], hello[i])
    }
}

Go lang 의 len() 함수의 결과값이 14가 나오는 이유는 Go lang 에서는 문자열을 기본적으로 UTF-8 로 인코딩된 바이트 시퀀스로 다루기 때문이다. 즉, 14 바이트는 영어와 기호는 각 1byte, 한글이 각 3 byte 를 차지하기 때문에 (1*8 + 3 * 2) = 14 byte 가 나오게 된다. 여기서 "왜 한글은 3 byte 일까?" 라는 의문이 들 수 있다. 오늘은 그 의문을 한번 풀어보려고 한다.

UTF-8 이란?

UTF-8 은 문자를 인코딩하는 체계화된 방식중 하나이다. 기본적으로 code point 를 1~4 byte 사이로 인코딩하도록 되어 있다.

WikiPedia 의 정의된 방식을 따라보면 code point 값으로 u~z 까지의 값을 치환해주면 된다고 한다. 즉, 한글은 3 Byte 가 소모되므로 “월” 이라는 문자의 유니코드를 wwww xxxxyy yyzzzz 값으로 치환해주면 될 것이다.

그렇다면 월이라는 단어의 유니코드를 확인해보자. 검색 해보니 “월” 의 유니코드는 “U+C6D4” 이다. 이걸 한번 16진수로 변환해보자. Go lang 에서는 16 진수를 나타내기 위해서는 U+ 대신 0x 를 이용해야 한다.

    codePoint := rune(0xC6D4)
    binary16BitStr := fmt.Sprintf("%016b", codePoint)

    fmt.Println("16비트 이진수 표현:", binary16BitStr)

위와 같이 코드를 작성하고 실행시키면 1100 0110 1101 0100 이 나온다. 보기 좋게 하기 위해 4글자 마다 개행을 하나씩 넣었다. 우리가 아까 위에서 나온 3바이트 기준으로 w~z 까지의 값을 대치하기 위해서는 4+6+6 으로 대입되어야 한다.

즉, 11101100 10011011 10010100 이 나오게 될 것이다. “월” 이라는 단어는 3byte 로 표현가능하며 UTF-8에서는 이렇게 표기가 된다는 것을 계산해 볼수 있다. 그렇다면 한번 검증해보자. Go lang String 공식문서에 따르면 String 은 UTF-8 로 인코딩된 8bit 의 집합이라 적혀있으므로 %08b 를 통해서 출력 가능할 것이다.

    r := "월"
    c := string(r)
    for i := 0; i < len(c); i++ {
        fmt.Printf("%08b ", c[i]) // 11101100 10011011 10010100
    }

출력값을 보면 11101100 10011011 10010100 으로 우리가 계산한 값과 같은 것을 확인할 수 있다. 더 정확하게 하고 싶다면 비트 연산(bit operators) 를 이용해서도 가능하다. 첫번째 4비트는 16비트에서 12개를 right shift 하고 1111 로 비트 mask 를 하는 것으로 추출 가능하다.

    standard3Bytes := "1110wwww 10xxxxyy 10yyzzzz"
    fmt.Println("표준 3바이트 인코딩:", standard3Bytes) // "1110wwww 10xxxxyy 10yyzzzz"

    wwww := (r >> 12) & 0b1111
    xxxxyy := (r >> 6) & 0b111111
    yyzzzz := r & 0b111111
    fmt.Println("첫 4비트:", fmt.Sprintf("%04b", wwww))     // 1100
    fmt.Println("다음 6비트:", fmt.Sprintf("%06b", xxxxyy))  // 011011
    fmt.Println("마지막 6비트:", fmt.Sprintf("%06b", yyzzzz)) // 010100

    standard3Bytes = strings.Replace(standard3Bytes, "wwww", fmt.Sprintf("%04b", wwww), 1)
    standard3Bytes = strings.Replace(standard3Bytes, "xxxxyy", fmt.Sprintf("%06b", xxxxyy), 1)
    standard3Bytes = strings.Replace(standard3Bytes, "yyzzzz", fmt.Sprintf("%06b", yyzzzz), 1)
    fmt.Println("최종 3바이트 인코딩:", standard3Bytes) // 11101100 10011011 10010100
}

결과가 동일하게 나오는 것을 확인할 수 있다.

마치며

Go 에서는 이러한 부분을 rune 을 쓰면 해결 가능하다. 다만 이 UTF-8 에 대한 이해가 선행되어야 rune 과 같은 타입에 대한 이해가 이루어진다고 보기에 먼져 정리해보았다.

More from this blog

RDB 에서 큰 컬럼을 인덱스로 잡으면 안되는 이유

B-Tree 는 기본적으로 페이지 사이즈 와 저장할 수 있는 원소의 개수를 고정값으로 사용한다. 하지만 우리가 실제로 페이지에 저장하는 값은 가변적인 크기를 가지고 있기 때문에 필연적으로 물리적으로 저장해야할 개수가 다 차기도 전에 페이지가 넘치는 상황에 부딪히게 된다. 예를 들어 100KB 를 저장하는 페이지에 위와 같이 데이터를 저장한 상태이다. 여

Feb 26, 20262 min read52

Slotted Page

데이터베이스와 관련된 기술을 보다보면 어떻게 데이터를 관리하고 저장하지? 특히 단편화(Fragmentation) 이 일어나는 것을 어떻게 통제하고 관리할까? 혹은 정렬된 자료구조 내부에서 데이터의 순서를 보존하기 위해 어떠한 행위들을 할까? 궁금해집니다. 오늘은 조금 더 데이터베이스 내부에 쓰이는 자료구조를 들여다보며 연관된 행위를 공부해보려고 합니다. F

Feb 22, 20264 min read68
Slotted Page

MCP 를 통한 workflow 자동화

AI native 최근에 LinkedIn 이나 여러 소셜 플랫폼들의 글을 보면 AI native 회사 라는 워딩들이 많이 보입니다. IBM 의 정의에 따르면 AI native 를 아래와 같이 정의한다고 하는데요. “AI를 사고와 업무 방식에 끊임없이 내재화하는 상태” 그렇다면 팀원들이 계속해서 AI 를 사고와 업무 방식에 끊임 없이 내재화 하려면 어떻게 해야할까요? 개발자들은 이미 Claude code 나 Codex 등 여러 AI Tool...

Feb 14, 20263 min read105

파이썬 톺아보기 2화 - Ast 와 바이트코드

식(Expression) 과 문장(Statement) 프로그래밍을 공부하다보면 위 두 단어를 반드시 마주하게 된다. 가끔 헷갈려하는 경우가 많은데 오늘은 python 에서 기본 모듈인 ast 모듈을 공부하며 이를 알아보도록 하자. 식(Expression) 기본적으로 식(Expression) 이란 평가되면 값이 나오는 코드 조각을 뜻한다. 파이썬에서는 어떠한 부분들이 있을까? 노드 타입설명예시 BinOp이항 연산a + b, x * y...

Feb 6, 20267 min read30
D

dev_roach

41 posts