2020/03/09

Nimのポインタと参照

修正版

以前ポインタと参照に関する記事を書いていましたが、内容が雑だったため改めて書き直しました。
NimではCライクなポインタと参照が用意されています。Cライクなポインタはアンセーフなもので、ガベージコレクタの管理外となっており、自身で管理しなければなりません。一方、参照はガベージコレクタの管理の対象となっているため、管理をガベージコレクタに任せることができます。

簡単なC言語のコードと比較してみます。

#include <stdio.h>

int main() {
    int x = 5;
    int* p = NULL;
    
    p = &x;
    
    printf("%d\n", *p);  // output: 5
    
    return 0;
}

このコードをNimのポインタを使って書くと以下のようになります。

var
  x: int = 5
  p: ptr int
  
echo repr(p)  # nil

p = x.addr
echo repr(p)  # ptr 0x56309c07cc28 --> 5
echo p[]  # output: 5

p[] = 6
echo x  # output: 6

ポインタ変数pは宣言された時点ではnilとなっています。そしてC言語でポインタが指すアドレスを取得する際は&ですが、Nimではaddrを使います。reprを使うと、上記のようにアドレスを表示することができます。そして値を取得する場合は[]を使います。
上記のようにallocalloc0reallocなどを用いていない場合、つまり動的にメモリー確保を行っていない場合は特に問題なさそうですが、もし使う場合はdeallocで解放する必要があります。またヒープ領域のデータを保持しているものの場合、GC_unrefを使う必要があります。つまりCやC++同様、メモリー解放などの事後処理にいろいろと細心の注意が必要となります。
一般的にメモリーエラーは以下のようなものがあります。
  • 範囲外への書き込み・読み込み
  • 共有メモリへの同時アクセス
  • 未定義ポインタへのアクセス
  • 解放後の使用(ダングリングポインタの参照外し)
  • 不正なポインタの使用
  • ヌルポインタの参照外し
  • スタック・ヒープの枯渇
  • 二重解放
  • 無効な解放
  • 不一致解放
などがあります。「確かに」と文章で読めばごく当たり前なことばかりなのですが、人間ですからプログラムを書く際に当然ミスがあります。プログラミングの玄人の方ですら、細心の注意を払うわけですから、私のようなものはより一層の注意が必要です。正直、私は全く自信がありません。
一方、参照の場合は以下のようになります。

var
  p: ref int
  
echo repr(p)  # nil

p = new int
echo repr(p)  # ref 0x7f051203b048 --> 0

p[] = 5
echo repr(p)  # ref 0x7f051203b048 --> 5
echo p[]  # output: 5

p[] = 6
echo p[]  # output: 6
echo repr(p)  # ref 0x7f051203b048 --> 6

上記の場合、int型への参照はnewを使うことで0で初期化されるようです。
こちらはガベージコレクタの対象なので、私でも安全に扱えるものだと思われます。

実はガベージコレクタも選べる

Nimはオプションとして、コンパイル時に--gc:〇〇でガベージコレクタをいくつか選ぶことができます。参照カウント方式なら--gc:refc、マークアンドスウィープ方式なら--gc:markAndSweep、Boehm GCなら--gc:boehm、Go言語のタイプなら--gc:go、など。また--gc:noneでガベージコレクタなしにすることもできるようです。
ちなみに、私にはハイレベル過ぎるお話なので、デフォルトのままにしています。これらの機能を見ていると、NimはCやC++などに精通した上級者向けの言語なのではないか?と今更ながら思うのでありますが、引き続きゆるゆると勉強をしていこうと思います。