GTK2 application memory leak using multiple images

2019-08-05 20:12发布

问题:

This is a follow up to my last question regarding GTK2 dealing with resource usage.

The application is properly displaying images, however it now appears to be leaking some memory every time GtkImages are loaded from disk and placed onto a fixed frame for absolute positioning.

I'm using the following basic method called every few seconds to load and display different sets of images.

int DisplaySymbols( GameInfo *m )
{
    // variable declarations removed for brevity 
    // error checking needs to be added
    GtkWidget *image;

    pos_y = 150;
    for( y = 0; y < 3; y++ )
    {
    pos_x = 187;
        for( x = 0; x < 5; x++ )
        {
            image=gtk_image_new_from_file( fileName );
            gtk_fixed_put(GTK_FIXED(frame), image, pos_x, pos_y);
        pos_x += symbols[i].pixel_width;
        }
    pos_y += symbols[i].pixel_height;
    }
    gtk_widget_show_all(window);
    return( 0 );
} 

I've read parts of the GTK+ documentation regarding resource usage and I just can't quite figure out how to use the API calls to prevent memory leaks.

Some thoughts and/or questions I have:

  • Should I create some image holding widgets in the fixed frame to more easily manage the images placed in the frame?
  • What is my code doing or not doing that is causing memory to be leaked? I'm likely not releasing the GtkImage widgets?
  • It seems a bit inefficient to load the images from disk every time I need to use them. How can I read them into memory and then display them in the frame as needed?

Partial source code is as follows:

//Compile me with: gcc -o leak leak.c $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0)

// include files removed for brevity
/* GTK */
#include <gdk/gdkx.h>
#include <gtk/gtk.h>

typedef struct
{
    unsigned int pixel_width, pixel_height;
    gchar fileName[20];
}symbol_t;

static symbol_t symbols[] =
{
    /* only showing 2 of eight - brevity */
    { 118, 107, "images/LO.jpg" },
    { 118, 107, "images/L1.jpg" }
};

typedef struct
{
    char SpinResult[3][5];      // index of images pointing into symbols struct
}GameInfo;

GameInfo egm;

GtkWidget *frame;       /* for absolute positionining of widgets */
GtkWidget *window;

/**** prototypes ****/
    // remove for brevity
/********************/

// init random number generator
int Init( void )
{
    // create random number - brevity
}

// Determine spin outcome and store into egm.SpinResult array
int DoSpin( GameInfo *egm )
{
    // generate matrix of indexes into symbols structure
    // so we know what symbols to display in the frame
}

int DisplaySymbols( GameInfo *egm )
{
    // variable declarations removed - brevity
    GtkWidget *image;

    pos_y = 150;
    for( y = 0; y < 3; y++ )
    {
        pos_x = 187;
        for( x = 0; x < 5; x++ )
        {
            image = gtk_image_new_from_file( symbols[i].fileName );             
            gtk_fixed_put(GTK_FIXED(frame), image, pos_x, pos_y);
            pos_x += symbols[i].pixel_width;
        }
        pos_y += symbols[i].pixel_height;
    }
    gtk_widget_show_all(window);
    return( 0 );
} 

void btnSpin_clicked(GtkWidget *button, gpointer data)
{
    DoSpin( &egm );
    DisplaySymbols( &egm );
    return;
}

GtkWidget *SetupWindow(gchar *data, const gchar *filename)
{
    // window setup code removed for brevity    
    return(window);
}

int main (int argc, char *argv[])
{
    GtkWidget *btnSpin, *btnExit;
    float spinDelay = 2.0;     

    Init();    
    gtk_init (&argc, &argv);
    window = SetupWindow("Tournament", "images/Midway_Madness_Shell.jpg");
    g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);

    frame = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(window), frame);

    btnSpin = gtk_button_new_with_label("Spin");
    gtk_widget_set_size_request(btnSpin, 70, 40);
    gtk_fixed_put(GTK_FIXED(frame), btnSpin, 720, 540);
    g_signal_connect(G_OBJECT( btnSpin ), "clicked", G_CALLBACK(btnSpin_clicked), NULL );

    btnExit = gtk_button_new_with_label("Exit");
    gtk_widget_set_size_request(btnExit, 70, 40);
    gtk_fixed_put(GTK_FIXED(frame), btnExit, 595, 540);
    g_signal_connect(G_OBJECT( btnExit ), "clicked", G_CALLBACK(btnExit_clicked), NULL );

    DoSpin( &egm );
    DisplaySymbols( &egm );

    gtk_widget_show_all(window);
    gtk_main ();
    return( 0 );
}

I would be very grateful for a detailed explanation of how to resolve this problem as well as some example code as I'm having a hard time understanding how to apply what I've read from the GTK2 documentation which appears to just provide the definition of objects and the function calls. A handsome bounty can be provided if needed for a very thorough response.

If it is useful to have all of the code, I've provided it here.

EDIT: The memory leak was fixed in two ways.

  1. destroy and re-create the GtkFixed container before redrawing the images as suggested by nobar.
  2. use a two-dimensional array of pointers to GtkWidget to hold the pointers to each image as they are drawn. When it's time to redraw the images the widget pointers in the two-dimensional array are first destroyed like this:

    int ReleaseImages( void ) { u_int8_t y,x;

    /* release images */
    for( y = 0; y < 3; y++ )
    {
        for( x = 0; x < 5; x++ )
        {
            gtk_widget_destroy( ptrImages[y][x] );
        }
    }
    return( 0 );
    

    }

    Now the image resources are released and new images can be redrawn.

回答1:

From the documentation of GtkFixed:

For most applications, you should not use this container! It keeps you from having to learn about the other GTK+ containers, but it results in broken applications. With GtkFixed, the following things will result in truncated text, overlapping widgets, and other display bugs...

I would like to emphasize overlapping widgets from the above quote. As your application continues to run, you continue to add more and more images to the GtkFixed container. Since you never remove images from the container, they continue to require memory even though you can no longer see them.

Not to put too fine a point on this, but the reason you can't see the old images is that you have covered them up with new images. As far as the GtkFixed widget is concerned, every image you have added since the application began is still in there.

To fix this, you need to remove the old images. There are many ways that you could do this. The simplest might be to destroy the entire container, but there are some complications with that since you also put the buttons into that container.

The better solution is probably to follow the advice from the above quote and don't use GtkFixed -- or at least layer your application so that GtkFixed is only used for the images (you could place the buttons outside of the GtkFixed). But as noted, this might require you to learn about the other GTK+ containers (unless you just use a second GtkFixed container to hold the images while the first GtkFixed container holds both the buttons and the second container).