ユーニックス総合研究所

  • home
  • archives
  • c-prepro-vector

C言語でプリロセッサ・ライブラリなベクタモジュールを作る

  • 作成日: 2024-04-28
  • 更新日: 2024-04-28

C言語で型指定可能なベクタモジュールを作ろうという記事です。
このモジュールを使うと型を指定して自動でモジュールを作れるようになります。

プリプロセッサ・ライブラリ

プリプロセッサ・ライブラリとはプリプロセスで書かれたライブラリです。
プリプロセスで静的にコードを生成して行うプログラミングのことを言います。

プリプロセスを使うとC言語の型を抽象化することができます。
C++で言うところのテンプレート・ライブラリと実現できることは同じです。

PMPの基礎

プリプロセス・プログラミングは型を指定してコードを生成すると言うものです。
いちばん簡単なコードは以下のようになります。

#include <stdio.h>  

#define DEF_VAR(TYPE, VARNAME, VALUE) TYPE VARNAME = VALUE;  

int main(void) {  
    DEF_VAR(int, n, 10);  
    printf("%d\n", n);  // 10  
    return 0;  
}  

上記のコードではdefineで定義しているDEF_VARというマクロ関数がPMPです。
このマクロ関数では指定された型と変数名と値で変数を定義しています。

ベクタモジュールを作る

では実用的なモジュールを作ります。
ベクタモジュールです。
動的配列で指定された型の配列を管理するモジュールです。
構造体とそれ用の関数をプリプロセッサで生成して、型を指定してコードを生成できるようにします。

まずvector.hを作ります。

#pragma once  

#include <stdlib.h>  

#define DECL_VEC(TYPE, NAME, STRUCT)\  
    typedef struct STRUCT STRUCT;\  
    struct STRUCT {\  
        TYPE *vector;\  
        size_t len;\  
        size_t capa;\  
    };\  
    \  
    STRUCT *\  
    NAME ## _new(void);\  
    \  
    void\  
    NAME ## _del(STRUCT *self);\  
    \  
    STRUCT *\  
    NAME ## _resize(STRUCT *self, size_t new_capa);\  
    \  
    STRUCT *\  
    NAME ## _push_back(STRUCT *self, TYPE value);\  

#define DEF_VEC(TYPE, NAME, STRUCT, ZERO)\  
    \  
    STRUCT *\  
    NAME ## _new(void) {\  
        STRUCT *self = malloc(sizeof(*self));\  
        if (self == NULL) {\  
            return NULL;\  
        }\  
        self->capa = 4;\  
        self->len = 0;\  
        size_t byte = sizeof(TYPE);\  
        size_t size = byte * self->capa + byte;\  
        self->vector = malloc(size);\  
        if (self->vector == NULL) {\  
            free(self);\  
            return NULL;\  
        }\  
        return self;\  
    }\  
    \  
    void\  
    NAME ## _del(STRUCT *self) {\  
         if (self) {\  
            free(self->vector);\  
            free(self);\  
        }\  
    }\  
    \  
    STRUCT *\  
    NAME ## _resize(STRUCT *self, size_t new_capa) {\  
        size_t byte = sizeof(TYPE);\  
        size_t size = byte * new_capa + byte;\  
        TYPE *tmp = realloc(self->vector, size);\  
        if (tmp == NULL) {\  
            return NULL;\  
        }\  
        self->vector = tmp;\  
        self->capa = new_capa;\  
        return self;\  
    }\  
    \  
    STRUCT *\  
    NAME ## _push_back(STRUCT *self, TYPE value) {\  
        if (self->len >= self->capa) {\  
             if (NAME ## _resize(self, self->capa * 2)) {\  
                return NULL;\  
            }\  
        }\  
        self->vector[self->len++] = value;\  
        return NULL;\  
    }\  

次にこのvector.hmain.cで使います。

#include <stdio.h>  
#include "vector.h"  

DECL_VEC(int, int_vec, IntVec)  
DEF_VEC(int, int_vec, IntVec, 0)  

int main(void) {  
    IntVec *v = int_vec_new();  
    int_vec_push_back(v, 1);  
    int_vec_push_back(v, 2);  
    int_vec_push_back(v, 3);  

    for (size_t i = 0; i < v->len; i++) {  
         printf("%d\n", v->vector[i]);  
    }  

    int_vec_del(v);  
    return 0;  
}  

上記のmain.cをコンパイルして実行すると以下の結果になります。

1  
2  
3  

プリプロセスによって可変長配列のモジュールのコードを少ないコードで生成できるようになりました。

プリプロセッサ・ライブラリのデメリット

プリプロセッサ・ライブラリはプリプロセスでコードを生成しますのでコンパイラがエラーが起きたときにコードを追跡してくれないというデメリットがあります。
ですがデバッグ方法は通常のprintfデバッグやgdbなどのデバッガを使えます。