これは何?
先日
という記事を書いたんだけど、そういえば 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 のアドレスが合わなくなって死ぬかもしれないと思うけどよくわからなない。死なないかな。どうだろう。