下载
第二部分 Linux编程常用C语言
函数库及构件库
第3章 glib库简介
g l i b库是 L i n u x平台下最常用的 C语言函数库,它具有很好的可移植性和实用性。 g l i b是
G t k +库和G n o m e的基础。g l i b可以在多个平台下使用,比如 L i n u x、U n i x、Wi n d o w s等。g l i b为
许多标准的、常用的 C语言结构提供了相应的替代物。如果有什么东西本书没有介绍到,请参
考g l i b的头文件:g l i b . h。g l i b . h中的头文件很容易理解,很多函数从字面上都能猜出它的用处
和用法。如果有兴趣, g l i b的源代码也是非常好的学习材料。
g l i b的各种实用程序具有一致的接口。它的编码风格是半面向对象,标识符加了一个前缀
“g”,这也是一种通行的命名约定。
使用g l i b库的程序都应该包含 g l i b的头文件 g l i b . h。如果程序已经包含了 g t k . h或g n o m e . h,
则不需要再包含g l i b . h。
3.1 类型定义
g l i b的类型定义不是使用 C的标准类型,它自己有一套类型系统。它们比常用的 C语言的
类型更丰富,也更安全可靠。引进这套系统是为了多种原因。例如, g i n t 3 2能保证是3 2位的整
数,一些不是标准 C的类型也能保证。有一些仅仅是为了输入方便,比如 guint 比u n s i g n e d更
容易输入。还有一些仅仅是为了保持一致的命名规则,比如, g c h a r和c h a r是完全一样的。
以下是g l i b基本类型定义:
整数类型:g i n t 8、g u i n t 8、g i n t 1 6、g u i n t 1 6、g i n t 3 2、g u i n t 3 2、g i n t 6 4、g u i n t 6 4。其中
g i n t 8是8位的整数, g u i n t 8是8位的无符号整数,其他依此类推。这些整数类型能够保证大小。
不是所有的平台都提供 6 4位整型,如果一个平台有这些, g l i b会定义G _ H AV E _ G I N T 6 4。
整数类型g s h o r t、g l o n g、g i n t和s h o r t、l o n g、i n t完全等价。
布尔类型 g b o o l e a n:它可使代码更易读,因为普通 C没有布尔类型。 G b o o l e a n可以取两
个值:T R U E和FA L S E。实际上FA L S E定义为0,而T R U E定义为非零值。
字符型g c h a r和c h a r完全一样,只是为了保持一致的命名。
浮点类型g f l o a t、g d o u b l e和f l o a t、d o u b l e完全等价。
指针g p o i n t e r对应于标准 C的void *,但是比void *更方便。
指针g c o n s t p o i n t e r对应于标准 C的const void *(注意,将 const void *定义为const gpointer
是行不通的)。
3.2 glib的宏
3.2.1 常用宏
g l i b定义了一些在 C程序中常见的宏,详见下面的列表。 T R U E / FA L S E / N U L L就是
50使用第二部分 Linux编程常用C语言函数库及构件库
下载
1 / 0 / ( ( v o i d * ) 0 )。M I N ( ) / M A X ( )返回更小或更大的参数。 A B S ( )返回绝对值。 CLAMP(x, low,
h i g h )若X在[ l o w, high]范围内,则等于 X;如果X小于l o w,则返回l o w;如果X大于h i g h,则返
回h i g h。
一些常用的宏列表
# i n c l u d e < g l i b . h >
T R U E
F A L S E
N U L L
M A X ( a , b )
MIN(a, b)
A B S ( x )
C L A M P ( x , l o w , h i g h )
有些宏只有 g l i b拥有,例如在后面要介绍的 g p o i n t e r-to-gint 和 g p o i n t e r- t o - g u i n t。
大多数 g l i b的数据结构都设计成存储一个 g p o i n t e r。如果想存储指针来动态分配对象,可
以这样做。然而,有时还是想存储一列整数而不想动态地分配它们。虽然 C标准不能严格保证,
但是在多数 g l i b支持的平台上,在 g p o i n t e r变量中存储g i n t或g u i n t仍是可能的。在某些情况下,
需要使用中间类型转换。
下面是示例:
g i n t m y _ i n t ;
g p o i n t e r m y _ p o i n t e r ;
m y _ i n t = 5 ;
m y _ p o i n t e r = G I N T _ T O _ P O I N T E R ( m y _ i n t ) ;
p r i n t f ( " W e a r e s t o r i n g % d \ n " , G P O I N T E R _ T O _ I N T ( m y _ p o i n t e r ) ) ;
这些宏允许在一个指针中存储一个整数,但在一个整数中存储一个指针是不行的。如果
要实现的话,必须在一个长整型中存储指针。
宏列表:在指针中存储整数的宏
# i n c l u d e < g l i b . h >
G I N T _ T O _ P O I N T E R ( p )
G P O I N T E R _ T O _ I N T ( p )
G U I N T _ T O _ P O I N T E R ( p )
G P O I N T E R _ T O _ U I N T ( p )
3.2.2 调试宏
g l i b提供了一整套宏,在你的代码中使用它们可以强制执行不变式和前置条件。这些宏很
稳 定 , 也 容 易 使 用 , 因 而 G t k +大 量 使 用 它 们 。 定 义 了 G_DISABLE_CHECKS 或
G _ D I S A B L E _ A S S E RT之后,编译时它们就会消失,所以在软件代码中使用它们不会有性能
损失。大量使用它们能够更快速地发现程序的错误。发现错误后,为确保错误不会在以后的
版本中出现,可以添加断言和检查。特别是当编写的代码被其他程序员当作黑盒子使用时,
这种检查很有用。用户会立刻知道在调用你的代码时发生了什么错误,而不是猜测你的代码
中有什么缺陷。
当然,应该确保代码不是依赖于一些只用于调试的语句才能正常工作。如果一些语句在
生成代码时要取消,这些语句不应该有任何副作用。
宏列表:前提条件检查
下载
第3章 g l i b库简介使用51
# i n c l u d e < g l i b . h >
g _ r e t u r n _ i f _ f a i l ( c o n d i t i o n )
g _ r e t u r n _ v a l _ i f _ f a i l ( c o n d i t i o n , r e t v a l )
这个宏列表列出了 g l i b的预条件检查宏。对 g _ r e t u r n _ i f _ f a i l ( ),如果条件为假,则打印一
个警告信息并且从当前函数立刻返回。 g _ r e t u r n _ v a l _ i f _ f a i l ( )与前一个宏类似,但是允许返回
一个值。毫无疑问,这些宏很有用—如果大量使用它们,特别是结合 G t k +的实时类型检查,
会节省大量的查找指针和类型错误的时间。
使用这些函数很简单,下面的例子是 g l i b中哈希表的实现:
v o i d
g _ h a s h _ t a b l e _ f o r e a c h ( G H a s h T a b l e * h a s h _ t a b l e ,
G H F u n c f u n c ,
g p o i n t e r u s e r _ d a t a )
{
G H a s h N o d e * n o d e ;
gint i;
g _ r e t u r n _ i f _ f a i l ( h a s h _ t a b l e ! = N U L L ) ;
g _ r e t u r n _ i f _ f a i l ( f u n c ! = N U L L ) ;
f o r ( i = 0 ; i < h a s h _ t a b l e - > s i z e ; i + + )
f o r ( n o d e = h a s h _ t a b l e - > n o d e s [ i ] ; n o d e ; n o d e = n o d e - > n e x t )
( * f u n c ) ( n o d e - > k e y , n o d e - > v a l u e , u s e r _ d a t a ) ;
}
如果不检查,这个程序把 N U L L作为参数时将导致一个奇怪的错误。库函数的使用者可能
要通过调试器找出错误出现在哪里,甚至要到 g l i b的源代码中查找代码的错误是什么。使用这
种前提条件检查,他们将得到一个很不错的错误信息,告之不允许使用 N U L L参数。
宏列表:断言
# i n c l u d e < g l i b . h >
g _ a s s e r t ( c o n d i t i o n )
g _ a s s e r t _ n o t _ r e a c h e d ( )
g l i b也有更传统的断言函数。g _ a s s e r t ( )基本上与a s s e r t ( )一样,但是对G _ D I S A B L E _ A S S E RT
响应(如果定义了 G _ D I S A B L E _ A S S E RT,则这些语句在编译时不编译进去),以及在所有平台
上行为都是一致的。还有一个g _ a s s e r t _ n o t _ r e a c h e d ( ),如果执行到这个语句,它会调用a b o r t ( )退
出程序并且(如果环境支持)转储一个可用于调试的 c o r e文件。
应该断言用来检查函数或库内部的一致性。 g _ r e t u r n _ i f _ f a i l ( )确保传递到程序模块的公用
接口的值是合法的。也就是说,如果断言失败,将返回一条信息,通常应该在包含断言的模
块中查找错误;如果 g _ r e t u r n _ i f _ f a i l ( )检查失败,通常要在调用这个模块的代码中查找错误。
这也是断言与前提条件检查的区别。
下面g l i b日历计算模块的代码说明了这种差别:
G D a t e *
g _ d a t e _ n e w _ d m y ( G D a t e D a y d a y , G D a t e M o n t h m , G D a t e Y e a r y )
{
GDate *d;
g _ r e t u r n _ v a l _ i f _ f a i l ( g _ d a t e _ v a l i d _ d m y ( d a y , m , y ) , N U L L ) ;
d = g _ n e w ( G D a t e , 1 ) ;
52使用第二部分 Linux编程常用C语言函数库及构件库
下载
d - > j u l i a n = F A L S E ;
d - > d m y = T R U E ;
d - > m o n t h = m ;
d - > d a y = d a y ;
d - > y e a r = y ;
g _ a s s e r t ( g _ d a t e _ v a l i d ( d ) ) ;
r e t u r n d ;
}
开始的预条件检查确保用户传递合理的年月日值;结尾的断言确保 g l i b构造一个健全的对
象,输出健全的值。
断言函数g_assert_not_reached() 用来标识“不可能”的情况,通常用来检测不能处理的
所有可能枚举值的 s w i t c h语句:
s w i t c h ( v a l )
{
}
c a s e F O O _ O N E :
b r e a k ;
c a s e F O O _ T W O :
b r e a k ;
d e f a u l t :
/* 无效枚举值 * /
g _ a s s e r t _ n o t _ r e a c h e d ( ) ;
b r e a k ;
所有调试宏使用g l i b的g _ l o g ( )输出警告信息,g _ l o g ( )的警告信息包含发生错误的应用程序
或库函数名字,并且还可以使用一个替代的警告打印例程。例如,可以将所有警告信息发送
到对话框或 l o g文件而不是输出到控制台。
3.3 内存管理
g l i b用自己的g _变体包装了标准的 m a l l o c ( )和f r e e ( ),即g_malloc() 和 g _ f r e e ( )。它们有以
下几个小优点:
• g_malloc()总是返回g p o i n t e r,而不是c h a r *,所以不必转换返回值。
• 如果低层的 m a l l o c ( )失败,g _ m a l l o c ( )将退出程序,所以不必检查返回值是否是 N U L L。
• g_malloc() 对于分配0字节返回N U L L。
• g_free()忽略任何传递给它的 N U L L指针。
除了这些次要的便利, g_malloc() 和 g _ f r e e ( )支持各种内存调试和剖析。如果将 e n a b l e -
m e m - c h e c k选项传递给 g l i b的c o n f i g u r e脚本,在释放同一个指针两次时, g _ f r e e ( )将发出警告。
e n a b l e - m e m - p r o f i l e选项使代码使用统计来维护内存。调用 g _ m e m _ p r o f i l e ( )时,信息会输出到
控制台上。最后,还可以定义 U S E _ D M A L L O C,G L I B内存封装函数会使用 m a l l o c ( )。调试宏
在某些平台上在d m a l l o c . h中定义。
函数列表: g l i b内存分配
第3章 g l i b库简介使用53
下载
# i n c l u d e < g l i b . h >
g p o i n t e r g _ m a l l o c ( g u l o n g s i z e )
v o i d g _ f r e e ( g p o i n t e r m e m )
g p o i n t e r g _ r e a l l o c ( g p o i n t e r m e m ,
g u l o n g s i z e )
g p o i n t e r g _ m e m d u p ( g c o n s t p o i n t e r m e m ,
g u i n t b y t e s i z e )
用g _ f r e e ( )和g_malloc(), malloc()和f r e e ( ),以及(如果正在使用C++)new 和 d e l e t e匹配是很
重要的,否则,由于这些内存分配函数使用不同内存池 ( n e w / d e l e t e调用构造函数和解构函数 ),
不匹配将会发生很糟糕的事。
另外,g _ r e a l l o c ( )和r e a l l o c ( )是等价的。还有一个很方便的函数 g _ m a l l o c 0 ( ),它将分配的
内存每一位都设置为 0;另一个函数 g _ m e m d u p ( )返回一个从 m e m开始的字节数为 b y t e s i z e的拷
贝。为了与 g _ m a l l o c ( )一致, g _ r e a l l o c ( )和 g _ m a l l o c 0 ( ) 都可以分配 0 字节内存。不过,
g _ m e m d u p ( )不能这样做。 g _ m a l l o c 0 ( )在分配的原始内存中填充未设置的位,而不是设置为数
值0。偶尔会有人期望得到初始化为 0 . 0的浮点数组,但这样是做不到的。
最后,还有一些指定类型内存分配的宏,见下面的宏列表。这些宏中的每一个 t y p e参数都
是数据类型名,c o u n t参数是指分配字节数。这些宏能节省大量的输入和操纵数据类型的时间,
还可以减少错误。它们会自动转换为目标指针类型,所以试图将分配的内存赋给错误的指针
类型,应该触发一个编译器警告。
宏列表:内存分配宏
# i n c l u d e < g l i b . h >
g _ n e w ( t y p e , c o u n t )
g _ n e w 0 ( t y p e , c o u n t )
g _ r e n e w ( t y p e , m e m , c o u n t )
3.4 字符串处理
g l i b提供了很丰富的字符串处理函数,其中有一些是 g l i b独有的,一些用于解决移植问题。
它们都能与 g l i b内存分配例程很好地互操作。
如果需要比 gchar *更好的字符串,g l i b提供了一个 G S t r i n g类型。
函数列表: 字符串操作
# i n c l u d e < g l i b . h >
g i n t g _ s n p r i n t f ( g c h a r * b u f ,
g u l o n g n ,
c o n s t g c h a r * f o r m a t ,
. . . )
g i n t g _ s t r c a s e c m p ( c o n s t g c h a r * s 1 ,
c o n s t g c h a r * s 2 )
g i n t g _ s t r n c a s e c m p ( c o n s t g c h a r * s 1 ,
c o n s t g c h a r * s 2 ,
guint n)
上面的函数列表显示了一些 ANSI C函数的g l i b替代品,这些函数在 ANSI C中是扩展函数,
一般都已经实现,但不可移植。对普通的 C函数库,其中的 s p r i n t f ( )函数有安全漏洞,容易造
成程序崩溃,而相对安全并得到充分实现的 s n p r i n t f ( )函数一般都是软件供应商的扩展版本。
54使用第二部分 Linux编程常用C语言函数库及构件库
下载
在含有s n p r i n t f ( )的平台上, g _ s n p r i n t f ( )封装了一个本地的 s n p r i n t f ( ),并且比原有实现更稳定、
安全。以往的 s n p r i n t f ( )不保证它所填充的缓冲是以 N U L L结束的,但 g _ s n p r i n t f ( )保证了这一
点。
g _ s n p r i n t f函数在 b u f参数中生成一个最大长度为 n的字符串。其中 f o r m a t是格式字符串,
后面的“. . .”是要插入的参数。
g _ s t r c a s e c m p ( )和g _ s t r n c a s e c m p ( )实现两个字符串大小写不敏感的比较,后者可指定需比
较的最大长度。 s t r c a s e c m p ( )在多个平台上都是可用的,但是有的平台并没有,所以建议使用
g l i b的相应函数。
下面的函数列表中的函数在合适的位置上修改字符串:第一个将字符串转换为小写,第
二个将字符串全部转换为大写。 g _ s t r r e v e r s e ( )将字符串颠倒过来。 g _ s t r c h u g ( )和g _ s t r c h o m p ( ),
前者去掉字符串前的空格,后者去掉结尾的空格。宏 g _ s t r s t r i p ( )结合这两个函数,删除字符串
前后的空格。
函数列表: 修改字符串
# i n c l u d e < g l i b . h >
v o i d g _ s t r d o w n ( g c h a r * s t r i n g )
v o i d g _ s t r u p ( g c h a r * s t r i n g )
v o i d g _ s t r r e v e r s e ( g c h a r * s t r i n g )
g c h a r * g _ s t r c h u g ( g c h a r * s t r i n g )
g c h a r * g _ s t r c h o m p ( g c h a r * s t r i n g )
下面的函数列表显示了几个半标准函数的 g l i b封装。 g _ s t r t o d类似于 s t r t o d ( ),它把字符串
n p t r转换为g d o u b l e。* e n d p t r设置为第一个未转换字符,例如,数字后的任何文本。如果转换
失败,* e n d p t r设置为n p t r值。* e n d p t r可以是N U L L,这样函数会忽略这个参数。 g _ s t r e r r o r ( )和
g _ s t r s i g n a l ( )与前面没有“ g _”的函数是等价的,但是它们是可移植的,它们返回错误号或警
号数的字符串描述。
函数列表: 字符串转换
# i n c l u d e < g l i b . h >
g d o u b l e g _ s t r t o d ( c o n s t g c h a r * n p t r ,
g c h a r * * e n d p t r )
g c h a r * g _ s t r e r r o r ( g i n t e r r n u m )
g c h a r * g _ s t r s i g n a l ( g i n t s i g n u m )
下面的函数列表显示了 g l i b中的字符串分配函数。
g _ s t r d u p ( )和g _ s t r n d u p ( )返回一个已分配内存的字符串或字符串前 n个字符的拷贝。为与
g l i b内存分配函数一致,如果向函数中传递一个 N U L L指针,它们返回N U L L。
p r i n t f ( )返回带格式的字符串。 g _ s t r e s c a p e在它的参数前面通过插入另一个“ \”,将后面的
字符转义,返回被转义的字符串。 g _ s t r n f i l l ( )根据l e n g t h参数返回填充f i l l _ c h a r字符的字符串.
g _ s t r d u p _ p r i n t f ( )值得特别注意,它是处理下面代码更简单的方法:
g c h a r * s t r = g _ m a l l o c ( 2 5 6 ) ;
g _ s n p r i n t f ( s t r , 2 5 6 , " % d p r i n t f - s t y l e % s " , 1 , " f o r m a t " ) ;
用下面的代码,不需计算缓冲区的大小:
gchar* str = g_strdup_printf("%d printf-style %", 1, "f o r m a t") ;
函数列表: 分配字符串
第3章 g l i b库简介使用55
下载
# i n c l u d e < g l i b . h >
g c h a r *
g _ s t r d u p ( c o n s t g c h a r * s t r )
g c h a r * g _ s t r n d u p ( c o n s t g c h a r * f o r m a t ,
guint n)
g c h a r * g _ s t r d u p _ p r i n t f ( c o n s t g c h a r * f o r m a t ,
. . . )
g c h a r * g _ s t r d u p _ v p r i n t f ( c o n s t g c h a r * f o r m a t ,
v a _ l i s t a r g s )
g c h a r * g _ s t r e s c a p e ( g c h a r * s t r i n g )
g c h a r * g _ s t r n f i l l ( g u i n t l e n g t h ,
g c h a r f i l l _ c h a r )
g_strconcat() 返回由连接每个参数字符串生成的新字符串,最后一个参数必须是 N U L L,
让g _ s t r c o n c a t ( )知道何时结束。 g _ s t r j o i n ( )与它类似,但是在每个字符串之间插入由 s e p a r a t o r指
定的分隔符。如果 s e p a r a t o r是N U L L,则不会插入分隔符。
下面是g l i b提供的连接字符串的函数。
函数列表: 连接字符串的函数
# i n c l u d e < g l i b . h >
g c h a r * g _ s t r c o n c a t ( c o n s t g c h a r * s t r i n g 1 ,
. . . )
g c h a r * g _ s t r j o i n ( c o n s t g c h a r * s e p a r a t o r ,
. . . )
最后,下面的函数列表总结了几个处理以 N U L L结束的字符串数组的例程。 g _ s t r s p l i t ( )在
每个分隔符处分割字符串,返回一个新分配的字符串数组。 g _ s t r j o i n v ( )用可选的分隔符连接
字符串数组,返回一个已分配好的字符串。 g _ s t r f r e e v ( )释放数组中每个字符串,然后释放数
组本身。
函数列表: 处理以N U L L结尾的字符串向量
# i n c l u d e < g l i b . h >
g c h a r * * g _ s t r s p l i t ( c o n s t g c h a r * s t r i n g ,
c o n s t g c h a r * d e l i m i t e r ,
g i n t m a x _ t o k e n s )
g c h a r * g _ s t r j o i n v ( c o n s t g c h a r * s e p a r a t o r ,
g c h a r * * s t r _ a r r a y )
v o i d g _ s t r f r e e v ( g c h a r * * s t r _ a r r a y )
3.5 数据结构
g l i b实现了许多通用数据结构,如单向链表、双向链表、树和哈希表等。下面的内容介绍
g l i b链表、排序二叉树、 N - A RY 树以及哈希表的实现。
3.5.1 链表
g l i b提供了普通的单向链表和双向链表,分别是 GSList 和 G L i s t。这些是由 g p o i n t e r链表
实现的,可以使用 G I N T _ TO_POINTER 和G P O I N T E R _ TO_INT 宏在链表中保存整数。 G S L i s t
和 G L i s t有一样的 A P I接口,除了有 g_list_previous() 函数外没有g _ s l i s t _ p r e v i o u s ( )函数。本节
讨论G S L i s t的所有函数,这些也适用于双向链表。
56使用第二部分 Linux编程常用C语言函数库及构件库
下载
在 g l i b实现中,空链表只是一个 N U L L指针。因为它是一个长度为 0的链表,所以向链表
函数传递N U L L总是安全的。以下是创建链表、添加一个元素的代码:
G S L i s t * l i s t = N U L L ;
g c h a r * e l e m e n t = g _ s t r d u p ( " a s t r i n g " ) ;
l i s t = g _ s l i s t _ a p p e n d ( l i s t , e l e m e n t ) ;
g l i b的链表明显受 L i s p的影响,因此,空链表是一个特殊的“空”值。 g _ s l i s t _ p r e p e n d ( )操
作很像一个恒定时间的操作:把新元素添加到链表前面的操作所花的时间都是一样的。
注意,必须将链表用链表修改函数返回的值替换,以防链表头发生变化。 G l i b会处理链
表的内存问题,根据需要释放和分配链表链接。
例如,以下的代码删除上面添加的元素并清空链表:
l i s t = g _ s l i s t _ r e m o v e ( l i s t , e l e m e n t ) ;
链表 l i s t 现在是 N U L L 。当然,仍需自己释放元素。为了清除整个链表,可使用
g _ s l i s t _ f r e e ( ),它会快速删除所有的链接。因为 g _ s l i s t _ f r e e ( )函数总是将链表置为 N U L L,它
不会返回值;并且,如果愿意,可以直接为链表赋值。显然 , g_slist_free()只释放链表的单元,
它并不知道怎样操作链表内容。
为了访问链表的元素,可以直接访问 G S L i s t结构:
g c h a r * m y _ d a t a = l i s t - > d a t a ;
为了遍历整个链表,可以如下操作:
G S L i s t * t m p = l i s t ;
w h i l e ( t m p ! = N U L L )
{
}
p r i n t f ( " L i s t d a t a : % p \ n " , t m p - > d a t a ) ;
t m p = g _ s l i s t _ n e x t ( t m p ) ;
下面的列表显示了用于操作 G S L i s t元素的基本函数。对所有这些函数,必须将函数返回
值赋给链表指针,以防链表头发生变化。注意, g l i b不存储指向链表尾的指针,所以前插
(p r e p e n d)操作是一个恒定时间的操作,而追加( a p p e n d)、插入和删除所需时间与链表大小
成正比。
这意味着用 g _ s l i s t _ a p p e n d ( )构造一个链表是一个很糟糕的主意。当需要一个特殊顺序的
列表项时,可以先调用 g _ s l i s t _ p r e p e n d ( )前插数据,然后调用 g _ s l i s t _ r e v e r s e ( )将链表颠倒过来。
如果预计会频繁向链表中追加列表项,也要为最后的元素保留一个指针。下面的代码可以用
来有效地向链表中添加数据:
v o i d
e f f i c i e n t _ a p p e n d ( G S L i s t * * l i s t , G S L i s t * * l i s t _ e n d , g p o i n t e r d a t a )
{
g _ r e t u r n _ i f _ f a i l ( l i s t ! = N U L L ) ;
g _ r e t u r n _ i f _ f a i l ( l i s t _ e n d ! = N U L L ) ;
i f ( * l i s t = = N U L L )
{
}
g _ a s s e r t ( * l i s t _ e n d = = N U L L ) ;
* l i s t = g _ s l i s t _ a p p e n d ( * l i s t , d a t a ) ;
* l i s t _ e n d = * l i s t ;