C++ C++;Linux(而不是Windows)上的内存泄漏(或奇怪行为)

C++ C++;Linux(而不是Windows)上的内存泄漏(或奇怪行为),c++,linux,memory-leaks,C++,Linux,Memory Leaks,在我的映像应用程序中,我在Linux上遇到了一些无法解释的内存行为。具体来说,应用程序似乎保留了一定数量的驻留内存,并且永远不会释放它。在我们的应用程序中,当分配更大的块时,例如四个会话,每个会话使用3Gb,一旦释放了所有资源,基线可能会跳转1GB,然后在一段时间内保持不变。在以后的某个时候,它可能会再次跳转。Windows上完全相同的操作序列演示了基线保持不变的预期内存行为,即内存使用恢复到应用程序启动时的状态 我们对代码进行了广泛的分析,以确认相同数量的分配/删除,并在valgrind中进行

在我的映像应用程序中,我在Linux上遇到了一些无法解释的内存行为。具体来说,应用程序似乎保留了一定数量的驻留内存,并且永远不会释放它。在我们的应用程序中,当分配更大的块时,例如四个会话,每个会话使用3Gb,一旦释放了所有资源,基线可能会跳转1GB,然后在一段时间内保持不变。在以后的某个时候,它可能会再次跳转。Windows上完全相同的操作序列演示了基线保持不变的预期内存行为,即内存使用恢复到应用程序启动时的状态

我们对代码进行了广泛的分析,以确认相同数量的分配/删除,并在valgrind中进行了验证,没有显示任何泄漏

当图像缓冲区是一个简单的字符数组时,分配和释放的行为与预期的一样。如果图像缓冲区是一个包含char数组的类,那么它也会按预期工作。如果我有一个容器类(DicomImageData)保存图像缓冲区类,则会出现问题。它已被隔离,如下所示

首先用输入参数(下面的C++代码中的工作循环泄漏)设置为0。第一次运行将分配2.9GB内存,然后释放它,如上图所示。在第二次和所有后续运行中,它将分配2.9GB的内存,但不会释放

变通

我很幸运地发现了一个“变通”方法,但是如果我创建一个中间变量来保存DicomImageData对象的向量,这个问题就不会再发生了。要进行验证,请在输入参数设置为1的情况下再次运行,然后执行相同的顺序。内存将持续增长到2.3GB,然后被释放。我已经在各种Ubuntu和RHEL实现上进行了测试,它们的行为都是一致的

作为参考,开发系统是Ubuntu18.04.4LTS,有72个内核和32GB内存,但是使用8个内核和16GB运行时表现出相同的行为,尽管16GB系统将更快地进行测试。我使用的是gcc 7.5.0

任何帮助都将不胜感激

我的cpp代码和Makefile如下 C++源代码< /p>

#define WORK_AROUND_LEAK 0

#include <stdio.h>
#include <iostream>
#include <vector>
#include <string.h>

int totalImages = 2299;

// Class to hold image data
class PixelData {
public:
    char * buffer;
    size_t size;

    PixelData()
        : buffer(NULL)
        , size(0) {
    }

    ~PixelData() {
         if (buffer) {
             delete[] buffer;
             buffer = NULL;
         }
        size = 0;
    }
};

// Class to hold above defined Pixel data and some other meta-data relevant to the image
class DicomImageData {
public:
    PixelData * pixelData;

    DicomImageData() {
        pixelData = NULL;
    }

    ~DicomImageData() {
        if (pixelData) {
             delete pixelData;
             pixelData = NULL;
        }
    }
};


using namespace std;

int main() {

#if WORK_AROUND_LEAK
    std::vector<DicomImageData *> imageDataArray[1];
#endif
    char c = 'c';
    int executionCount(0);
    size_t pixelDataSize(2 * 1024 * 520);
    do {
        int numIterations = 1;
        executionCount++;
        cout << "\nStarting execution test1 " << executionCount << endl;

        int iter = 0;
        cout << "Starting execution test iterations " << iter << endl;

        std::vector<DicomImageData*> imageData;

        for (size_t i = 0; i < totalImages; i++) {

            char * readPixelData = new char [pixelDataSize + 1 + (i*3)];
            memset(readPixelData, '@', pixelDataSize + 1);

            DicomImageData * dicomImageData = new DicomImageData();
            if (dicomImageData) {
                // Create new pixel data struct and set
                PixelData * pixelData = new PixelData();
                pixelData->buffer = readPixelData;
                pixelData->size = pixelDataSize;
                dicomImageData->pixelData = pixelData;

                imageData.push_back(dicomImageData);
            }
#if WORK_AROUND_LEAK
        imageDataArray[0] = imageData;
#endif
        }
        printf("\nPress ENTER to release memory \n");
        scanf("%c", &c);


        iter = 0;
        cout << "Starting release iterations " << iter << endl;
#if WORK_AROUND_LEAK
        imageData = imageDataArray[0];
#endif
        for (size_t i = 0; i < totalImages; i++) {

            PixelData *pixelData = imageData[i]->pixelData;
            delete[] pixelData->buffer;
            pixelData->buffer = NULL;

            delete imageData[i]->pixelData;
            imageData[i]->pixelData = NULL;
            
            delete imageData[i];
            imageData[i] = NULL;
        }

        imageData.clear();

        printf("Press ENTER to run another test or press X to exit \n");
        scanf("%c", &c);
    } while (c != 'x' && c != 'X');
    
}

P>我的知识C++不将变量初始化为空值,所以

    ~DicomImageData() {
        if (pixelData) {
             delete pixelData;
             pixelData = NULL;
        }
    }
这可能是问题所在。试试这个

// Class to hold image data
class PixelData {
public:
    char * buffer = nullptr;
    size_t size;

    PixelData()
        : buffer(nullptr)
        , size(0) {
    }

    ~PixelData() {
         if (buffer!=nullptr) {
             delete[] buffer;
             buffer = nullptr;
         }
        size = 0;
    }
};

对具有指针成员变量的其他类执行相同的
nullptr
初始化。另外,您可以使用
唯一的\u ptr
来保存指针,从而完全避免这种情况。有关如何使用
唯一\u ptr
保存阵列的信息,请参见本节。总是尝试使用智能指针直到你不能。

< P>我的知识C++不将变量初始化为空值,所以

    ~DicomImageData() {
        if (pixelData) {
             delete pixelData;
             pixelData = NULL;
        }
    }
这可能是问题所在。试试这个

// Class to hold image data
class PixelData {
public:
    char * buffer = nullptr;
    size_t size;

    PixelData()
        : buffer(nullptr)
        , size(0) {
    }

    ~PixelData() {
         if (buffer!=nullptr) {
             delete[] buffer;
             buffer = nullptr;
         }
        size = 0;
    }
};

对具有指针成员变量的其他类执行相同的
nullptr
初始化。另外,您可以使用
唯一的\u ptr
来保存指针,从而完全避免这种情况。有关如何使用
唯一\u ptr
保存阵列的信息,请参见本节。始终尝试使用智能指针,直到无法使用为止。

“特别是,应用程序似乎保留了一定数量的驻留内存,并且永远不会释放它。”正确。仅仅因为你删除了一些东西,这并不意味着内存会被释放到操作系统中。C++不这样工作。库只是将这个内存池循环使用,以备将来程序可能
new
使用。这不是内存泄漏。动态内存管理通常很昂贵(例如,请求操作系统分配内存,而操作系统又分配硬件资源)。因此,C或C++标准库的一些实现优化了内存使用——当程序释放内存时,该内存被标记为重用,而不立即返回给OS。然后,程序对内存的后续请求被分配给以前使用过的内存,而不是从操作系统请求。操作系统可以类似地管理硬件资源。这些效果可能意味着程序在内存中保存一段时间。如果您希望更多地控制内存分配和释放,您可能希望绕过C++标准库分配器,并使用Linux和Windows上的函数直接从操作系统内核请求内存。这样,您就可以使用这些函数的文档中列出的相应函数显式地将该内存返回到内核。可能有助于理解如何使用
mmap
直接从内核分配内存。您可以使用
munmap
将其直接返回到内核。看起来代码实际上可以正常工作。但这是一个写得很差的C++,有几个问题。以if为例进行代码检查“特别是,应用程序似乎保留了一定数量的驻留内存,并且永远不会释放它。”正确。仅仅因为你删除了一些东西,这并不意味着内存会被释放到操作系统中。C++不这样工作。库只是将这个内存池循环使用,以备将来程序可能
new
使用。这不是内存泄漏。动态内存管理通常很昂贵(例如,请求操作系统分配内存,而操作系统又分配硬件资源)。因此,C或C++标准库的一些实现优化了内存使用——当程序释放内存时,该内存被标记为重用,而不立即返回给OS。然后,程序对内存的后续请求被分配给以前使用过的内存,而不是从操作系统请求。操作系统可以类似地管理硬件资源。这些效果可能意味着程序在内存中保存一段时间。如果您希望更多地控制内存分配和释放位置,则可能希望绕过C++标准库分配器AN。