😺

実用的な#include ="でかいデータ用ファイル"について

に公開

背景・動機

研究なのでデータ処理のためのCコードを書いているとき,アプリオリに定義されているデータ構造をファイルIOを用いず使いたい場合がある.
私の場合だと標準地域メッシュを列にとる時系列データベースを組んでおり,それに関連したコードを書いている.
この場合だと列名は固定で,また勝手に列名を変えられたくもないので,ハードコードしてしまいたい欲求がある.
また今回であれば私以外のメンバーも用いるプログラムなため,可能であればポータブル(単一バイナリで実行可能)にしたい.

愚直な実装

みんな大好きC言語を用いているため,普通に考えれば以下のような実装ができる.

//
// Created by ryuzot on 25/01/02.
//

#ifndef MESHID_OPS_H
#define MESHID_OPS_H
#include <stdint.h>

static const uint32_t meshid_list[] = {
#include "../external/meshids/meshid_1_2rd.csv"
};

#endif //MESHID_OPS_H

データの内容はどうでも良いが,これは../external/meshids/meshid_1_2rd.csvに存在するcsvファイル(csvとはいえ改行は無くカンマのみ)を#includeディレクティブで展開し,uint32の配列として用いている.
他にも,バイナリーデータとして用いたいものについてはxxdコマンドを用いCヘッダーファイルを生成する手法も主流である.

[hoge]$ xxd -i meshid_mobaku.mph  > meshid_mobaku.h
[hoge]$ head meshid_mobaku.h
unsigned char meshid_mobaku_mph[] = {
  0x63, 0x68, 0x6d, 0x00, 0xb3, 0xb3, 0x17, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x6a, 0x65, 0x6e, 0x6b, 0x69, 0x6e, 0x73, 0x00,
  0x6d, 0xc2, 0x22, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x6a, 0x65, 0x6e, 0x6b,
  0x69, 0x6e, 0x73, 0x00, 0x4e, 0xc6, 0x23, 0x00, 0x7e, 0x89, 0x31, 0x00,
  0xb3, 0xb3, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

ところで,私は一昔のハッカーとは違い,C言語でも強力なIDEを用いコードを書いている.
vim縛りをしている一部のヤバい人とは違うのである.
私の最も愛用しているIDEはJetbrainsのもの(C言語用であればClion)だが,これらはコーディング支援のために#includeするものはすべて読み込む習性がある.
これは多分主流流行のvscodeとかでも同様.
上記手法だと,#includeするファイルが数MB以上になる場合,IDEがめちゃんこ重くなる.
まあこれは設定次第でどうとでもなるかもだが,そもそもコンパイル時間も多少長くなってよろしくない.
上記ファイルはスタテックなため,リンク時にうまく処理できるはずである.

事前オブジェクトファイル化とcmakeでの利用

まずstaticを外し,また配列サイズを返す変数を入れる.

#include <stdint.h>
#include <stddef.h>

const uint32_t meshid_list[] = {
#include "meshid_1_2rd.csv"
};

const size_t meshid_list_size = sizeof(meshid_list)/sizeof(meshid_list[0]);

コンパイラでオブジェクトファイル作成

[hoge]$ clang -c meshid.c -o meshid.o

次に,利用側から見れるようにするため,以下のようなヘッダファイルを作成

//
// Created by ryuzot on 25/01/02.
//

#ifndef MESHID_OPS_H
#define MESHID_OPS_H

#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

extern const size_t meshid_list_size;
extern const uint32_t meshid_list[];

#ifdef __cplusplus
}
#endif

#endif //MESHID_OPS_H

このヘッダはプロジェクトの中に作ること.
cmakelistでは,まず普通にadd_executableに含めたあと,SET_SOURCE_FILES_PROPERTIESで外部からのオブジェクトファイルであることを明示させる.

...前略...

SET(OBJS
        ${CMAKE_CURRENT_SOURCE_DIR}/external/meshids/meshid.o
)

add_library(lib
        いろいろ
        ${OBJS}
)

...中略...

add_executable(test_meshid_ops
        tests/test_meshid_ops.c
)

target_include_directories(test_meshid_ops PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_link_libraries(test_meshid_ops PUBLIC
        lib
)

SET_SOURCE_FILES_PROPERTIES(
        ${OBJS}
        PROPERTIES
        EXTERNAL_OBJECT true
        GENERATED true
)

感想

cmake周りで思いのほか時間食った

参考

https://stackoverflow.com/questions/38609303/how-to-add-prebuilt-object-files-to-executable-in-cmake

Discussion