🦔

Goのstring型の内部表現をみてみる

2024/09/06に公開

はじめに

この記事では、Goのstring型の内部表現を確認し、文字列がどのように扱われているかを詳しくみてみます。

string型の内部表現

string型の実態はstringStructという構造体です。
普段操作する際は文字列として扱っていますが、実際は構造体を操作しています。

構造体は以下です。

type stringStruct struct {
	str unsafe.Pointer
	len int
}

ref: https://github.com/golang/go/blob/master/src/runtime/string.go#L242-L245

stringStructには、strと呼ばれる文字列の実態を保持するポインタと、文字列の長さを管理するlenフィールドがあります。一見すると実態があればlenフィールドは不要に思えるかもしれませんが、O(1)で長さを取得でき効率的な点や、C言語のような文字列末尾のNUL文字('\0')に依存せず任意のバイト列を文字列として扱える点などのメリットがあります。

さらに、この構造体はGoの文字列の重要な特性である不変性(イミュータブル)を実現しています。Goで文字列操作を行う際、実際には新しいstringStructが作成され、元の文字列データは変更されません。これにより、文字列の予期せぬ変更を防ぎ、並行処理での安全性を高めています。

ただし、通常のプログラミングでは、この構造体を直接意識する必要はありません。fmt.Printlnなどで文字列を表示する際は、Goのランタイムが自動的にstrフィールドにアクセスをしています。

動かしてみる

本当に内部表現が上記のものなのか、コードを動かして確認します。

Go Playgroundで動かしてみたい方はこちら -> https://go.dev/play/p/W0AvnmPsX6C

package main

import (
	"fmt"
	"unsafe"
)

// 内部実装を模倣したモック (プライベートな構造体なので、再度定義しています)
type stringStruct struct {
	str unsafe.Pointer
	len int
}

func main() {
	s := "hello"

	// unsafe.Pointerで型情報を取り除き、単なるメモリアドレスとして扱う
    // unsafeは実際の開発では推奨されないので注意
	ss := (*stringStruct)(unsafe.Pointer(&s))

	// stringStructへのキャストとフィールドアクセスが成功している -> stringの内部実装は 上記で定義したstringStructと同等のもの
	fmt.Printf("stringStruct str: %v, len: %d\n", ss.str, ss.len) //str: 0x4b0248, len: 5
	fmt.Printf("stringStruct struct: %+v\n\n", ss) //&{str:0x4b0248 len:5}

	// 型の確認
	fmt.Printf("Type s: %T\n", s) //string
	fmt.Printf("Type ss: %T\n\n", ss) //*main.stringStruct

	// 内容の確認
	fmt.Printf("Bytes s: %v\n", []byte(s)) //[104 101 108 108 111]
	bytes := (*[5]byte)(ss.str) // 直接バイトアクセス
	fmt.Printf("Bytes ss: %v\n", *bytes) //[104 101 108 108 111]
}

コードの解説

上記のサンプルコードでは、まず文字列"hello"を定義し、その後unsafe.Pointerを用いて型情報を取り除いたメモリアドレスを取得します。そのアドレスを*stringStruct構造体にキャストし、strとlenフィールドにアクセスしたところ、実際にstrには文字列へのアドレス、lenは文字列の長さが入っているのを確認できました。

strの内容をみる際は、*[5]byte型にキャストしてからアクセスをしています。実行結果を見るとsのバイト配列とstrフィールドのバイト配列が一致している(=つまり同じ文字列である) ことが確認できましたね。

まとめ

Goの文字列は内部的にstringStruct構造体として表現されています。この構造体は文字列データへのポインタや長さを持っており、これらを利用するこでより効率的に文字列操作ができます。ただし、実際の開発ではこの内部表現を直接操作することはほぼなく、基本的にはGoが提供する安全な文字列操作メソッドを使用することが推奨されます。

Discussion