6

我有一个相当大的程序,通常运行得很好,但运行时使用了大量的内存。这是一种收集大量数据的机器学习方法,因此这通常是可以的,但是即使在收集了所有数据之后,内存也会增长并且增长得非常快,所以我使用 valgrind massif 来找出问题所在。massif 的堆树的顶部如下所示:

99.52% (60,066,179B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->43.50% (26,256,000B) 0x439785: Image::Image(SDL_Surface*) (Image.cpp:95)
| ->43.50% (26,256,000B) 0x437277: EncodedFeature::forwardPass() (EncodedFeature.cpp:65)
....

所以我想,嗯,也许构造的 Image 没有释放,但没有:

void EncodedFeature::forwardPass()
{
    // Get image:
    Image* img = new Image(screen);

    // Preprocess:
    if(preprocessor)
        preprocessor->process(img);

    // Do forward pass:
    encoder->encode(img, features);

    delete img;
}

所以对于 Image 构造函数:

Image::Image(SDL_Surface* surface)
{
    this->width = surface->w;
    this->height = surface->h;
    pixels = new int*[width];
    for(int i = 0; i < width; i++)
        pixels[i] = new int[height];

    for(int x = 0; x < surface->w; x++)
        for(int y = 0; y < surface->h; y++)
            pixels[x][y] = getPixelFromSDLSurface(surface, x, y);
}

只需分配一个稍后在析构函数中释放的像素数组:

Image::~Image()
{
    if(!pixels)
        return;

    for(int x = 0 ; x < width; x++)
        delete[] pixels[x];

    delete[] pixels;
}

所以最后一个罪魁祸首:

Uint32 Image::getPixelFromSDLSurface(SDL_Surface *surface, int x, int y)
{
    if(!surface)
        return 0;

    // Got this method from http://www.libsdl.org/cgi/docwiki.fcg/Pixel_Access
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;
        break;

    case 2:
        return *(Uint16 *)p;
        break;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;
        break;

    case 4:
        return *(Uint32 *)p;
        break;

    default:
        return 0;       /* shouldn't happen, but avoids warnings */
    }
}

正如评论中提到的,我是从 SDL wiki 获得的,所以我希望那里没有泄漏。在我的情况下,bpp 实际上总是 1,所以它只是返回地址 p 处的 int,这对我来说听起来并没有泄漏。

我在我的智慧尽头。谁能想到记忆去了哪里?我的意思是,地块专门指向 Image 构造函数,但我看不出那里有什么问题......

非常感谢您查看我的问题!

最大限度


回答您的意见:

你是对的,我不需要 img 作为指针。我来自 Java 背景,所以我只希望一切都是指针 :) 更改了它但没有帮助。

第 95 行在构造函数的第一个 for 循环内: pixels[i] = new int[height];

在其中一个预处理器中,我确实调整了图像的大小,但我这样做是调用我的重置函数,它应该确保旧数组被删除:

void Image::reset(int width, int height)
{
    if(pixels)
    {
        // Delete old image:
        for(int x = 0 ; x < width; x++)
            delete[] pixels[x];

        delete[] pixels;
    }

    this->width = width;
    this->height = height;
    pixels = new int*[width];
    for(int i = 0; i < width; i++)
        pixels[i] = new int[height];
}

之后我重新填充像素值......

任何地方都没有抛出异常。

你会建议我在哪里使用智能指针?

感谢所有的答案!

4

2 回答 2

6

我不认为您在图像类中正确表示像素。vector我认为您可以使用正确的无符号类型(uint32_t?)的简单一维。

(在标题中):

class Image {
protected:
    std::vector<uint32_t> pixels;
    ...
};

(在实施文件中)

size_t Image::offset(unsigned x, unsigned y) {
    return (y * width) + x;
}

Image::Image(const SDL_Surface* surface)
{
    width = surface->w;
    height = surface->h;
    pixels.reserve(width * height);
    for(unsigned x = 0; x < width; x++)
        for(unsigned y = 0; y < height; y++)
            pixels[offset(x, y)] = getPixelFromSDLSurface(surface, x, y);
}

然后,您的析构函数将完全为空,因为它无事可做:

Image::~Image() {
}

请注意,您需要使用该方法为任何给定的 x/y 对offset()获取正确的索引。vector

于 2013-08-15T15:02:21.613 回答
1

最可能的情况是在某些代码路径中没有调用您的重置或析构函数,从而泄漏了该内存。

幸运的是,C++ 具有防止此类泄漏的内置功能:它被称为vector.

所以首先你让你pixels看起来像这样:

typedef std::vector<int> PixelCol;
typedef std::vector<PixelCol> Pixels;
Pixels pixels;

现在我们在构造函数中调整它的大小:

Image::Image(SDL_Surface* surface)
: pixels(surface->w, PixelCol(surface->h))
{
    this->width = surface->w;
    this->height = surface->h;

    for(int x = 0; x < surface->w; x++)
        for(int y = 0; y < surface->h; y++)
            pixels[x][y] = getPixelFromSDLSurface(surface, x, y);
}

最后我们更新reset

void Image::reset(int width, int height)
{
    Pixels(width, PixelCol(height)).swap(pixels);
}

通过让语言管理您的内存,您可以完全消除代码中的任何此类内存管理错误。

于 2013-08-15T15:44:37.720 回答