時系列順に調査・実装などについて、書いていきます。
調査するべき項目が生じたときは Q: … として、後に明らかになれば言及します。
2024/09/26以前
Refinedrustの適用可能性について
フロントエンド
Radiumの表現力は十分に見えるが、フロントエンド・Coqによる形式化に手を加える
必要がある。RustからCoqへの変換フロントエンドの機能がrlsfの実装をそのまま扱
うには、不十分だが等価な実装に置き換えることで対応可能だと思われる。
(ref. refinedrust-dev/case_studies/tests)
Radiumの表現力
元論文ではunsafeな実装やreborrowなどが扱えるとされており、rlsfの実装を検証す るのに十分な表現力があるように見える。
標準ライブラリの形式化状況
標準ライブラリに関する形式化などは、まだほとんどなされていない。shim とい
う仕組みが用意されていて、手動で書いたRadium/Coqの表現を使った検証が可能。
ビット演算の形式化
TLSFではビット演算を多用するため、Rustによるビット演算の実装を検証できるこ とが望ましい。RefinedRustでは、現在TLSFで用いるビット演算に関する形式化がさ れていないため実装する。
方針
Rustのプリミティブ整数型では、TLSFで扱う wrapping_add / saturating_add
/ rotate_right のような関数はllvm intrinsicsへコンパイルされ、
[1]
Rustの意味論を使って検証することができないため、RefinedRustの only_spec アノ
テーションで形式仕様のみ記述する。 only_spec がついたオブジェクトはradiumコー
ドが生成されず、仕様のみ生成される。
また、Rustのコンパイラプラグインの事情でプリミティブ型へのメソッドを扱うことが
難しそうだったため、[2]
プリミティブへのメソッド呼び出しを通常の関数でラップした上で、
rr::only_spec アノテーションをしている。
RefinedRust/Radiumでは、Rustの整数型はCoqの(無限長の)整数型 Z 上の制限と
して形式化されている。Coqの Z は正の数をバイナリ表現で形式化した
positive 型を使って以下のように定義されているため、 Z.land / Z.lor の
ような論理演算が定義されている。
Inductive Z : Set :=
| Z0 : Z
| Zpos : positive -> Z
| Zneg : positive -> Z.
Z については、様々な数学的成果が形式されているが、ここではビット演算の仕様を
記述するため、 Z.log2 のような数学的な語彙ではなく Z.shiftr のような論理演
算に関する形式化を使う。 Z.log2 のような語彙を使うと、TLSFのインデックス計算
で用いる性質をそのままエンコードしてしまい、形式化の信頼性が損なわれる。
[3]
rotate_right
シフトするビット数が負になり得るケースを含む定義を上手くする方法を考察中。
定義の妥当性を保証する仕様として、n bitシフトしたときにm bit目(0-indexed)が
(m - n + ws) mod ws に移動していてほしい(ref. Zrotate_right_spec )
count_(leading|traling)_zeros
0になるまで右シフトする定義が具体的な値に対する計算量が大きすぎたので、代わりに 与えられた幅だけ1ビットづつテストしていく定義を与えた。
RefinedRustを使った検証コードベースに外部のCoqライブラリを導入する
cargo-refinedrust に手を加えて、 RefinedRust.toml にオプション external_coq_libs を追加
QuickChickを導入
Coqのproperty-based testingができるQuickChickを導入。証明に入る前に定義を吟味する際に便利。
2024/09/27
-
開発環境を自前の
flake.nixに移行することを試みた-
Coqのバージョン、コンパイルしたOCamlコンパイラのバージョンが一致しているにもかかわらず、以下のようなエラーが発生する
Compiled library stdpp.numbers (in file /nix/store/nsbxf0vixraln8nlsi4frg5kxrpcddmy-coq8.17-stdpp-4be5fd62ddbd5359f912e2cebb415b015c37e565/lib/coq/8.17/user-contrib/stdpp/numbers.vo) makes inconsistent assumptions over library Coq.Init.Ltac
-
現在は
refinedrust-dev/flake.nixのdevShellに依存を追加して使っている
-
-
本リポジトリのドキュメント・
rr-exのcoqdocをwebサイトにした -
rotate_rightに関する補題を各ビット幅に具体化して示すように方針を変えた
-
理由: 実際に使うビット幅は限られるため [4]
-
Z上の制限として固定ビット幅の整数に関するビット操作を扱っているVST/CompCertやVellvmでも同様の方針をとっているように見える
-
-
2024/10/02
-
この方針にしたがって定義を簡素化した
2024/10/03
-
rotate shift/count leading zeros周りの補題(
count_leading_zeros_usize_spec/Zrotate_right_usize_spec)の証明を進めた
2024/10/04
2024/10/07
-
A: ビット操作を扱うコードの検証例を直した(これは勘違い)
-
原因: アノテーションの使い方が間違っていた
-
関数の引数に関する事前条件の指定には
rr::argsを使うべき
-
-
-
Q:
rotate_rightのシフト幅が負の場合の扱い-
厄介な点:
u32を符号付きとして暗黙的に扱っている(assert_eq!(2, 1usize.rotate_right(u32::MAX))) -
Coq側で
i32として解釈した分シフトするようにして、事後条件もusizeの取る値の範囲でシフト幅の正負が変わることを明示する
-
2024/10/10
-
RefinedRustのAllocator APIに関する形式化を調査した ref. refinedrustに関するメモ
-
簡略化した
map_floorの検証-
template tacticが失敗するのでデバッグしていた
-
RefinedRustのspecでタプルを扱う方法がわからない
-
-
block_size_range_not_overwrapの証明を進めた
2024/10/11
-
A: タプルの扱いについてCoqの項
hlist (place_rfn {Coqの型})でrefineできることがわかった -
簡略化した
map_floorの検証: RefinedRust側の問題は概ね解決した -
block_size_range_not_overwrapの証明を進めた(SLIがSLLENと混同されていた) -
抽象仕様の検討
-
Q: 割当済みブロックの追跡
-
他の検証プロジェクトを調査する
-
2024/10/17
-
BlockHdr/FreeBlockHdr/Tlsfのアノテーション・変換fix-
rr_frontendが未対応な部分はsedの力を借りている
-
-
ビットマップとfreelistの整合性に関する不変条件を追加
-
Q: const parametersの扱い
-
refinedrustでも単純なジェネリクスが扱えるが、const parametersが扱えるかの検証
-
-
Q: 再帰的な構造に対する述語の書き方
-
first_free[fl][sl].next_freeに関する言明
-
2024/10/18
-
ビット操作周りはなんとかなりそう [5] なので、先にraw pointerを多用するコードが実際に検証できることを確かめたい
-
rawポインタを扱うプログラムの検証の実験,リンクリストの結合で
&Tから*const Tに変換する部分の推論が上手く行かない-
ローカル変数にアノテーションが出来ないためsideconditionが残ったと思われる cf.
rr_ex::silly
-
-
NonNullAPIの形式仕様 -
core::mem::replaceの形式仕様 -
単純化した
Tlsf::link_free_blockをつかってリンクリストの検証方法を確かめる(wip)-
link listは以下のような形で実現できそう?
l ↦ vのvはただのバイト列で、layoutやrefinementの条件を足すことで構造体が格納されていることを言えそう (ref.has_layout_val) [6]
-
l ↦ v * v @ struct_t ... field ... * l' ↦ v' * to_val_or_somthing l' @ field
2024/10/21
-
ptr::write後ptr::readする関数の検証が出来ない原因の調査-
rlsfでは
mem::replaceを使っているが中身がptr::read/ptr::writeなのでこれは問題
-
-
Q:
type_of_ptr_read(theories/rust_typing/shims.v)が弱すぎる-
ptr::readの仕様に該当するものだが、事後条件で読み出した値のuninitが返ることになっている-
ptr::writeの事後条件から情報が減っている
-
-
enumの初期化周りの都合らしい
-
workaroundの検討
-
強い条件をadmitしてshimとして使う
-
特定の構造体について同様の関数を用意してよりつよい条件を証明する→この方針で進める
-
value_t(バイト列として解釈するための型)を使ったインタフェースをラップして使えないか -
mem::replaceを使っている限り&mutを持っているので大丈夫な可能性…
-
-
-
特定の構造体について同様の関数を用意して証明する
-
refinedrust.examples.rr_ex.generatedとextrasのtheoryが循環参照になる-
post_generation_hookでcoq/shims.vをgenerated/にコピーする
-
-
2024/10/24
-
A:
ptr::readの仕様が弱い問題 についてrlsfではmem::replaceが用いられており、&mutが引数なので所有権に関する仮定が使え るので問題にならない -
mem::replaceの検証 -
Q:
NonNullを使った検証でフロントエンドがパニックする-
A: フロントエンドの制約で再帰的な出現が扱えない (
*const Tが許されているのでNonNull<T>も大丈夫かと思ったが違った)
-
-
-
case_studies/minivecでは*mut Tへ明示的にアドレス計算をして要素にアクセスしている -
workaround: const fn で要素アクセスをラップして、only_spec
-
配列のリテラルを含むコードを自動的にスキップするのか上手く行かなかった。
-
*mut Tに変換して扱うか、RefindeRust側で配列の要素アクセスを扱えるようにする必要がある
-
2024/10/25
-
静的な配列アクセスが扱えない問題のworkaroundの調査
-
A: refinedrust側の拡張を考えつつ、
*mut Tで代替する
-
-
配列の
*mut Tへの書き換えとアノテーション(wip)
2024/10/31
-
配列がサポートされていない問題のworkaround中
-
*mut TはRefinedRustの型システムでは [.line-through]#locとして扱われ、型情報が消えるため# アノテーションが適切でなかった rr_frontendがTに関連する定義を解析してくれず、手動でshimなどをインポートする必要がある -
TODO: 生成されたスペック中の
std_option_Option_tyの定義をファイルの上の方に持ってくる
-
-
self.first_freeをrefineするCoqの型block_matrixを修正
2024/11/01
2024/11/06
-
A: raw poitner dereferenceの事前条件について
silly_derefの事前条件としてptr::readの強めた仕様を指定したらほぼ自動で証明してくれた-
空虚かもしれない
-
#[rr::params("l" : "loc", "vs", "z" : "Z")]
#[rr::args("l" @ "alias_ptr_t")]
#[rr::requires(#iris "l ◁ₗ[π, Owned false] PlaceIn vs @ (◁ value_t (st_of (int usize_t))) ∗ vs ◁ᵥ{π} z @ (int usize_t)")]
#[rr::returns("z")]
unsafe fn silly_deref(x: *mut usize) -> usize {
*x
}
2024/11/07
-
構造体のフィールドのraw poitner dereferenceについて調査中
-
l ◁ₗ[π, Owned false] -[b; pf; nf] @ (◁ FreeBlockHdr_ty)のようにして仕様はかけた -
(*x).next_free = None;の事後条件としてl ◁ₗ[π, Owned false] -[b; pf; #None] @ (◁ FreeBlockHdr_ty)を証明したい-
ptr::writeのshimを見ても事前条件は十分に見えるので、手動で証明する方法を探している
-
-
-
Q: raw pointer越しのフィールド更新の事後条件の証明方法
-
抽象仕様の分離論理へのエンコードに関するメモを書いた
2024/11/08
-
基本的な機能の実現に時間が掛かりそうなため、一度RefinedRustから離れて他の方法を検討する
-
候補1: Verus
-
既にmimallocの検証に使われており、他のケーススタディも豊富なため
-
-
候補2: C言語実装をVSTで検証する
-
VSTを使った検証プロジェクトは多く、アロケータについても先行研究がある
-
-
-
verusによるmimallocの検証コードを読んでいた
2024/11/11
-
Verusのmimalloc検証コード読みつづき
-
verusによるリンクリストの検証例を作っていた
2024/11/13
-
verusによる単方向リンクリストの検証例を作った
-
ハマった点: 以下のエラメッセージがわかりにくかった
-
実際には以下のスペックは間違っていて、正しくは
… =~= old(self)@.push(v)だった点の修正self@とold(self)@の同じ要素を含んでいる部分に関するアサーションを追加すれば解決した -
structの修飾子やspecのopen/closedが関係しているかと思ってしまった
-
error: assertion failed
--> ./singly_linked_list_trivial.rs:77:28
|
77 | assert(self@ =~= seq![v].add(old(self)@));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assertion failed
note: diagnostics via expansion:
self.view() =~= empty().push(v).add(old(self).view())
datatype is opaque here
2024/11/14
2024/11/18
-
Verusの基礎について書き進めたverus.adoc
2024/11/21
-
Verusのraw pointerサポートについて書いた
2024/11/22
-
Verusによるrlsfの検証を開始
-
インデックス計算に関する定義を追加(wip)
-
-
Q: usizeに対するビット操作が一部用意されていない(leading_zeros/trailing_zeros)workaround
-
u64/u32などにはu64_leading_zerosとしてある
-
workaround: external_bodyの関数として事前事後条件でプラットフォーム毎に対応する
*_leading_zerosなどと同等の結果が得られることを言う
-
2024/11/25
-
Q: const定数の定義でconst fnが呼び出せない→仕様
-
A: 定数の代わりにconst fnを利用
-
test_verify_one_file! {
#[test] test1_fails5 verus_code! {
const fn f() -> u64 { 1 }
const S: u64 = 1 + f();
} => Err(err) => assert_vir_error_msg(err, "cannot call function `crate::f` with mode exec")
}
-
Verusのビット演算に関する調査・インデックス計算の検証つづき
-
rotate_rightの形式仕様
-
usizeのビット演算が未サポートな問題のworkaround
-
2024/12/09
-
RQ: 対象のユースケースと直接関係しないが、 複数の連続しないメモリ領域をTLSFに追加して適切に処理されるかという問題も証明の対象として良さそう
-
証明用の状態に関する情報(
verus-ex/tlsf.rsGhostTlsf) の定義とメモリ領域の正当性の伝播の流れを設計
2024/12/10
-
alloc/deallocの仕様を追加
-
verusdocの導入を試みたが、こちらの問題に出会った https://verus-lang.zulipchat.com/#narrow/channel/399078-help/topic/Verusdoc 内部的な利用以外の使用例も無いため、素のrustdocで一時的に代替(Verus特有の機能が消えた状態のものがドキュメント化される)
2024/12/12
-
標準ライブラリの整数に関する補題を調査した
-
ref.
calc!,lemma_low_bits_mask_unfold
-
2024/12/13
-
Q:
Tlsf::MAX_POOL_SIZEがOptionになっている意図がわからない-
A: 特定の環境ではビットマップをあふれるようなサイズのメモリプールを扱いたいため
-
-
Q: DeallocToken内にヘッダ全体の権限があるとdellocate時にprev_phys_blockを通じて隣接ブロックを結合するのに必要な権限が得られない
-
option 1: ヘッダのアドレスをIDとするトークンをヘッダのPoinsToの代わりに発行する
-
意味論的にも割り当てられた領域を管理するヘッダへの所有権をユーザーがもっているのはおかしい
-
-
-
インデックス計算用の関数のOptionを取り除いた
-
正常なサイズを受け取ることを仮定できればtotalなため
-
-
insert_free_block_ptr_alignedの移植つづき
2024/12/16
2024/12/17
-
A: Verusはオブジェクトのメモリ上でのサイズについて関知しないためglobal directiveをつかって必要に応じて教える必要がある
-
インデックス計算の性質に関する補題を立てた(各インデックスの範囲の一意性・妥当なサイズに対するインデックスの存在性)
-
wrapping_add/wrapping_subの形式仕様を追加
2024/12/17
-
rlsfのインデックス計算の実装に関するインフォーマルな正当化を文書にまとめている ./rlsf-index-calc.html
2024/12/20
-
インデックス計算関連の補題・証明などをモジュールに切り出した
-
Verusのバグっぽい挙動のminimal exampleを作成
use vstd::prelude::*;
verus! {
const G: usize = 0;
struct B<const N: usize> {}
impl<const N: usize> B<N> {
fn f() -> B<G> { B {} }
}
}
2024/12/23
-
先日のバグの報告・workaround
-
インデックス計算に関する補題の証明(wip)
-
Verusで区間に関する理論を扱う方法を模索中
-
set_int_rangeで整数上の区間をSetとして生成できるが、Setに条件をつけて回るため区間同士の関係の扱いが煩雑になる -
半開区間しか扱わないため、集合に関する証明に落とすよりは始点・終点の議論で済ませたい
-
-
2024/12/24
-
インデックス計算に関する補題の証明(wip)
-
Q: BlockIndexでtype_invariantとしてstart<endを持つか
-
現状、半開区間を表す型とその不変条件を別途用意しているが、一緒にしてもいいかも
-
-
Q: block index(tuple/BlockIndex)上の順序を導入したほうが見通しが良いかもしれない
-
ref.
vstd::relationsでSet<V>上の関係に関する形式化がある
-
-
2024/12/27
-
インデックスから計算されるブロックサイズの一意性の証明を進めた
2025/01/07
-
インデックス計算の検証に関するdesign doc(実装のインフォーマルな正当化)を書き進めた
2025/01/08
-
rlsf_verified::bitsのリファクタリング(is_op_spec(arg, res)→op_spec(x) == res) -
インデックス計算の検証に関するdesign docを書き進めた
-
重要な行間は埋めることができたような気がしている
-
2025/01/09
-
ビット演算周りの補題の証明(wip)
-
bit maskと整数剰余との相互関係を使いたいので
vstd::bits::lemma_u64_low_bits_mask_is_modあたりをusizeに移植する必要がある
-
2025/01/16
-
添字計算式の再考→有理数上での再定式化
-
Verus上での有理数の形式化
-
[verifier::type_invariant]はspec modeのものには使えず、数学的な対象にはadmit()などを使って公理化するのが標準的なよう -
vstd::relationsが==で同値関係が定義されていることを仮定しており、VerusはまだPartialEqのユーザー実装をサポートしていないため、 該当部分を再実装
-
2025/01/20
-
有理数の形式化を公理ベースから構造体+wf-ness述語(分母が0より大きいことを示す)に移行
-
Verusはspec modeのオブジェクトに不変条件を与えることが実質できないため
-
2025/01/22
-
broadcast useでパニックする問題の解決-
broadcast groupで指定する補題にbroadcast proof fnを指定していなかった
-
-
有理数上での諸補題の再構築
2025/01/23
-
半開区間に関する補題を追加・証明
-
区間演算について教えて頂いたので調査
-
将来的に区間演算によって、空きリストの添字が適切な区間に落ちることを記述できると良さそう
-
区間順序の導入は以前から同等のものを検討していた
-
要求サイズから添字への関数の単調性を証明する際に役立ちそう
-
-
2025/01/24
-
有理数に関する補題を整備
-
broadcast groupを追加
-
x ÷ y * y = xの証明
-
2025/01/28
-
search_suitable_free_block_list_for_allocationの移植・形式仕様 -
GhostTlsfの構造見直し -
Tlsf::update_bitmapのFIXME原因調査
2025/01/30
-
有理数の構成を再考
-
正の整数を表す
Posをペアノ自然数と同様に構成し、i > 0なるintとの全単射によって同一視する方法のPoCを作成 https://play.verus-lang.org/?version=stable&mode=basic&edition=2021&gist=5bff4aee0cbadc50299946b153da9369-
この不整合 は、Verusの
intにかかる形で公理を与えていたことが原因だったため、Posを使うことで公理的な形式化をする際に同様の原因による不整合を防げる-
vstdのように間接的な方法を除いて
intを含む公理を作るのはやめたほうが良い 詳細はわからないが以下のような公理をあたえるとassert(false)が証明できる
-
-
-
struct Rational;
impl Rational {
spec fn num() -> int;
...
spec fn new(n: int, d: Pos) -> Self;
}
proof fn axiom_new(n: int, d: Pos)
ensures Rational::new(n, d).num() == n,
...
{admit()}
proof fn ex() {
axiom_new(1, from_int(2));
axiom_new(0, from_int(2));
assert(false);
}
-
以上の例から、公理的な形式化をする際にも
struct Rational(int, Pos)のような定義とその上の演算・同値関係はVerus内で定義しておく必要があるように見える-
また、公理的な形式化ではなくてもすべての定理の前提に
requires r.wf()を要求せずにすむ
-
2025/02/03
2025/02/07
-
リファクタリングが完了(自動化もある程度動くようになった)
-
half_open_rangeに一意性の証明で利用する補題を整備
2025/02/10
-
サーベイ
-
FSMによるアプローチがある
-
Coq上のFSM形式化によるもの: Li, Shaofeng, Lei Qiao, and Mengfei Yang. "Memory state verification based on inductive and deductive reasoning." IEEE Transactions on Reliability 70.3 (2021): 1026-1039.
-
BIPを用いたモデルベースのもの: Lu, Xiutai, et al. "Towards Formal Verification of Dynamic Memory Allocator Properties Using BIP Framework." Proceedings of the 5th International Conference on Computer Science and Application Engineering. 2021.
-
-
2025/02/14
-
Q: 有理数の四則演算に関する証明をある程度自動化したい
-
同値関係がビルトインのものではないため、等式変形による証明が冗長
-
vstd::arithmetic::mulを参考にbroadcast proofを構成すれば達成できそう-
Q: メソッドチェインにうまくtriggerを指定できなさそう
-
-
2025/02/27
-
A: この不整合は
wf_node中で含意と連言を取り違えて書いていたことが原因だった
2025/02/28
-
insertの実装と形式仕様が固まった-
証明用のメタデータの設計に関して気づいたことがあるため、このままPoCの検証を継続する
-
Mapのキーとして
*mut Tをintで代用するのがうまく行かなかった(コメント参照)
-
-
2025/03/03
-
Q:
insertの事前条件としてself.ptrs@のすべての要素と異なっていることが必要だが、メモリ開放処理内で使う際にどのように証明できるか?-
A: DeallocTokenにtype_invariantとproof methodとしてもっておく
-
-
リンクリストの検証PoCで
insertの証明が完了
2025/03/05
2025/03/10
2025/03/11
-
DeallocTokenの設計検討
-
deallocate()でDeallocTokenをムーブアウトすることでdouble freeを防ぐ
-
-
執筆環境構築
-
3ヶ月後を目処に文章を書いていく
-
2025/03/14
-
A: この可視性のエラーは パブリックな関数の
ensures節で言及される構造体は、すべてのフィールドがパブリックでなくてはいけないという意味で、今回はフィールドが一つプライベートだったため起こった -
進捗状況をREADMEにまとめ始めた
-
関数ごとの仕様・実装・証明の状況を示す表を追加した
-
-
リンクリストのPoCの移植(done)
-
unlink(ノードへのraw pointerを使ってDLLから切り離す)の検証(done)
-
2025/03/17
-
Rustのアラインメントに関する調査: ref. https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment
-
Q:
alloc::Layoutに異常なアラインメントを指定できるか/RLSFではどのように扱われるか-
A:
sizeを丸めたあとにisizeの範囲に収まれば、自由な2の冪を指定できる -
A: RLSFでは先にオーバヘッドを計算し要求サイズを算出した後、空きブロックの探索に入るため、要求サイズが大きすぎて単純に探索が失敗する
-
-
2025/03/18
-
allocate()の移植-
next_phys_block()ヘッダの情報からメモリ上で隣接するブロック(Free/Used)へのポインタを取得する-
この機能の検証が現状の課題 以前 のアプローチを試している
-
e.g.
fn next_phys_block(h: Hdrs) → Hdrsとして、FreeBlockHdrが隣接しないという不変条件からUsedBlockHdrからはFreeBlockHdrしか返さないような実装とする-
wf()かつnext_of(i).is_some()ならば権限も一緒に返す
-
wf()ではないユースケースでは、その場でポインタ演算によって対応する
-
-
-
Q: Verusが構造体へのraw pointerと権限をそのフィールドについて分割できれば、話が単純になる
-
e.g.
struct T { x: S, y: U }についてPointsTo<T>からPointsTo<S>とPointsTo<U>を取得
-
-
-
2025/03/24
-
allocate()の移植: 18日にそってnext_phys_blockの実装・検証-
next_phys_blockのユースケースとしては、-
first_freeから取り出したFreeBlockHdrから計算するケース
-
allocate時の余剰ブロックの分割・再登録処理
-
-
ユーザから返却されたUsedBlockHdrから計算するケース
-
deallocate時の隣接ブロックの割当済みフラグ確認
-
-
-
各々計算元のヘッダの状態は確認済み
-
空きブロックの非隣接性から*mut Free→*mut Used, *mut Used→*mut Freeというふうにしか計算できないようにし、 wf-nessが不完全な箇所は、その場でポインタ演算の議論をする
-
2024/03/27
-
allocate()の移植: 概ね完了-
残りはnext_phys_blockのインタフェース周りとヘッダ更新時のメモリ書き込み
-
GhostTlsfとヘッダポインタからヘッダへの権限を取り出すための簡単な方法を作ったほうが良さそう
-
2024/03/28
-
allocate()の移植が完了 -
deallocate()の移植