動的確保したメモリー領域の解放戦略

[code]

    public class Greeter {
        public static String greet(final String name) {
            final String message = String.format("こんにちは、%s！", name);
            return message;
        }
    
        public static void main(final String[] args) {
            final String message = greet("世界");
            System.out.println(message);
    
            // String.format() が確保した領域は(そのうち)GCが回収する
        }
    }
    
[/code]

上のJavaコードのうち、`greet`メソッドをCライブラリに置き換えようと考えています。  
上記のコードはJavaとしては合法だと考えていますが、同じようなノリのC実装はメモリリークするので問題があるかと思います。

[code]

    char* greet(char *name) {
        char *message = calloc(256, sizeof(char));
        sprintf(message, "こんにちは、%s！", name);
        return message;
    }
    
    int main(int argc, char **argv) {
        char *message = greet("世界");
        printf("%s\n", message);
        return 0;
        // calloc() が確保した領域が解放されないまま
    }
    
[/code]

そこで、後述のような、動的に確保した領域の解放方法(及び、対になる確保方法)を考えていました。  
が、そもそもこのような要求は別に特殊というわけでも無いと思うので、イディオム、パターン、ライブラリ実装などで一般化されているのでは、という思いも浮かんできました。

この件に関して、参考になりそうな(検索キーワードになりそうな)名前や実装などはあるでしょうか。

* * *

以下、自分が検討していた対応方法です。

1.  
解放処理を提供するパターン。  
実際には動的確保する関数としない関数が混在することになると思うので、呼び忘れとかが発生しそう。

[code]

    void greet_retval_dtor(char *retval) {
        free(retval);
    }
    
    // 呼び出し元
        char *message2 = greet("デストラクタ");
        printf("%s\n", message2);
        greet_retval_dtor(message2);
    
[/code]

2.  
呼び出し元が確保した領域に呼び出し先が書き込むパターン。  
あらかじめ必要なサイズの当たりがつくような処理でないと利用できない  
(あるいは、先に必要なサイズを教えてもらえるような関数が別途必要)。

[code]

    void greet_prepared_buffer(char *name, char *message) {
        sprintf(message, "こんにちは、%s！", name);
    }
    
    // 呼び出し元
        char *buff = calloc(256, sizeof(char));
        greet_prepared_buffer("バッファ", buff);
        printf("%s\n", buff);
        free(buff);
    
[/code]

3.  
コールバックで呼び出し元に返すパターン。  
関数のスコープと確保した領域のライフタイムが一致するという点では自然な気が。

[code]

    void greet_callback(char *name, void (*cb)(char *message)) {
        char *message = calloc(256, sizeof(char));
    
        sprintf(message, "こんにちは、%s！", name);
        cb(message);
    
        free(message);
    }
    
    // 呼び出し元
    void callback(char *message) {
        printf("%s\n", message);
    }
    
        greet_callback("コールバック", callback);
    
[/code]

* * *

(追記)  
実際には、呼び出し元はJava([Project
Panama](https://openjdk.java.net/projects/panama/)利用)、呼び出し先CライブラリはRustで実装しようと考えています。  
ただ、今回の件は実装言語に依存する話ではないのでは、と考えています(ので、C言語以外だと解法がある、というのであればそれも知りたいです)。

以下は前述のパターン2(呼び出し元が領域確保するパターン)を実装した…つもりのコードです。

[呼び出し元コード](https://github.com/yukihane/stackoverflow-
qa/blob/141e262c6b1d0b14193ee297558f357ca27637a2/so62868/mainjava/src/Main.java)(Java,
14-panama):

[code]

    public static void main(final String[] args) {
        final Scope scope = greeter_lib.scope();
    
        final Pointer<Byte> name = scope.allocateCString("錆びたお茶");
        final long size = 256;
        final Pointer<Byte> message = scope.allocateArray(NativeTypes.UINT8, size).elementPointer();
    
        greeter_lib.greet(name, message, size);
    
        final String retval = Pointer.toString(message);
        System.out.println(retval);
    
    }
    
[/code]

[呼び出し先コード](https://github.com/yukihane/stackoverflow-
qa/blob/141e262c6b1d0b14193ee297558f357ca27637a2/so62868/librust/src/lib.rs)(Rust,
1.41.0):

[code]

    #[no_mangle]
    pub unsafe extern "C" fn greet(name: *const c_char, message: *mut c_char, count: size_t) {
        let name = CStr::from_ptr(name);
        let name = name.to_str().unwrap();
        let text = format!("こんにちは、{}！", name);
        let text = CString::new(text).unwrap();
    
        message.copy_from(text.as_ptr(), count);
    }
    
[/code]




Javaの文字列はUTF-16なので、相互運用するのであればネイティブコード側でもUTF-16を扱える言語のほうが効率的だし楽です。JNI (Java
Native Interface) のAPI関数には`char*`/`const char*`のインターフェイスも用意されていますが、これはModified
UTF-8と呼ばれる特殊形式であり、またUTF-16との余計な変換コストもかかります。

C/C++のような **ネイティブ言語** と、Java/C#のような **マネージ言語** を相互運用する際、 **ネイティブヒープ** と
**マネージヒープ**
ではメモリ管理の扱いが異なります。マネージ型を受け取って処理してマネージ型を返すような単純なケースなのか、それともネイティブヒープへのポインタをマネージコード側で保持しておきたいのか、によって方針も変わってきます。また、相互運用は言語やフレームワークごとに用意されている方法に従う必要があります。JavaであればJNIを使う、C#であればP/InvokeやCOM相互運用あるいはC++/CLIやC++/CXを利用する、などです。

Javaとネイティブ言語間のやりとりの場合、まずは素直にJNIを使って、C++ (C++11以降) でネイティブ側を実装する方法が良いと思います。  
（おそらく Project Panama はユーザーが少なく、まともな開発情報が得られるとは思えません）

`JNIEnv::NewString()`で返却される`jstring`はJavaの`String`型（マネージヒープ）に対応するJNIローカル参照で、nativeメソッド呼び出しの制御がJava側に返るとGC管理対象になり、解放をJVM任せにできます。

[code]

    // C++ 側:
    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_example_Greeter_greet
    (JNIEnv* env, jclass /* sender */, jstring name) {
        if (!name) {
            return nullptr;
        }
        const jsize srcLength = env->GetStringLength(name);
        const jchar* nativeSrcBuffer = env->GetStringChars(name, nullptr);
    
        jstring resultString = nullptr;
        try {
            std::u16string str = u"こんにちは、";
            if (srcLength > 0 && nativeSrcBuffer) {
                str += std::u16string(reinterpret_cast<const char16_t*>(nativeSrcBuffer), static_cast<std::size_t>(srcLength));
            }
            str += u"！";
            resultString = env->NewString(reinterpret_cast<const jchar*>(str.c_str()), static_cast<jsize>(str.length()));
        } catch (...) {
        }
    
        if (nativeSrcBuffer) {
            env->ReleaseStringChars(name, nativeSrcBuffer);
            nativeSrcBuffer = nullptr;
        }
        return resultString;
    }
    
[/code]

[code]

    // Java 側:
    package com.example;
    
    public class Greeter {
        static {
            System.loadLibrary("xxx");
        }
        public static native String greet(String name);
        ...
    }
    
[/code]

ただし、計算量の多い処理の高速化や、既存の膨大なネイティブコード資産の活用など、よほどの理由がないかぎりJava/ネイティブコード相互運用には手を出すべきではありません。たいていの場合、Javaだけで組んだほうがコードのメンテナンス性の観点から好ましく、またヘタにネイティブコードを使うとかえってパフォーマンスが低下することもあります。

一般論で言うと、異なる言語間あるいは異なるABI間でのネイティブヒープメモリのやりとりは、通例バックエンド（ライブラリ）側にバッファの確保関数と解放関数を用意して、フロントエンド（アプリケーション）側で確保と解放を実行します。バッファサイズが不明な場合は別途必要バッファ長の問い合わせ関数を用意するケースもあります。C言語関数形式で書かれたAPI（例えばOpenCLやVulkanなど）の設計が参考になるはずです。

上位レベルの言語側では解放処理を自動化してメモリリークを防ぐため、例えばC++であればコンストラクタ・デストラクタによるRAII、Javaであれば`AutoCloseable`、C#であれば`IDisposable`などのリソース管理フレームワークにネイティブリソースの確保と解放を封じ込めるのが定石です。有名なC言語関数形式のAPIには、対応するC++/Java/C#向けのラッパー（バインディング）が公式または第三者によって開発されていることが多く、そういったものもまた参考になるはずです。

Windows APIやCOM (Component Object Model)
では`GlobalAlloc()`/`GlobalFree()`や`CoTaskMemAlloc()`/`CoTaskMemFree()`や`SysAllocString()`/`SysFreeString()`など、フレームワークやOSが用意したAPIでメモリ確保が行なわれるので、呼び出し側で不要になったときに対応APIを使って解放せよ、という設計になっているものもあります。ATL/MFCにはRAIIラッパーが用意されています。

なお、C++コードではポインタあるいは`intptr_t`/`uintptr_t`を、C#コードではポインタあるいは`IntPtr`/`UIntPtr`を使ってネイティブヒープのポインタやハンドルを保持することができますが、Javaコードではポインタやハンドルを直接保持することができないため、`byte[]`などを使う必要があります（`int`や`long`を使うと移植性が下がります）。

