LoginSignup
152
129

More than 5 years have passed since last update.

golang で string を []byte にキャストしてもメモリコピーが走らない方法を考えてみる

Last updated at Posted at 2015-02-03

追記: go1.9 でこの方法を使うと安定動作しない可能性があります

コピーしない方法は無くはないんじゃないかなーと思いまして筆を取りました。

golang では []byte(str) をコンパイルすると runtime.gostringtoslicebyte によりバイト配列にコピーされます。

runtime.go
func stringtoslicebyte(s string) []byte {
    b := rawbyteslice(len(s))
    copy(b, s)
    return b
}

しかし実際に golang は内部で utf-8 な文字列を扱っている訳なので、そのまま取り出す方法さえあればいいはずなんですよね。

そこで unsafe の登場です...

これを使うと自らプログラムを破壊出来るんだ。

内部のバイト列はもちろん...

stringbytes.go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    str := "hello, world!"
    vec := *(*[]byte)(unsafe.Pointer(&str))
    fmt.Println(vec)
}

ポインタ演算まで出来ちゃう!

pointer.go
package main

import (
    "unsafe"
)

type foo struct {
    k int64
    v int64
}

func main() {
    f := &foo{3,4}

    // unsafe.Pointer() で匿名ポインタにして
    // uintptr() で演算可能にして
    // +8 バイト(64bit)足して
    // unsafe.Pointer で匿名ポインタに戻して
    // そこにはフィールド v があるはずなので *int64 にキャストして
    // デリファレンスすれば出来上がり
    *(*int64)(unsafe.Pointer((uintptr(unsafe.Pointer(f))+8))) = 5 // グヒヒ

    println(f.v) // 5
}

ちなみに上記の stringbytes.gogo tool 8g -S stringbytes.go でアセンブラにしてみると...

    0x001c 00028 (stringbytes2.go:8)    LEAL    go.string."hello, world!"+0(SB),BX
    0x0022 00034 (stringbytes2.go:8)    MOVL    (BX),CX
    0x0024 00036 (stringbytes2.go:8)    MOVL    4(BX),BP
    0x0027 00039 (stringbytes2.go:9)    MOVL    CX,"".str+24(SP)
    0x002b 00043 (stringbytes2.go:9)    MOVL    CX,(SP)
    0x002e 00046 (stringbytes2.go:9)    MOVL    BP,"".str+28(SP)
    0x0032 00050 (stringbytes2.go:9)    MOVL    BP,4(SP)
    0x0036 00054 (stringbytes2.go:9)    PCDATA  $0,$0
    0x0036 00054 (stringbytes2.go:9)    CALL    ,runtime.stringtoslicebyte(SB)
    0x003b 00059 (stringbytes2.go:9)    MOVL    8(SP),DX
    0x003f 00063 (stringbytes2.go:9)    MOVL    12(SP),CX
    0x0043 00067 (stringbytes2.go:9)    MOVL    16(SP),AX

※部分抜粋

この様にコードに落ちていたのが

    0x001c 00028 (stringbytes.go:9) LEAL    go.string."hello, world!"+0(SB),BX
    0x0022 00034 (stringbytes.go:9) MOVL    (BX),BP
    0x0024 00036 (stringbytes.go:9) MOVL    BP,"".str+24(SP)
    0x0028 00040 (stringbytes.go:9) MOVL    4(BX),BP
    0x002b 00043 (stringbytes.go:9) MOVL    BP,"".str+28(SP)
    0x002f 00047 (stringbytes.go:10)    LEAL    "".str+24(SP),BX
    0x0033 00051 (stringbytes.go:10)    CMPL    BX,$0
    0x0036 00054 (stringbytes.go:10)    JEQ $1,232
    0x003c 00060 (stringbytes.go:10)    MOVL    (BX),DX
    0x003e 00062 (stringbytes.go:10)    MOVL    4(BX),CX
    0x0041 00065 (stringbytes.go:10)    MOVL    8(BX),BP

こーんな形になるー!
これでコピー無しで文字列が扱える!
プログラムも高速になる!
だれも困らない!
みんなハッピー!
みんなどんどん使っていこう!

unsafe をご使用になる時は、用法・用量を守り正しくお使い下さい。race conditionでもあり、あらゆるケースにおいてもGC-safeという訳ではありません。

152
129
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
152
129