对于任何应用程序,内存管理都是一个重要的方面,并且他随着应用程序的规模和复杂度增长。然而Gib提供了大量用来管理内存的函数,本小节将只涉及经常用到的这些函数。
内存片
高于2.1版本的内存分配器和内存段用来负责内存片分配。然而,在当前发布的版本中,内存片的引入大大地提高了效率。因此,本节只涉及内存片这种内存分配
方式。不管什么原因,如果您正在使用旧版的GLib,您应该在API文档中检查一下GMemChunk。
使用内存片的优点在于它避免了内存的过度浪费,并且解决了困扰内存块稳定性和性能的问题。通过Slab分配器解决了此问题。
与分配内存段类似,内存片可以高效地分配内存。这意味着她们可以被分成独立的对象,这些对象可以与两个指针一样小,也可以与许多相同大小的对象一样大。
Slab 内存分配器
最初Sun Microsystems的Jeff Bonwick设计了Slab分配器。 她不是最初设计需要的,而是一个用来帮助降低内存储器碎片问题的内存管理方案,这个问题是由系统大块分配内存造成的。
为了理解slab分配器,您应该了解上下文中slab和缓存的概念。缓存是用来存储一种且只是一种类型数据的高效内存段。 一个或多个slab组成了一个缓存。
最初每个对象都被标记为自由状态,这意味着么个slab都是空的。当一个进程向内核请求一个新的对象时,系统将试图在一个没有完全填满的slab上找到一
个用来存放这个对象的位置。如果没有找到存放这个对象的未填满slab,系统会从连续的物理内存中分配一个新的slab,并且把这个slab放到缓存中。
当一个slab装满时,她将被标记为已使用状态。
Slab分配器存在很多优点,但是最重要的是她申请的内存空间与真正分配的大小相同。这避免了内存碎片,并且极大地提高了分配效率。更多的信息,您可以在线阅读Jeff Bonwick的文章中关于slab分配器部分。
当您需要申请大块内存时,自然会用到系统的malloc()函数。虽然我们将在下一节会简单地讨论g_malloc()函数的用法及与她相关的函数,但是
只要您不打算申请内存分配之后不改变对象的大小,您应该用内存片为新的代码分配空间。内存片的一个限制条件是:系统为对象分配空间和释放它的大小是一样。
片分配器的使用方法有两种:为单个对象分配比两个指针大的任意内存空间,和为很多对象分配相同内存空间。列表6-1中的代码,描述了怎么为多个对象分配空间;她利用片分配器为存放100个对象的数组分配内存,并且将她释放。
列表 6-1 分配多个对象
#define SLICE_SIZE 10
gchar *strings[100];
gint i;
for (i = 0; i < 100; i++)
strings[i] = g_slice_alloc (SLICE_SIZE);
/* ... Use the strings in some way ... */
/* Free all of the memory after you are done using it. */
for (i = 0; i < 100; i++)
g_slice_free1 (SLICE_SIZE, strings[i]);
在列表6-1中,g_slice_alloc()用来给长度SLICE_SIZE的100个字符串分配空间。片分配器非常简单--您需要做的就是提供片应
该需要的内存大小。与malloc()相似,这个函数返回一个内存的gpointer,而不是类型转换过的对象。
在系统内部,GLib决定是否用slab分配器,还是将内存分配器委托于g_malloc()。当期望的内存片非常大时,用g_malloc()函数来实
现内存分配。GLib还提供了g_slice_alloc0()函数,她可以将返回的内存段初始化为0。
注意:内存片会根据运行时的情况选择最高效的内存分配方法,或者用slab分配器,或者g_malloc(),或者其他方法。然而,您可以通过将环境变量G_SLICE设置成always-malloc,强制她始终使用g_malloc()。
当您不再使用内存是,您应该用g_slice_free1()将其释放,以便您应用程序其他部分使用。这个函数释放了strings[i]中SLICE_SIZE大小的内存块。
g_slice_free1 (SLICE_SIZE, strings[i]);
在系统内部,内存通过与其分配相同的方式释放。因此,为了使用这个函数,您必须使用g_slice_alloc() 或 g_slice_alloc0()分配内存。
当您只需要为一个单例对象分配内存是,可以使用g_slice_new()。列表6-2描述了利用这个函数为一个对象分配内存的实例。
typedef struct
{
GtkWidget *window;
GtkWidget *label;
} Widgets;
Widgets *w = g_slice_new (Widgets);
/* Use the structure just as you would any other structure. */
w->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
w->label = gtk_label_new ("I belong to widgets!");
/* Free the block of memory of size "Widgets" so it can be reused. */
g_slice_free (Widgets, w);
如果您需要用片分配器分配一大块内存,不要使用列表6-1中的方法,可以调用call g_slice_new()。这个函数如下定义,她将g_slice_alloc()的返回值强制转换为您期望的类型。
#define g_slice_new(type) ((type*) g_slice_alloc (sizeof (type))。
除了g_slice_new(), GLib提供了g_slice_new0(),她利用g_slice_alloc0()将返回的片初始化为0。
您使用完内存之后,需要释放她。因为我们在列表6-2中分配了一片内存,所以可以用g_slice_free()将其释放,这个函数释放Widgets大小的一片内存,并且位置W释放。
Glib提供了大量包装函数,这些函数是对标准C库的包装。这一节会介绍其中的一些函数。
注意:您不需要校验下面任何函数调用正确与否,这一点很重要。如果任何分配内存的调用失败,GLib会自动终止应用程序,同时,一个错误发生的消息会打印到标准错误输出。
为了分配一个或更多个结构,您应该使用g_new()。这个函数接收到分配的数据类型和结构的数量。然后她返回一个指向内存的指针。
struct_type* g_new (struct_type, number_of_structs);
返回值已经被转换为正确的类型,所以没有必要再对对象进行类型转换了。如果您想默认初始化所有的结构为0,您应该使用g_new0()。
一个函数与C中的malloc()很相似。GLib提供了一个方便的包装函数g_malloc()。这个函数返回分配的字节数,或者返回所分配内存的指针。
gpointer g_malloc (gulong number_of_bytes);
最简单的计算分配的内存的字节数的方法是对数据类型执行sizeof()函数。返回的对象没有被自动进行类型转换,所以在大多数情况下,您应该小心对其进行类型转换。如果你想分配一段新内存并将器初始化为0,GLib还提供了g_malloc0()函数。
当使用g_malloc0()分配内存失败是,应用程序会中止。另外,您可以使用g_try_malloc(),当内存分配失败时,她不会中止应用程序,
而会返回空。当您的应用程序可以从分配内存失败恢复时,可以使用这个函数。使用g_try_malloc()是,处理返回值为空的情况很重要。
gpointer g_try_malloc (gulong number_of_bytes);
当您使用完一块内存后,为了她能重复利用,您应该将其释放。如果没有释放,您的应用程序会存在内存泄漏,这可不是什么好事。您可以调用call g_free()来释放一片内存。在GTK+ API中,这个函数可以用来释放很多函数返回的字符串。
void g_free (gpointer memory);
如果您显示地为一个对象分配了内存,但是没有提供自动销毁和释放的函数调用,您可以使用这个函数。举例来说,对于利用内存分片分配的一大块内存,您可以永
远不使用g_free()。 如果一片数据提供了释放自己的函数,您应该首选此函数。如果g_free()收到空内存,将会被忽略,函数会返回。
另外一个重要内存函数是g_memmove(),她用来移动内存片。举例来说,下面的g_memmove()可以用来移动字符串中以pos为起点,以len为长度的字符串的字符。
g_memmove (str + pos, str + pos + len, strlen(str) - (pos + len));
str[strlen(str) - len] = 0;
除了g_memmove(),我最后一次强调,当您想为一个或多个相同的大小的对象分配内存时,您应该使用内存分片,而不是使用g_malloc()。
GLib 提供了一个非常简单的用来查看您的应用程序内存分析的概况的方法。这可以通过调用g_mem_profile()得到,在您的应用程序的任何地方都可以。
在使用内存分析之前,您必须设置GMemVTable。类表6-3,描述了如何建立默认GMemVTable,并且在应用程序终止时,输出内存分析信息。
注意:
通过使用默认的GMemVTable,只调用g_malloc(),
g_free(),其友元函数会被计数。调用malloc()和free()不会被计数。而且,为了分析内存片,您应该设置环境变量G_SLICE为
always-malloc,这样可以强制她总是使用g_malloc().GLib内存分析器不会计算用slab分配器分配的内存。为了跟踪内存,您应
该使用一个外部的工具,例如Valgrind。
Listing 6-3 内存分析 (memprofile.c)
#include
int main (int argc,
char *argv[])
{
GSList *list = NULL;
/* Set the GMemVTable to the default table. This needs to be called before
* any other call to a GLib function. */
g_mem_set_vtable (glib_mem_profiler_table);
/* Call g_mem_profile() when the application exits. */
g_atexit (g_mem_profile);
list = (GSList*) g_malloc (sizeof (GSList));
list->next = (GSList*) g_malloc (sizeof (GSList));
/* Only free one of the GSList objects to see the memory profiler output. */
g_free (list->next);
return 0;
}
在您输出一个内存使用概要之前,您不得不用g_mem_set_vtable()设置GMemVTable。GMemVTable定义了新版本的内存分配
函数,并且是分析器使能,所以他们可以被GLib跟踪。这些包含malloc()、realloc()、free()、calloc()、
try_malloc()和try_realloc()。
虽然可以创建您自己的GMemVTable,但是GLib提供了一个预编译的版本的内存表glib_mem_profiler_table。几乎在任何情况下,默认内存表都会被使用。
在定义了GMemVTable之后,列表6-3调用g_atexit(),所以当应用程序退出时,g_mem_profile()会被调用。g_atexit()指定的函数必须接受无参并且无返回值。
下面列出了列表6-3中应用程序输出。这个输出根据您的GLib版本,系统类型,或其它因素不同而有所变化。
上表列出了分配的内存的大小,然后是malloc()函数被调用的次数。两块8字节代表分配了两个GSList对象。然后列出了多少块内存被free()
释放,调用realloc()分配了多少次内存,调用realloc()释放了多少内存。最后一列列出了没有被释放的内存的字节数。既然只有GSList
对象被释放了,所以有8个字节的内存泄漏。
因为在应用程序中没有任何调用失败,所以此表只是阐述了成功操作的结果。如果在分配内存或重新分配内存时有失败的情况,那将会是另一个表格来描述这些操作。
阅读(1017) | 评论(0) | 转发(0) |