これは何?
先日
という記事を書いたんだけど、そういえば go もたくさん *
をつけられそうだなと思って試した。
*
の付け方
こんな感じ。
package main
import (
"log"
"unsafe"
)
func main() {
var p ********************************uintptr
x := (uintptr)((unsafe.Pointer)(&p))
*(*uintptr)((unsafe.Pointer)(&p)) = x
log.Println(********************************p)
}
x の初期化と *****(略)*****p
の間に GC が入ると死ぬような気がするけど気にしない。
実行するとこんな出力が得られる。
2021/08/10 18:33:55 824634449616
限界に挑戦
ruby でこんなコードを書いた。
# CC="gcc-7"
# CC="gcc-11"
CC="clang"
def test_aster(n)
aster = "*"*n
src=<<~"SRC"
package main
import (
"log"
"unsafe"
)
func main() {
var p #{aster}uintptr
x := (uintptr)((unsafe.Pointer)(&p))
*(*uintptr)((unsafe.Pointer)(&p)) = x
log.Println(#{aster}p)
}
SRC
Dir.chdir("go") do
File.open( "main.go", "w" ){ |f| f.puts(src) }
puts %x(time go run main.go)
puts("n=#{n} err = #{$?}")
end
$?==0
end
n=1
loop do
break unless test_aster(n)
n*=2
end
a = [*(n/2)..n]
p(a.bsearch do |n|
!test_aster(n)
end)
go version go1.16.5 darwin/amd64
で試したらこんな結果になった。
2021/08/08 18:28:39 824634449760
0.48 real 0.21 user 0.20 sys
n=1 err = pid 53457 exit 0
︙
略
︙
2021/08/08 19:02:32 824634834896
1773.83 real 1761.87 user 9.20 sys
n=8192 err = pid 53747 exit 0
2021/08/08 23:15:01 824634900432
15149.74 real 15074.27 user 60.22 sys
n=16384 err = pid 54106 exit 0
2021/08/10 14:35:05 824635031504
139271.45 real 139042.02 user 438.17 sys
n=32768 err = pid 56707 exit 0
# command-line-arguments
./main.go:12:14: internal compiler error: name too long: **********(略)**********uintptr
Please file a bug report including a short program that triggers the error.
https://golang.org/issue/new
1864.84 real 1874.95 user 21.12 sys
n=65536 err = pid 79661 exit 2
ここで ctrl+C を押して止めた。
n=32768 の場合、139042秒 = 38.6時間を要した。これをあと数回やらないと結果が出ないと思われるので諦めた。
n=65536 だと失敗するので、32768 と 65536 の間のどこかに境界があるのだろう。
失敗する場合のメッセージは上記の通り
# command-line-arguments
./main.go:12:14: internal compiler error: name too long: **********(略)**********uintptr
Please file a bug report including a short program that triggers the error.
https://golang.org/issue/new
という感じ。本当は (略) の部分に六万個を超える *
がある。
internal compiler error なので、実装者の予期せぬ事態が起こっている模様。
チャレンジした身としては、internal compiler error が起こることより、*
が倍になるとコンパイル時間が 10倍ぐらいになるのがつらかった。
補足: *************
の意味
見出し詐欺だけど、 *************
の意味だと説明しにくいので、 ***
ぐらいにしておく。
コードの上では
var p ***uintptr
x := (uintptr)((unsafe.Pointer)(&p))
*(*uintptr)((unsafe.Pointer)(&p)) = x
log.Println(***p)
ということになる。
普通、
-
p
の値は xxxx。p
は***uintptr
なので、 xxxx を**uintptr
の値が書いてあるアドレスとみなす。 - アドレス xxxx には yyyy と書いてある。yyyy は
**uintptr
型なので、 yyyy を*uintptr
の値が書いてあるアドレスとみなす。 - アドレス yyyy には zzzz と書いてある。zzzz は
*uintptr
型なので、 zzzz をuintptr
の値が書いてあるアドレスとみなす。 - というわけで、
***p
の値はアドレス zzzz に書いてある値になる。
という具合に間接参照演算子(*
)は機能する。
今回は、 unsafe.Pointer
と邪悪なキャストを使って、 p
自身のアドレスを p
に書き込んでいる。
例えば &p
を uintptr
にキャストした値が 824634449760
の場合を考える。
この場合の ***p
は
-
p
の値は824634449760
。p
は***uintptr
なので、824634449760
を**uintptr
の値が書いてあるアドレスとみなす。 - アドレス
824634449760
には824634449760
と書いてある。この文脈では824634449760
は**uintptr
型なので、824634449760
を*uintptr
の値が書いてあるアドレスとみなす。 - アドレス
824634449760
には824634449760
と書いてある。この文脈では824634449760
は*uintptr
型なので、824634449760
をuintptr
の値が書いてあるアドレスとみなす。 - というわけで、
***p
の値はアドレス824634449760
に書いてある値824634449760
になる。
という具合に解釈される。
これは *
が三個しかない例だけど、見て分かる通り、何個になってもうまくいくはず。
とはいえ、p
のアドレスを取っている箇所とその先にある log.Println
の間で GC が発生すると、 p
の値と p
のアドレスが合わなくなって死ぬかもしれないと思うけどよくわからなない。死なないかな。どうだろう。