1LIBCDT(3)                  Library Functions Manual                  LIBCDT(3)
2
3
4

NAME

6       Cdt - container data types
7

SYNOPSIS

9       #include <cdt.h>
10
11   DICTIONARY TYPES
12       Dt_t;
13       Dtdisc_t;
14       Dtmethod_t;
15       Dtlink_t;
16       Dtstat_t;
17
18   DICTIONARY CONTROL
19       Dt_t*       dtopen(const Dtdisc_t* disc, const Dtmethod_t* meth);
20       int         dtclose(Dt_t* dt);
21       void        dtclear(dt);
22       Dtmethod_t* dtmethod(Dt_t* dt, const Dtmethod_t* meth);
23       Dtdisc_t*   dtdisc(Dt_t* dt, const Dtdisc_t* disc, int type);
24       Dt_t*       dtview(Dt_t* dt, Dt_t* view);
25
26   STORAGE METHODS
27       Dtmethod_t* Dtset;
28       Dtmethod_t* Dtbag;
29       Dtmethod_t* Dtoset;
30       Dtmethod_t* Dtobag;
31       Dtmethod_t* Dtlist;
32       Dtmethod_t* Dtstack;
33       Dtmethod_t* Dtqueue;
34       Dtmethod_t* Dtdeque;
35
36   DISCIPLINE
37       #define DTOFFSET(struct_s,member)
38       #define DTDISC(disc,key,size,link,makef,freef,comparf,hashf,memoryf,eventf)
39       typedef void*      (*Dtmake_f)(Dt_t*, void*, Dtdisc_t*);
40       typedef void         (*Dtfree_f)(Dt_t*, void*, Dtdisc_t*);
41       typedef int          (*Dtcompar_f)(Dt_t*, void*, void*, Dtdisc_t*);
42       typedef unsigned int (*Dthash_f)(Dt_t*, void*, Dtdisc_t*);
43       typedef void*      (*Dtmemory_f)(Dt_t*, void*, size_t, Dtdisc_t*);
44       typedef int          (*Dtevent_f)(Dt_t*, int, void*, Dtdisc_t*);
45
46   OBJECT OPERATIONS
47       void*   dtinsert(Dt_t* dt, void* obj);
48       void*   dtappend(Dt_t* dt, void* obj);
49       void*   dtdelete(Dt_t* dt, void* obj);
50       void*   dtattach(Dt_t* dt, void* obj);
51       void*   dtdetach(Dt_t* dt, void* obj);
52       void*   dtsearch(Dt_t* dt, void* obj);
53       void*   dtmatch(Dt_t* dt, void* key);
54       void*   dtfirst(Dt_t* dt);
55       void*   dtnext(Dt_t* dt, void* obj);
56       void*   dtlast(Dt_t* dt);
57       void*   dtprev(Dt_t* dt, void* obj);
58       void*   dtfinger(Dt_t* dt);
59       void*   dtrenew(Dt_t* dt, void* obj);
60       int       dtwalk(Dt_t* dt, int (*userf)(Dt_t*, void*, void*), void*);
61       Dtlink_t* dtflatten(Dt_t* dt);
62       Dtlink_t* dtlink(Dt_t*, Dtlink_t* link);
63       void*   dtobj(Dt_t* dt, Dtlink_t* link);
64       Dtlink_t* dtextract(Dt_t* dt);
65       int       dtrestore(Dt_t* dt, Dtlink_t* link);
66
67       #define   DTTREESEARCH(Dt_t* dt, void* obj, action)
68       #define   DTTREEMATCH(Dt_t* dt, void* key, action)
69
70   DICTIONARY STATUS
71       Dt_t*     dtvnext(Dt_t* dt);
72       int       dtvcount(Dt_t* dt);
73       Dt_t*     dtvhere(Dt_t* dt);
74       int       dtsize(Dt_t* dt);
75       int       dtstat(Dt_t* dt, Dtstat_t*, int all);
76
77   HASH FUNCTIONS
78       unsigned int dtstrhash(unsigned int h, char* str, int n);
79       unsigned int dtcharhash(unsigned int h, unsigned char c);
80

DESCRIPTION

82       Cdt  manages run-time dictionaries using standard container data types:
83       unordered set/multiset, ordered set/multiset, list, stack, and queue.
84
85   DICTIONARY TYPES
86     Dt_t
87       This is the type of a dictionary handle.
88
89     Dtdisc_t
90       This defines the type of a discipline structure which describes  object
91       lay-out and manipulation functions.
92
93     Dtmethod_t
94       This defines the type of a container method.
95
96     Dtlink_t
97       This is the type of a dictionary object holder (see dtdisc().)
98
99     Dtstat_t
100       This  is  the  type of a structure to return dictionary statistics (see
101       dtstat().)
102
103   DICTIONARY CONTROL
104     Dt_t* dtopen(const Dtdisc_t* disc, const Dtmethod_t* meth)
105       This creates a new dictionary.   disc  is  a  discipline  structure  to
106       describe   object   format.   meth  specifies  a  manipulation  method.
107       dtopen() returns the new dictionary or NULL on  error.   See  also  the
108       events DT_OPEN and DT_ENDOPEN below.
109
110     int dtclose(Dt_t* dt)
111       This  deletes  dt  and its objects.  Note that dtclose() fails if dt is
112       being viewed by some  other  dictionaries  (see  dtview()).   dtclose()
113       returns 0 on success and -1 on error.  See also the events DT_CLOSE and
114       DT_ENDCLOSE below.
115
116     void dtclear(Dt_t* dt)
117       This deletes all objects in dt without closing dt.
118
119     Dtmethod_t dtmethod(Dt_t* dt, const Dtmethod_t* meth)
120       If meth is NULL, dtmethod() returns the current method.  Otherwise,  it
121       changes  the  storage  method  of dt to meth.  Object order remains the
122       same during a method switch among Dtlist, Dtstack, Dtqueue and Dtdeque.
123       Switching  to  and from Dtset/Dtbag and Dtoset/Dtobag may cause objects
124       to be rehashed, reordered, or removed as the case requires.  dtmethod()
125       returns the previous method or NULL on error.
126
127     Dtdisc_t* dtdisc(Dt_t* dt, const Dtdisc_t* disc, int type)
128       If  disc  is NULL, dtdisc() returns the current discipline.  Otherwise,
129       it changes the discipline of dt to  disc.   Objects  may  be  rehashed,
130       reordered,  or removed as appropriate.  type can be any bit combination
131       of DT_SAMECMP and DT_SAMEHASH.  DT_SAMECMP means that objects will com‐
132       pare  exactly the same as before thus obviating the need for reordering
133       or removing new duplicates.  DT_SAMEHASH  means  that  hash  values  of
134       objects  remain  the  same thus obviating the need to rehash.  dtdisc()
135       returns the previous discipline on success and NULL on error.
136
137     Dt_t* dtview(Dt_t* dt, Dt_t* view)
138       A viewpath allows a search or walk starting from a dictionary  to  con‐
139       tinue  to  another.  dtview() first terminates any current view from dt
140       to another dictionary.  Then, if view is NULL, dtview returns the  ter‐
141       minated  view  dictionary.   If view is not NULL, a viewpath from dt to
142       view is established.  dtview() returns dt on success and NULL on error.
143
144       It is an error to have dictionaries on a viewpath with different  stor‐
145       age  methods.   In  addition, dictionaries on the same view path should
146       treat objects in a consistent manner  with  respect  to  comparison  or
147       hashing.  If not, undefined behaviors may result.
148
149   STORAGE METHODS
150       Storage  methods  are  of type Dtmethod_t*.  Cdt supports the following
151       methods:
152
153     Dtoset
154     Dtobag
155       Objects are ordered by comparisons.  Dtoset keeps unique objects.  Dto‐
156       bag allows repeatable objects.
157
158     Dtset
159     Dtbag
160       Objects  are  unordered.   Dtset  keeps  unique  objects.  Dtbag allows
161       repeatable objects and always keeps them together (note the  effect  on
162       dictionary  walking.)   These methods use a hash table with chaining to
163       manage the objects.  See also the event DT_HASHSIZE  below  on  how  to
164       manage hash table resizing when objects are inserted.
165
166     Dtlist
167       Objects  are  kept in a list.  The call dtinsert() inserts a new object
168       in front of the current object (see dtfinger()) if it is defined or  at
169       list  front if no current object is defined.  Similarly, the call dtap‐
170       pend() appends a new object after the current object  (see  dtfinger())
171       if it is defined or at list end if no current object is defined.
172
173     Dtdeque
174       Objects  are  kept  in  a  deque. This is similar to Dtlist except that
175       objects are always inserted at the front and appended at  the  tail  of
176       the list.
177
178     Dtstack
179       Objects  are  kept  in  a  stack,  i.e., in reverse order of insertion.
180       Thus, the last object inserted is at stack top and will be the first to
181       be deleted.
182
183     Dtqueue
184       Objects  are  kept  in a queue, i.e., in order of insertion.  Thus, the
185       first object inserted is at queue head and will  be  the  first  to  be
186       deleted.
187
188   DISCIPLINE
189       Object  format  and  associated management functions are defined in the
190       type Dtdisc_t:
191           typedef struct
192           { int        key, size;
193             int        link;
194             Dtmake_f   makef;
195             Dtfree_f   freef;
196             Dtcompar_f comparf;
197             Dthash_f   hashf;
198             Dtmemory_f memoryf;
199             Dtevent_f  eventf;
200           } Dtdisc_t;
201
202     int key, size
203       Each object obj is identified by a key used for  object  comparison  or
204       hashing.   key  should  be non-negative and defines an offset into obj.
205       If size is negative, the key is a null-terminated string with  starting
206       address *(void**)((char*)obj+key).  If size is zero, the key is a null-
207       terminated  string  with  starting   address   (void*)((char*)obj+key).
208       Finally,  if  size  is positive, the key is a byte array of length size
209       starting at (void*)((char*)obj+key).
210
211     int link
212       Let obj be an object to be inserted into dt  as  discussed  below.   If
213       link is negative, an internally allocated object holder is used to hold
214       obj. Otherwise, obj should have  a  Dtlink_t  structure  embedded  link
215       bytes into it, i.e., at address (Dtlink_t*)((char*)obj+link).
216
217     void* (*makef)(Dt_t* dt, void* obj, Dtdisc_t* disc)
218       If  makef  is  not NULL, dtinsert(dt,obj) or dtappend() will call it to
219       make a copy of obj suitable for insertion into dt.  If makef  is  NULL,
220       obj itself will be inserted into dt.
221
222     void (*freef)(Dt_t* dt, void* obj, Dtdisc_t* disc)
223       If not NULL, freef is used to destroy data associated with obj.
224
225   int (*comparf)(Dt_t* dt, void* key1, void* key2, Dtdisc_t* disc)
226       If  not  NULL,  comparf  is used to compare two keys.  Its return value
227       should be <0, =0, or >0 to indicate whether key1 is smaller, equal  to,
228       or  larger  than  key2.   All  three  values are significant for method
229       Dtoset and Dtobag.  For other methods, a zero value indicates  equality
230       and a non-zero value indicates inequality.  If (*comparf)() is NULL, an
231       internal function is used  to  compare  the  keys  as  defined  by  the
232       Dtdisc_t.size field.
233
234     unsigned int (*hashf)(Dt_t* dt, void* key, Dtdisc_t* disc)
235       If  not  NULL,  hashf  is used to compute the hash value of key.  It is
236       required that keys compared equal will also have same hash values.   If
237       hashf  is NULL, an internal function is used to hash the key as defined
238       by the Dtdisc_t.size field.
239
240     void* (*memoryf)(Dt_t* dt, void* addr, size_t size, Dtdisc_t* disc)
241       If not NULL, memoryf is used to allocate and free memory.  When addr is
242       NULL,  a memory segment of size size is requested.  If addr is not NULL
243       and size is zero, addr is to be freed.  If addr is not NULL and size is
244       positive, addr is to be resized to the given size.  If memoryf is NULL,
245       malloc(3) is used.
246
247     int (*eventf)(Dt_t* dt, int type, void* data, Dtdisc_t* disc)
248       If not NULL, eventf announces various events.  Each event may have par‐
249       ticular  handling  of  the  return  values from eventf.  But a negative
250       return value typically means failure.  Following are the events:
251
252       DT_OPEN:
253              dt is being opened.  If eventf  returns  negative,  the  opening
254              process  terminates  with  failure.  If eventf returns zero, the
255              opening process proceeds in a default manner.  A positive return
256              value  indicates  special  treatment  of  memory as follows.  If
257              *(void**)data is set to point to some  memory  segment  as  dis‐
258              cussed  in  memoryf, that segment of memory is used to start the
259              dictionary. If *(void**)data is NULL, all memory including  that
260              of the dictionary handle itself will be allocated via memoryf.
261
262       DT_ENDOPEN:
263              This  event  announces  that  dtopen() has successfully opened a
264              dictionary and is about to return. The data argument  of  eventf
265              should be the new dictionary handle itself.
266
267       DT_CLOSE:
268              dt  is about to be closed. If eventf returns negative, the clos‐
269              ing process stops immediately and dtclose() returns -1.  Objects
270              in  the dictionary are deleted only if eventf returns zero.  The
271              dictionary handle itself is processed as  follows.   If  it  was
272              allocated  via  malloc(), it will be freed.  If it was allocated
273              via memoryf (see dtopen()) and eventf returns 0, a call to memo‐
274              ryf  will  be  issued to attempt freeing the handle.  Otherwise,
275              nothing will be done to its memory.
276
277              As should be clear from their description,  the  events  DT_OPEN
278              and  DT_CLOSE are designed to be used along with memoryf to man‐
279              age the allocation and deallocation  of  dictionary  and  object
280              memory  across dictionaries. In fact, they can be used to manage
281              dictionaries based on shared and/or persistent memory.
282
283       DT_ENDCLOSE:
284              This event announces that dtclose() has  successfully  closed  a
285              dictionary and is about to return.
286
287       DT_DISC:
288              The  discipline  of  dt  is  being changed to a new one given in
289              (Dtdisc_t*)data.
290
291       DT_METH:
292              The method of dt  is  being  changed  to  a  new  one  given  in
293              (Dtmethod_t*)data.
294
295       DT_HASHSIZE:
296              The  hash table (for Dtset and Dtbag) is being resized.  In this
297              case, *(int*)data has the current size of the table.  The appli‐
298              cation  can set the new table size by first changing *(int*)data
299              to the desired size, then return a positive value.  The applica‐
300              tion can also fix the table size at the current value forever by
301              setting *(int*)data to a negative value,  then  again  return  a
302              positive  value. A non-positive return value from the event han‐
303              dling function means that Cdt will be responsible  for  choosing
304              the hash table size.
305
306   #define DTOFFSET(struct_s,member)
307       This  macro  function  computes  the offset of member from the start of
308       structure struct_s. It is useful for getting the offset of  a  Dtlink_t
309       embedded inside an object.
310
311   #define DTDISC(disc,key,size,link,makef,freef,comparf,hashf,memoryf,eventf)
312       This  macro function initializes the discipline pointed to by disc with
313       the given values.
314
315   OBJECT OPERATIONS
316     void* dtinsert(Dt_t* dt, void* obj)
317     void* dtappend(Dt_t* dt, void* obj)
318       These functions add an object prototyped by obj  into  dt.   dtinsert()
319       and  dtappend()  perform  the  same function for all methods except for
320       Dtlist. See Dtlist for details.  If there is an existing object  in  dt
321       matching  obj and the storage method is Dtset or Dtoset, dtinsert() and
322       dtappend() will simply return the matching object.   Otherwise,  a  new
323       object  is inserted according to the method in use.  See Dtdisc_t.makef
324       for object construction.  The new object or a matching object as  noted
325       will be returned on success while NULL is returned on error.
326
327     void* dtdelete(Dt_t* dt, void* obj)
328       If  obj  is NULL, methods Dtstack and Dtqueue delete respectively stack
329       top or queue head while other methods do nothing.  If obj is not  NULL,
330       there  are two cases.  If the method in use is not Dtbag or Dtobag, the
331       first object matching obj is deleted.  On the other hand, if the method
332       in  use  is  Dtbag or Dtobag, the library check to see if obj is in the
333       dictionary and delete it.  If obj is not in the dictionary, some object
334       matching  it  will  be deleted.  See Dtdisc_t.freef for object destruc‐
335       tion.  dtdelete() returns the deleted object (even if  it  was  deallo‐
336       cated) or NULL on error.
337
338     void* dtattach(Dt_t* dt, void* obj)
339       This  function is similar to dtinsert() but obj itself will be inserted
340       into dt even if a discipline function makef is defined.
341
342     void* dtdetach(Dt_t* dt, void* obj)
343       This function is similar to dtdelete() but the  object  to  be  deleted
344       from dt will not be freed (via the discipline freef function).
345
346     void* dtsearch(Dt_t* dt, void* obj)
347     void* dtmatch(Dt_t* dt, void* key)
348       These  functions  find  an object matching obj or key either from dt or
349       from some dictionary accessible from dt via a viewpath (see  dtview().)
350       dtsearch() and dtmatch() return the matching object or NULL on failure.
351
352     void* dtfirst(Dt_t* dt)
353     void* dtnext(Dt_t* dt, void* obj)
354       dtfirst()  returns the first object in dt.  dtnext() returns the object
355       following obj.  Objects are ordered based on the storage method in use.
356       For  Dtoset and Dtobag, objects are ordered by object comparisons.  For
357       Dtstack, objects are  ordered  in  reverse  order  of  insertion.   For
358       Dtqueue,  objects  are  ordered  in  order  of  insertion.  For Dtlist,
359       objects are ordered by list position.  For Dtset and Dtbag, objects are
360       ordered  by  some internal order (more below).  Thus, objects in a dic‐
361       tionary or a viewpath can be walked using a for(;;) loop as below.
362           for(obj = dtfirst(dt); obj; obj = dtnext(dt,obj))
363       When a dictionary uses Dtset or Dtbag, the object order  is  determined
364       upon  a  call to dtfirst()/dtlast().  This order is frozen until a call
365       dtnext()/dtprev() returns NULL or when these same functions are  called
366       with a NULL object argument.  It is important that a dtfirst()/dtlast()
367       call be balanced by a  dtnext()/dtprev()  call  as  described.   Nested
368       loops  will require multiple balancing, once per loop.  If loop balanc‐
369       ing is not done carefully, either performance is degraded or unexpected
370       behaviors may result.
371
372     void* dtlast(Dt_t* dt)
373     void* dtprev(Dt_t* dt, void* obj)
374       dtlast()  and  dtprev()  are  like  dtfirst()  and dtnext() but work in
375       reverse order.  Note that dictionaries on a viewpath are  still  walked
376       in order but objects in each dictionary are walked in reverse order.
377
378     void* dtfinger(Dt_t* dt)
379       This  function  returns  the current object of dt, if any.  The current
380       object is defined  after  a  successful  call  to  one  of  dtsearch(),
381       dtmatch(),  dtinsert(), dtfirst(), dtnext(), dtlast(), or dtprev().  As
382       a side effect of this implementation of Cdt, when a dictionary is based
383       on  Dtoset  and Dtobag, the current object is always defined and is the
384       root of the tree.
385
386     void* dtrenew(Dt_t* dt, void* obj)
387       This function repositions and perhaps rehashes an object obj after  its
388       key  has  been  changed.   dtrenew()  only  works if obj is the current
389       object (see dtfinger()).
390
391     dtwalk(Dt_t* dt, int (*userf)(Dt_t*, void*, void*), void* data)
392       This function calls (*userf)(walk,obj,data) on each object  in  dt  and
393       other dictionaries viewable from it.  walk is the dictionary containing
394       obj.  If userf() returns a <0 value, dtwalk()  terminates  and  returns
395       the same value.  dtwalk() returns 0 on completion.
396
397     Dtlink_t* dtflatten(Dt_t* dt)
398     Dtlink_t* dtlink(Dt_t* dt, Dtlink_t* link)
399     void* dtobj(Dt_t* dt, Dtlink_t* link)
400       Using  dtfirst()/dtnext() or dtlast()/dtprev() to walk a single dictio‐
401       nary can incur significant cost due to function calls.   For  efficient
402       walking  of  a single directory (i.e., no viewpathing), dtflatten() and
403       dtlink() can be used.  Objects in dt are made into a  linked  list  and
404       walked as follows:
405           for(link = dtflatten(dt); link; link = dtlink(dt,link) )
406
407       Note that dtflatten() returns a list of type Dtlink_t*, not void*. That
408       is, it returns a dictionary holder pointer, not a user  object  pointer
409       (although both are the same if the discipline field link is zero.)  The
410       macro function dtlink() returns the dictionary holder object  following
411       link.   The macro function dtobj(dt,link) returns the user object asso‐
412       ciated with link, Beware that the flattened object list is  unflattened
413       on any dictionary operations other than dtlink().
414
415     Dtlink_t* dtextract(Dt_t* dt)
416     int dtrestore(Dt_t* dt, Dtlink_t* link)
417       dtextract()  extracts  all  objects  from dt and makes it appear empty.
418       dtrestore() repopulates dt with objects previously obtained  via  dtex‐
419       tract().   dtrestore()  will  fail if dt is not empty.  These functions
420       can be used to share a same dt handle among many sets of objects.  They
421       are useful to reduce dictionary overhead in an application that creates
422       many concurrent dictionaries.  It is important that the same discipline
423       and  method  are  in use at both extraction and restoration. Otherwise,
424       undefined behaviors may result.
425
426     #define   DTTREESEARCH(Dt_t* dt, void* obj, action)
427     #define   DTTREEMATCH(Dt_t* dt, void* key, action)
428       These macro functions are analogues of  dtsearch()  and  dtmatch()  but
429       they  can  only  be used on a dictionary based on a binary search tree,
430       i.e., Dtoset or Dtobag.
431
432       obj or key:
433              These are used to find a matching object. If there is no  match,
434              the result is NULL.
435
436       action:
437              The  matching  object o (which may be NULL) will be processed as
438              follow:
439
440                  action (o);
441
442              Since action is used verbatim, it can be  any  C  code  fragment
443              combinable with (o) to form a syntactically correct C statement.
444              For example, suppose that the matching object is an integer, the
445              below code accumulates the integer value in a variable total:
446
447                  DTTREEMATCH(dt, key, total += (int));
448
449
450   DICTIONARY INFORMATION
451     Dt_t* dtvnext(Dt_t* dt)
452       This returns the dictionary that dt is viewing, if any.
453
454     int dtvcount(Dt_t* dt)
455       This returns the number of dictionaries that view dt.
456
457     Dt_t* dtvhere(Dt_t* dt)
458       This  returns  the  dictionary  v  viewable from dt where an object was
459       found from the most recent search or walk operation.
460
461     int dtsize(Dt_t* dt)
462       This function returns the number of objects stored in dt.
463
464     int dtstat(Dt_t *dt, Dtstat_t* st, int all)
465       This function reports dictionary statistics.  If all is  non-zero,  all
466       fields  of  st  are  filled.   Otherwise,  only the dt_type and dt_size
467       fields are filled.  It returns 0 on success and -1 on error.
468
469       Dtstat_t contains the below fields:
470
471       int dt_type:
472              This is  one  of  DT_SET,  DT_BAG,  DT_OSET,  DT_OBAG,  DT_LIST,
473              DT_STACK, and DT_QUEUE.
474
475       int dt_size:
476              This contains the number of objects in the dictionary.
477
478       int dt_n:
479              For  Dtset  and Dtbag, this is the number of non-empty chains in
480              the hash table.  For Dtoset and  Dtobag,  this  is  the  deepest
481              level  in the tree (counting from zero.)  Each level in the tree
482              contains all nodes of equal distance from the root  node.   dt_n
483              and the below two fields are undefined for other methods.
484
485       int dt_max:
486              For  Dtbag  and Dtset, this is the size of a largest chain.  For
487              Dtoset and Dtobag, this is the size of a largest level.
488
489       int* dt_count:
490              For Dtset and Dtbag, this is the list of counts  for  chains  of
491              particular  sizes.   For  example,  dt_count[1] is the number of
492              chains of size 1.  For Dtoset and Dtobag, this is  the  list  of
493              sizes  of  the  levels.  For example, dt_count[1] is the size of
494              level 1.
495
496   HASH FUNCTIONS
497     unsigned int dtcharhash(unsigned int h, char c)
498     unsigned int dtstrhash(unsigned int h, char* str, int n)
499       These  functions  compute  hash   values   from   bytes   or   strings.
500       dtcharhash()  computes  a  new hash value from byte c and seed value h.
501       dtstrhash() computes a new hash value from string str and seed value h.
502       If  n is positive, str is a byte array of length n; otherwise, str is a
503       null-terminated string.
504

IMPLEMENTATION NOTES

506       Dtset and Dtbag are based on hash tables with  move-to-front  collision
507       chains.   Dtoset and Dtobag are based on top-down splay trees.  Dtlist,
508       Dtstack and Dtqueue are based on doubly linked list.
509

AUTHOR

511       Kiem-Phong Vo, kpv@research.att.com
512
513
514
515                                                                     LIBCDT(3)
Impressum