【问题标题】:How to fix "double free or corruption" error?如何修复“双重免费或损坏”错误?
【发布时间】:2021-07-09 04:40:48
【问题描述】:

我一直在使用 JNI 编写的 Java 中的 C++ 库。

操作系统:Ubuntu 16.04

代码:

JNIEXPORT jint JNICALL Java_FocasUser_Focas2_getMacroDataArray
(JNIEnv *env, jobject instance, jint handle, jint startIndex, jint endIndex, jdoubleArray jArr) {

    int len = env->GetArrayLength(jArr);
    IODBMR *macro;
    macro = (IODBMR *) malloc(8 + (8 * len));

    clock_t before = clock();
    if (PrintLogCat) LOGGER.debug("cnc_rdmacror.. start Index: [%d]\t endIndex:[%d]\t",1);
    short ret = cnc_rdmacror(handle, startIndex, endIndex, 8 + (8 * len), macro);

    int elapsedTime = calcElapsedTime(before, clock());
    loggerStream << "[" << "cnc_rdmacror" << "]=" << ret << "\tElapsed Time ms : " << elapsedTime;
    fileLogger(loggerStream.str(), LOG_FILE_WRITE_COND(elapsedTime));
    
    loggerStream.str("");

    jdouble *arr = env->GetDoubleArrayElements(jArr, 0);
    double temp = 0;

    if (ret == EW_OK) {

        for (int idx = 0; idx <= endIndex - startIndex; idx++) {
            temp = macro->data[idx].mcr_val;
            for (int i = 0; i < macro->data[idx].dec_val; i++) {
                temp = temp / 10;
            }
            arr[idx] = temp;
        }
    } else {

        env->ReleaseDoubleArrayElements(jArr, arr, 0);

        free(macro);
        free(arr);
        macro = NULL;
        arr = NULL;
        env->DeleteLocalRef(instance);
        if (PrintLogCat) LOGGER.error("getMacroValue pmc error : %d", ret,1);
        return -1;
    }

    env->ReleaseDoubleArrayElements(jArr, arr, 0);


    free(macro);
    free(arr);

    arr = NULL;
    macro = NULL;
    
    env->DeleteLocalRef(instance);

    return 0;
}

“双重释放或损坏”出现在“free(arr)”行。 我在“free(arr)”之前初始化然后没问题,但我不确定它是否正常。 我该如何解决?

非常感谢您,

日志:https://www.evernote.com/l/AtWkcucPzCZMN4b61B9rvNLCZazK95mk3Eo/

【问题讨论】:

  • 通过使用std::vector而不是malloc来摆脱手动内存管理。那么这些对free 的调用都不需要在那里,把事情搞砸了。事实上,对于健全的 JNI 编程,最好使用 RAII 对象,例如std::vector,而不是手动管理内存。代码变得更简单、更易于维护,并且实际上消除了内存泄漏问题。
  • 您已经使用ReleaseDoubleArrayElements 释放了arr 指向的数组。是什么让您认为您也需要free?作为一般规则,如果您没有从 malloc 获得任何东西,您不应该 free 它(除非相关文档明确说明您应该这样做)。
  • 您可以考虑(如果在 Linux 上)使用valgrind;用调试信息编译大部分代码,所以使用g++ -Wall -Wextra -g -O

标签: c++ java-native-interface fatal-error


【解决方案1】:

正如 cmets 中所提到的,使用 std::vector 而不是使用 mallocfree 进行手动内存管理,该功能变得更加简单。 不仅更简单,而且即使没有完全消除,内存泄漏的可能性也会最小化。

另外,arr不是使用malloc分配的,所以调用free(arr);是不正确的。

这是您重写的函数(未经测试),它完全消除了对 free() 的所有调用:

#include <vector>
//... 
struct LocalRefHandler
{
    jobject *m_pObject;
    JNIEnv *m_pEnv; 

    LocalRefHandler(JNIEnv* pEnv, jobject* instance) : 
                      m_pEnv(pEnv), m_pObject(instance) {}

    // Release the resources  
    ~LocalRefHandler() { m_pEnv->DeleteLocalRef(*m_pObject); }
};

JNIEXPORT jint JNICALL Java_FocasUser_Focas2_getMacroDataArray
(JNIEnv *env, jobject instance, jint handle, jint startIndex, jint endIndex, jdoubleArray jArr) {

    // This will automatically call DeleteLocalRef when the function exits 
    LocalRefHandler refHandler(env, &instance); 

    int len = env->GetArrayLength(jArr);

    // This will clean up the memory for us when the function returns 
    std::vector<char> vc(8 + (8 * len));

    // point to the underlying data
    IODMBR* macro = static_cast<IODMBR*>(vc.data());

    clock_t before = clock();
    if (PrintLogCat) LOGGER.debug("cnc_rdmacror.. start Index: [%d]\t endIndex:[%d]\t",1);
    short ret = cnc_rdmacror(handle, startIndex, endIndex, 8 + (8 * len), macro);

    int elapsedTime = calcElapsedTime(before, clock());
    loggerStream << "[" << "cnc_rdmacror" << "]=" << ret << "\tElapsed Time ms : " << elapsedTime;
    fileLogger(loggerStream.str(), LOG_FILE_WRITE_COND(elapsedTime));
    
    loggerStream.str("");

    jdouble *arr = env->GetDoubleArrayElements(jArr, 0);
    double temp = 0;

    if (ret == EW_OK) {
        for (int idx = 0; idx <= endIndex - startIndex; idx++) {
            temp = macro->data[idx].mcr_val;
            for (int i = 0; i < macro->data[idx].dec_val; i++) {
                temp = temp / 10;
            }
            arr[idx] = temp;
        }
    } else {
        env->ReleaseDoubleArrayElements(jArr, arr, 0);
        if (PrintLogCat) LOGGER.error("getMacroValue pmc error : %d", ret,1);
        return -1;
    }

    env->ReleaseDoubleArrayElements(jArr, arr, 0);
    return 0;
}

std::vector 将在函数返回时自动释放内存,无论函数返回的原因或位置如何

这很重要的原因是无论函数返回到哪里,std::vector 都会自动“释放”内存。这包括是否由于某种原因引发异常。所以不仅不用担心多个return点,即使抛出异常,vector也会释放内存。

如果要使用 JNI,这种编程方法,即 RAII,是必不可少的。

由于抛出异常的可能性有点高,因此必须在退出 JNI 函数时清理所获取的任何资源(例如分配的内存、文件句柄、各种 JNI 结构等)。使用在销毁时自动清理的对象比处理多个 try/catch 块(手动清理对象)要容易得多。

例如,您在多个地方调用env-&gt;DeleteLocalRef(instance);。如果抛出异常,而您没有机会调用 env-&gt;DeleteLocalRef 怎么办? JNI 编程是几乎必须使用 RAII 技术的主要示例。

请注意,LocalRefHandler 是一个创建的结构,当这种类型的对象被销毁时,DeleteLocalRef 会被调用。从 JNI 环境指针和实例创建 refHandler 变量后,您可以看到我们不再需要在代码中的 return 点处添加 DeleteLocalRef 调用 -- DeleteLocalRef 无论如何都会被调用一旦refHandler 超出范围,函数就会退出的原因。

您还可以创建一个类似的类来处理env-&gt;GetDoubleArrayElementsenv-&gt;ReleaseDoubleArrayElements,以便在函数退出时自动清理这些资源。

最重要的是,您应该为 JNI 目的构建自己的这些小型 RAII 对象的代码库,以便随时可以使用它们。

【讨论】:

  • IODMBR* macro = static_cast&lt;IODMBR*&gt;(vc.data()); 应该是 reinterpret_cast,对吧?另请注意,在应用 p0593(为低级操作隐式创建对象)之前,此操作为未定义行为。更安全的方法是创建一个正确长度的std::vector&lt;IODMBR*&gt;,然后将sizeof(IODMBR*) * @some vectorname@.size() 作为缓冲区大小传递给cnc_rdmacror
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
相关资源
最近更新 更多