ぺんぎんさんのおうち

日記です。たまに日記じゃないこともあります。

mpz_t, mpz_class メモ2

GMP v6.2 https://gmplib.org/

 

実話です. 

前 https://ykm11.hatenablog.com/entry/2020/05/13/011804

次 

 

今回はGMPの実装をみながら解説.

 

/* gmp.h */

typedef struct {
    int _mp_alloc;
    int _mp_size;
    mp_limb_t *_mp_d;
} __mpz_struct;


typedef __mpz_struct mpz_t[1];

 

_mp_sizeは負数も取ることができ,_mp_sizeの正負によって値の正負を管理する.

 

f:id:ushiromiya3:20200620141052p:plain

mpz_tのメンバ変数

mpz_classはmpz_tのwrapperである. mpz_tに演算子や各コンストラクタを実装して扱いやすくしたものだと思えばよい.

 

 

実際にmpz_classを使ってみる.

#include <gmpxx.h>

// -lgmpxx -lgmp -O3

int main() {
    mpz_class x;
    mpz_class y = 0;
    mpz_class z = 1;
    mpz_class w("DEADBEEF", 16);
}

 

-Sオプション付けてアセンブルしたものを抜粋

    call __gmpz_init@PLT
    movq %r13, %rdi
    call __gmpz_init@PLT
    movl $1, %esi
    movq %r12, %rdi
.LEHB0:
    call __gmpz_init_set_ui@PLT
.LEHE0:
    leaq 48(%rsp), %r14
    movl $16, %edx
    leaq .LC0(%rip), %rsi
    movq %r14, %rdi
.LEHB1:
    call __gmpz_init_set_str@PLT

mpz_classインスタンスの宣言のみ,宣言時に0で初期化する場合はmpz_initが実行される. 

文字列と基数を渡して大きな数字(256bitsとか)での初期化も可能.

mpz_classはコンストラクタ,デストラクタでmpz_init, mpz_clearを実行してくれる.

 

ちなみにmpz_tを使って上のコードと同じことをするには

mpz_t x, y, z, w;
mpz_init(x);
mpz_init(y);
mpz_init_set_ui(z, 1);
mpz_init_set_str(w, "DEADBEEF", 16);

// 最後にmpz_clear

 と書かなければならない.

 

mpz_tを使う場合はプログラマがmpz_init, mpz_clearを書く必要がある.mpz_clearを実行しないと「関数内でmallocしたのにfreeせずにリターンした」状態になる.上で示したような小さいコードでは問題なく終了するが,大規模なプログラムではいつかヒープ領域を食いつぶしてプロセスが落ちるかもしれない.

 

mpz_classの便利なところは,足し算や掛け算などで追加の領域が必要になったときにマシンが勝手に領域を拡張してくれる点.このときreallocが走る.プログラマはオーバーフローやメモリについて気にする必要がない.

 

 

/* mpz/init.c */

void mpz_init (mpz_ptr x) __GMP_NOTHROW {
    static const mp_limb_t dummy_limb=0xc1a0;
    ALLOC (x) = 0;
    PTR (x) = (mp_ptr) &dummy_limb;
    SIZ (x) = 0;
}

 

ALLOC, PTR, SIZはgmp-impl.hで次のように定義されている.

#define SIZ(x) ((x)->_mp_size)
#define PTR(x) ((x)->_mp_d)
#define ALLOC(x) ((x)->_mp_alloc)

 

_mp_size, _mp_allocを0で初期化している.mpz_tインスタンスの宣言時には_mp_size, _mp_allocにどんな値が入っているかわからないので,初期化せずに放っておくと計算が合わなかったりプログラムが落ちたりする(可能性がある).

 

ある程度の領域を確保してmpz_tを初期化したいときはmpz_init2を使う. 

/* mpz/init2.c */

void mpz_init2 (mpz_ptr x, mp_bitcnt_t bits) {
    mp_size_t new_alloc;

    bits -= (bits != 0);

    new_alloc = 1 + bits / GMP_NUMB_BITS;

 

   // omitted

 

 

    PTR(x) = __GMP_ALLOCATE_FUNC_LIMBS (new_alloc);
    ALLOC(x) = new_alloc;
    SIZ(x) = 0;

}