はじめに
Nimはメモリ管理を意識せずにプログラミングできるが、バックエンドにCやC++があるため、
凝ったことをしようとする時にはNimがメモリ割り当てをどうやってるか意識しないといけない場合があります。
この記事の結論を先に述べると、
「良く分からない人はnewSeqOfCap
とsetLen
を組み合わせて使わないようにしましょう。」
注意する例
問題
以下の(1),(2),(3)は何が出力されるでしょうか?
var
seq1:seq[int]
seq2:seq[int]
seq3:seq[int]
seq1 = newSeq[int](10)
seq2 = newSeqUninitialized[int](10)
seq3 = newSeqOfCap[int](10)
seq3.setLen(10)
echo "seq1[0]:",seq1[0] # (1)
echo "seq2[0]:",seq2[0] # (2)
echo "seq3[0]:",seq3[0] # (3)
答え
(1) 0
(2) 不定
(3) 不定
解説
(1) newSeq
でseq
を生成すると割り当てられるメモリ領域はseq
要素の型(今回はint型)の初期値に初期化されるため、0となります。
(2) newSeqUninitialized
は名前の通り初期化されないため、割り当てられたメモリ領域に入ってる値は分かりません。(その代わり、newSeq
に比べて高速です)
(3) 今回注意したいのはnewSeqOfCap
で、最初に確保したCapacity分のメモリ領域は初期化されません。setLen
でseq長を伸ばした場合もメモリ再割り当てが起きない場合(最初のCapacityを超えない場合)は伸びた部分については初期化されません。
初期化されないことで問題が生じる例
先ほどのプログラムの前方にseq
を何度も生成する処理を追加します。
var
seq0:seq[int]
for i in 0..<10_000:
seq0 = newSeq[int](100)
for j in 0..<100:
seq0[j] = 100
var
seq1:seq[int]
seq2:seq[int]
seq3:seq[int]
seq1 = newSeq[int](10)
seq2 = newSeqUninitialized[int](10)
seq3 = newSeqOfCap[int](10)
seq3.setLen(10)
echo "seq1[0]:",seq1[0] # (1)
echo "seq2[0]:",seq2[0] # (2)
echo "seq3[0]:",seq3[0] # (3)
echo "seq3[1]:",seq3[1]
echo ""
seq3.setLen(1)
seq3.setLen(10)
echo "seq3[0]:",seq3[0]
echo "seq3[1]:",seq3[1]
この場合、環境によっては以下の結果となります。
seq1[0]:0
seq2[0]:100
seq3[0]:100
seq3[1]:100
seq3[0]:100
seq3[1]:0
seq0
に割り当てられたメモリがGCで回収されてseq2
やseq3
に割り当てられたため、100が出力されたと考えられます。
ただ、setLen
でseq長を短くした場合は、削られた部分は初期化されるようです。
そのため、newSeq
のみを使っている場合にはsetLen
でseq長を変更しても安全と言えそうです。
まとめ
newSeqOfCap
をsetLen
を組み合わせて使う場合には注意しましょう。
よく分からない場合はnewSeq
を使うか、newSeqOfCap
で生成したseqにsetLen
は使わないようにしましょう。