ガーベージコレクタ

メモリリークの問題に悩まされている人は多いと思います。ガーベージコレクタを使うと、メモリ管理の煩わしさから解放されます。

Boehm GC

#include <gc/gc.h>
void GC_INIT(void);
void *GC_MALLOC(size_t size);
void *GC_MALLOC_ATOMIC(size_t size);
void *GC_REALLOC(void *ptr, size_t size);
size_t GC_get_heap_size(void);

Boehm GCと呼ばれているガーベージコレクタです。Debianならlibgc-dev、MacFinkならgcという名前のパッケージになっています。
基本的にmalloc()、realloc()と使い方は同じですが、GC_MALLOC()は確保した領域を0で初期化します。
GC_MALLOC_ATOMIC()は、確保した領域にポインタを置かない場合(例えば文字列等)に使います。ガーベージコレクタの動作が速くなります。また、確保した領域を0で初期化しません。
これら三つの関数は、実際はマクロとして定義されており、GC_DEBUGが定義されているとデバッグ用の関数に置き換わります。メモリ関係のエラーが出る場合はGC_DEBUGを定義してみましょう(GC_DEBUGはgc.hを組み込む前に定義する必要があります)。
GC_INIT()はガーベージコレクタを初期化し、GC_get_heap_size()は、このライブラリが確保しているメモリの総量を返してくれます。

使い方

こんなひどいコードを書いても使用するメモリの量は一定です。

#include <gc/gc.h>
#include <stdio.h>

int main() {
  char *s;
  int i;

  GC_INIT();
  for (i=0;;i++) {
    s=(char*)GC_MALLOC(100*sizeof(char));
  }
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

Linux

gcc -lgc test.c

Mac(ライブラリをFinkでインストールした場合)

gcc -lgc -L/sw/lib -I/sw/include test.c

DNS

djbdns

#include <djbdns/dns.h>
void dns_random_init(const char seed[128]);
int dns_ip4(stralloc *out, const stralloc *fqdn);
int dns_name4(stralloc *out, const char ip[4]);
int dns_mx(stralloc *out, const stralloc *fqdn);

djbdnsに含まれているライブラリです。Debianならlibdjbdns1-devというパッケージになっています。
このライブラリでは、文字列の受け渡しにstrallocという構造体を使います。strallocの使い方については、strallocの項を御覧下さい。

void dns_random_init(const char seed[128]);

乱数生成機を初期化します。パケット盗聴から守るために必要です。

int dns_ip4(stralloc *out, const stralloc *fqdn);

fqdnに対応するIPアドレスをoutに格納します。各IPアドレスは4バイトのデータになっており、繋がった状態で格納されます。
成功すると0、失敗すると-1を返します。

int dns_name4(stralloc *out, const char ip[4]);

ipで渡されたIPアドレスに対応するホスト名をoutに格納します。
成功すると0、失敗すると-1を返します。

int dns_mx(stralloc *out, const stralloc *fqdn);

fqdnのMXレコードをoutに格納します。格納される形式は次のようになります。

優先度(2byte) + ホスト名 + \0 + 優先度(2byte) + ホスト名 + \0 + ...

成功すると0、失敗すると-1を返します。

使い方

gmail.com」のIPアドレスを得る

#include <djbdns/dns.h>
#include <stdio.h>

void print_ips(unsigned char *ips,unsigned int len);

int main() {
  char seed[128];
  stralloc domain={0},ips={0};

  dns_random_init(seed);
  stralloc_copys(&domain,"gmail.com");
  dns_ip4(&ips,&domain);
  print_ips((unsigned char*)ips.s,ips.len);
  return 0;
}

void print_ips(unsigned char *ips,unsigned int len) {
  int i;

  for (i=0;i<len;i+=4) {
    printf("%u.%u.%u.%u\n",ips[i],ips[i+1],ips[i+2],ips[i+3]);
  }
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc -ldjbdns test.c
実行結果

gmail.com」のIPアドレス(IPアドレスは変わる可能性があります)

74.125.127.83
209.85.225.83
74.125.79.83


「209.191.93.53」のホスト名を得る

#include <djbdns/dns.h>
#include <stdio.h>

int main() {
  char ip[4]={209,191,93,53},seed[128];
  stralloc domain={0};

  dns_random_init(seed);
  dns_name4(&domain,ip);
  stralloc_append(&domain,"");
  printf("%s\n",domain.s);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc -ldjbdns test.c
実行結果

「209.191.93.53」のホスト名(ホスト名は変わる可能性があります)

b1.www.vip.mud.yahoo.com

gmail.com」のMXレコードを得る

#include <djbdns/dns.h>
#include <stdio.h>

int main() {
  char seed[128];
  int i;
  stralloc domain={0},mxs={0};

  dns_random_init(seed);
  stralloc_copys(&domain,"gmail.com");
  dns_mx(&mxs,&domain);
  for (i=0;i<mxs.len;i++) {
    unsigned int mx;

    mx=(unsigned char)mxs.s[i++];
    mx<<=8;
    mx+=(unsigned char)mxs.s[i++];
    printf("%u ",mx);
    for (;mxs.s[i]!='\0';i++) putchar(mxs.s[i]);
    putchar('\n');
  }
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc -ldjbdns test.c
実行結果

gmail.com」のMXレコード(MXレコードは変わる可能性があります)

5 gmail-smtp-in.l.google.com
20 alt2.gmail-smtp-in.l.google.com
30 alt3.gmail-smtp-in.l.google.com
10 alt1.gmail-smtp-in.l.google.com
40 alt4.gmail-smtp-in.l.google.com

自動拡張する配列

GLibには自動拡張する配列があります。要素の追加、削除が容易に行えます。

GArray

#include <glib.h>
typedef struct {
  gchar *data;
  guint len;
} GArray;
GArray* g_array_new(gboolean zero_terminated, gboolean clear_, guint element_size);
#define g_array_append_val(a,v)
#define g_array_prepend_val(a,v)
#define g_array_insert_val(a,i,v)
#define g_array_index(a,t,i)
GArray* g_array_remove_index(GArray *array, guint index_);
gchar* g_array_free(GArray *array, gboolean free_segment);

GArrayでは、データを追加する際に複製したものが追加されます。
なお、GArray->dataは、要素を追加した際にアドレスが変わる可能性があるので注意しましょう。

GArray* g_array_new(gboolean zero_terminated, gboolean clear_, guint element_size);
  • 新しいGArrayを返します
  • zero_terminatedをTRUEにすると、配列の最後に0が追加されます
  • clear_をTRUEにすると、配列が0で初期化されます
  • element_sizeは、配列に格納する要素の型の大きさです
#define g_array_append_val(a,v)
#define g_array_prepend_val(a,v)
#define g_array_insert_val(a,i,v)
#define g_array_index(a,t,i)
  • aは操作を行うGArrayへのポインタです
  • vは追加するデータです。実際の呼び出しは&(v)となっているので、vは変数でないといけません
  • tはGArrayに格納されているデータの型です
  • iは添字です
  • g_array_append_val()は配列の最後に要素を追加し、g_array_prepend_val()は配列の先頭に要素を追加します
  • g_array_insert_val(a,i,v)は指定された添字の場所に要素を挿入します
  • g_array_index()は指定された添字の要素を返します
GArray* g_array_remove_index(GArray *array, guint index_);

arrayからindex_にある要素を削除します。index_よりも後ろにある要素は前にずれることに注意してください。

gchar* g_array_free(GArray *array, gboolean free_segment);

arrayを解放します。free_segmentをTRUEにすると、array->dataも解放します。

使い方
#include <glib.h>
#include <stdio.h>

int main() {
  GArray *garray;
  int a=100,b=120,c=130,d=140,i;

  garray=g_array_new(FALSE,FALSE,sizeof(int));
  g_array_append_val(garray,a);
  g_array_append_val(garray,b);
  g_array_prepend_val(garray,c);
  g_array_insert_val(garray,1,d);
  for (i=0;i<garray->len;i++) {
    printf("%d\n",g_array_index(garray,int,i));
  }
  g_array_free(garray,TRUE);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c `pkg-config --cflags glib-2.0` `pkg-config --libs glib-2.0`
実行結果
130
140
100
120

GPtrArray

#include <glib.h>
typedef struct {
  gpointer *pdata;
  guint     len;
} GPtrArray;
GPtrArray* g_ptr_array_new(void);
void g_ptr_array_add(GPtrArray *array, gpointer data);
#define g_ptr_array_index(array,index_)
gpointer g_ptr_array_remove_index(GPtrArray *array, guint index_);
gpointer* g_ptr_array_free(GPtrArray *array, gboolean free_seg);

GPtrArrayはポインタを格納する配列です。GArrayよりも関数が分かり易いですね。ただし、GArrayのように要素の挿入はできません。
GPtrArray->pdataも、要素を追加した際にアドレスが変わる可能性があるので注意しましょう。

使い方
#include <glib.h>
#include <stdio.h>

int main() {
  GPtrArray *gparray;
  int i;

  gparray=g_ptr_array_new();
  g_ptr_array_add(gparray,"aaa");
  g_ptr_array_add(gparray,"bbb");
  g_ptr_array_add(gparray,"ccc");
  for (i=0;i<gparray->len;i++) {
    printf("%s\n",g_ptr_array_index(gparray,i));
  }
  g_ptr_array_free(gparray,TRUE);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c `pkg-config --cflags glib-2.0` `pkg-config --libs glib-2.0`
実行結果
aaa
bbb
ccc

GByteArray

#include <glib.h>
typedef struct {
  guint8 *data;
  guint   len;
} GByteArray;
GByteArray* g_byte_array_new(void);
GByteArray* g_byte_array_append(GByteArray *array, const guint8 *data, guint len);
GByteArray* g_byte_array_prepend(GByteArray *array, const guint8 *data, guint len);
GByteArray* g_byte_array_remove_index(GByteArray *array, guint index_);
guint8* g_byte_array_free(GByteArray *array, gboolean free_segment);

GByteArrayは、配列にバイト単位のデータを追加していきます。追加の際には複製されたものが追加されます。
GByteArray->dataも要素を追加した際にアドレスが変わる可能性があるので注意しましょう。

使い方
#include <glib.h>
#include <stdio.h>

int main() {
  GByteArray *gbarray;
  int i;

  gbarray=g_byte_array_new();
  g_byte_array_append(gbarray,(guint8*)"aaa",3);
  g_byte_array_append(gbarray,(guint8*)"bb",2);
  g_byte_array_prepend(gbarray,(guint8*)"cccc",4);
  for (i=0;i<gbarray->len;i++) {
    printf("%c",gbarray->data[i]);
  }
  printf("\n");
  g_byte_array_free(gbarray,TRUE);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c `pkg-config --cflags glib-2.0` `pkg-config --libs glib-2.0`
実行結果
ccccaaabb

ハッシュテーブル

glibcのハッシュテーブル

#include <search.h>
int hcreate(size_t nel);
ENTRY *hsearch(ENTRY item, ACTION action);
void hdestroy(void);

nelでハッシュテーブルに格納できるデータ数の最大値を設定します。
キーには文字列しか使えません。
hdestroy()を呼ぶとハッシュテーブルだけでなくitem.keyも解放されるので、ハッシュテーブルへ渡す際にはstrdup()を使いましょう。

使い方
#include <search.h>
#include <stdio.h>
#include <string.h>

int main() {
  ENTRY e,*r;

  e.key=strdup("key");
  e.data="value";
  hcreate(11);
  hsearch(e,ENTER);
  r=hsearch(e,FIND);
  printf("%s %s\n",r->key,(char*)r->data);
  hdestroy();
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c
実行結果
key value

GHashTable

#include <glib.h>
GHashTable* g_hash_table_new(GHashFunc hash_func, GEqualFunc key_equal_func);
void g_hash_table_insert(GHashTable *hash_table, gpointer key, gpointer value);
gpointer g_hash_table_lookup(GHashTable *hash_table, gconstpointer key);
void g_hash_table_destroy(GHashTable *hash_table);

GLibに含まれているハッシュテーブルです。挿入されるデータが増えると自動的にテーブルの大きさを拡張します。
g_hash_table_new()ではハッシュ値を生成する関数、キーの比較関数を指定できるので、どんな型もキーに使えます(文字列、整数値、ポインタ用のハッシュ値生成関数と比較関数は予め用意されています)。
また、ハッシュテーブルを操作する関数も豊富にあります。

使い方
#include <glib.h>
#include <stdio.h>

int main() {
  GHashTable *table;
  gpointer r;

  table=g_hash_table_new(g_str_hash,g_str_equal);
  g_hash_table_insert(table,"key","value");
  r=g_hash_table_lookup(table,"key");
  printf("%s\n",(char*)r);
  g_hash_table_destroy(table);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c `pkg-config --cflags glib-2.0` `pkg-config --libs glib-2.0`
実行結果
value

一行入力

getline()、getdelim()

#define _GNU_SOURCE
#include <stdio.h>
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);

Linuxで使える関数です。
getlineはstreamから一行読み込み、*lineptrに格納します。
必要に応じて勝手に*lineptrを拡張してくれます。自分でバッファを用意するのがめんどくさい人は*lineptrにNULLを入れましょう。
getdelim()は改行以外の区切り文字をdelimに指定できます。

使い方

標準入力から入力されたものを出力します。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main() {
  FILE *file;
  char *line;
  size_t length;
  file=fopen("/dev/stdin","r");
  line=NULL;
  while (getline(&line,&length,file)!=-1) {
    printf(line);
  }
  free(line);
  fclose(file);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c

fgetln()

#include <stdio.h>
char *fgetln(FILE *stream, size_t *len);

BSD系のOSで使える関数です。
読み込んだ一行を返してくれますが、次の行を読み込んだり、ファイルと閉じたりするとポインタが無効になるので、読み込んだ行を残しておきたい場合は複製する必要があります。また、返ってくる文字はヌル文字で終わっていないので注意が必要です。

使い方

標準入力から入力されたものを出力します。

#include <stdio.h>

int main() {
  FILE *file;
  char *line;
  size_t length;

  file=fopen("/dev/stdin","r");
  while ((line=fgetln(file,&length))!=NULL) {
    int i;

    for (i=0;i<length;i++) {
      putchar(line[i]);
    }
  }
  fclose(file);
  return 0;
}
コンパイル

プログラムが書かれたファイル名をtest.cとします。

gcc test.c