プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

組み込み屋こそC++を使うべきなのです

 やーやーやー。なんとワタクシ、遂に脱8bitしました!ワーワー、ドンドンパチパチ!

 さて、組み込みでもRustがアツかったり色々しますね。ただまぁ、Rustってサポートや車載対応等が無いので、政治的ごにょごにょでちょっと使いにくいですよね。

 だから、今まで通りC言語………って決めてしまう前に、ちょっと待って、まだ選択肢有るじゃない。

 そう、C++ですよ!

 

新しい言語とか無理

 安心してください。C++は昭和の言語です(1983年)。昭和ですよ、ショーワ。世界大戦があった時代の言語です。今は令和ですよ。断言しますが、この記事書いてる現在、令和生まれの若い子って、昭和とかもう知らないですよ。古すぎるといって過言ではありません。

 

そうじゃない オブジェクト指向とか無理

 安心してください。C++はオブジェクト指向言語ではありません。C++は 魔留血原太武言語 組み込み屋向けに嬉しい機能がいっぱいある言語です。

 単に組み込みって言ってしまうと、かなり範囲が広いので一概には言えませんが、特にRTOSも使わない様な環境では、実はオブジェクト指向ってしっくりこないんです。staticを理解していない人のコードを見ると、いちいちインスタンス宣言しているので笑ってしまう。

 オブジェクト指向なんて、メモリとポインタ使ってなんぼです。リソースも計算時間も無い系な組み込みには向いてません。オブジェクト指向なんて、組み込み屋が使えなくても問題ありません。newとか組み込みでは死語です。vector、string?使わねぇって。

 組み込み屋が理解すべきは、C++の 魔境 templateとconstexprと型システムです。

 え、潤沢に富豪プログラムができる環境だって?JavaとかPythonとか使えば良いんじゃないかな。

 

気分はtemplate constexpr!

 C言語で、あるペリフェラルのレジスタのビットに書き込みたいとき、どう書くでしょうか?こう?

PIYO->CTRL = 0x10;
FOO->EN     = 1 << 15 | 1 << 20 | 1 << 1;

 

 あ、ちなみに、場合によりけりだけど、以下の様にビットフィールドを使うのは駄目です。

 FOO->EN_b.EN15=1;  FOO->EN_b.EN20=1; FOO->EN_b.EN1=1;

 通常、ペリフェラルのレジスタはvolatileだから、上記のコードはread-modify-writeが3回走ります。一括で初期化したい場合には向いてないですね。

 C++なら、こんな感じでかけます。

// ----------- どこかのヘッダに書いておく -------------------
// ビットを立てない場合は0
constexpr uint32_t bit(){return 0;}

// ビットの計算をする関数
template<typename... ARGS> //←ここは今は気にしない
constexpr uint32_t bit(size_t a,ARGS... args){ //ARGS...も気にしない
  return (1 << a) | bit(args...); //bit(args...)も気にしない
}
//-----------------------------------------------------------------

FOO->EN = bit(1,15,20);

 1,15,20を引数とするbit関数の呼び出しになっていますが、C言語と違って、最適化ビルドをしなくても、この関数の計算をコンパイル時にさせてしまう機能がC++にはあります。この関数の実行時の計算コストは0で、C言語で直値で書いたのと同じです。恐ろしいですね。

 

   

constexprな変数を使ってみよう

 constexprは定数式と言います。C言語では定数を#defineで名前を付けていました。C++では、これをconstexprで記述します。

#define            FOO_MODE1_CONFIG   1 << 1 | 1 << 21
constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 もはや理解が追いつかない!と言う程の大した差は無いですね。

 

 組み込み屋にとって、一番の大きな違いは、マクロ特有のバグが入らないことです。

 例えば、以下のコードは、#defineとconstexprでは結果が変わります。

FOO->CONFIG = FOO_MODE1_CONFIG + 0x1;

 define文はあくまで文字列置換なので、「1 << 1 | 1 << 21 + 0x1」となり、結果的に「1 << 1 | 1 << 22」と同じになりますね。このバグを避ける為に、defineを書くときはヒステリックな程に()で式を囲います。

 constexprでは特に()で囲ったりしていませんが、正しく「FOO_MODE1_CONFIGの値と、0x1の加算」となります。

 

constexprな変数?定数?

 ところで、constexprを外すと、普通の変数宣言と同じになります。

constexpr uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;
          uint32_t FOO_MODE1_CONFIG = 1 << 1 | 1 << 21;

 ということは、どこかに変数用の領域が用意されていて、それを読み出しているのだろうと思われるかも知れません。

 対象デバイスの命令セットに依存しますが、今回のFOO_MODE1_CONFIGは大きな値なので、命令の直値にはならないでしょう。多分、普通の定数と同じく、関数の後ろに値が配置されるかと思われます。FOO_MODE1_CONFIGが非常に大きな配列やら構造体なら、Flashのどこかに1箇所だけ置かれるかも知れません。

 しかし、逆に、FOO_MODE1_CONFIGが1とか100みたいな小さな数であれば、命令の直値に変換されることでしょう。

 constexprな変数は、書き換え不可能な変数であり、かつ、直値にもなり得えます。直値として埋め込まれ、誰も実体を必要としなければ、最終的なバイナリに残りません。

 マクロと違って安全で、それでいて、マクロと同じく直値にもなり得る。こんな素晴らしい機能があるのに組み込みで使わないなんて信じられません。さぁ、C++を使いましょう。

 

constexprな関数を使ってみよう

 constexprな変数FOO_MODE1_CONFIGには問題があります。

 MODEが10個くらいあったらどうするというのだろうか。全部書くのか? FOO_MODE A_X_1 CONFIGとか、プロパティ増えたらどーすんの!?

 これを解決してくれるのがconstexprな関数です。

 例えば、FOO_MODEx_CONFIGは「1 << x | 1 << 21」としよう。

constexpr uint32_t FOO_MODE_CONFIG(int x){  return 1 <<  x  |  1 << 21; }

//使う場合
FOO->CONFIG = FOO_MODE_CONFIG(1) + 0x1;

 

 関数なので、全部のモードについて定義をしなくても、勝手に値が求まります。

 そして、C++コンパイラはconstexprな関数を定数だけで呼び出している場合、勝手に計算して、定数にしてくれます。 実行時には関数呼び出しが行われませんので、計算コストはタダです。

 なお、実行時に値が決まる変数などで呼び出すと、実行時に関数を呼び出します。constexprな関数は、定数としても、関数としても使うことができます

 文法も単純で、ふつーの関数定義にconstexprがついているだけです。非常に簡単ですね。

 

 と、言いたいのですが、constexprな関数は場合によってはちょっと面倒くさくなります。

 例えば、 \sum_{n = 0}^x nを愚直に0+1+2+3....と計算するconstexprな関数はこう書きます。なお、愚直じゃない計算は  \frac{x(x+1)}{2}ですね。

constexpr uint32_t sum(uint32_t x)
{
  return 
    x == 0? 0 :   
    x == 1? 1 :
            2 * (sum(x/2) + sum((x-1)/2)) + (x +1)/2;
}

 ちょっと何書いてるのか意味が分からないですね。これ書いた私は、非常に頭が悪いですね。私にもさっぱり分かりません。

 こんな頭の悪いクソコードを読み解く必要はなくて、重要なのは、return文しかないという所です。実はconstexprな関数には、return文しか書けませんでした。

 でした、って述べた様に、この書き方はC++11っていうちょびっと古いバージョンで、凡骨向けの書き方です。 私の様なポンコツには想像も及ばない世界です。

 でも大丈夫。C++14でだいぶポンコツフレンドリーになりました。C++14ではfor文もif文も変数宣言も解禁され、以下の様に書けます。

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

 読める、読めるぞぉ!

 C++11のconstexprは、どうしても再帰関数の形になって、最適化しないとスタック消費が激しいですが、C++14なら、constexpr用のsumと実行時用のsumを別個に実装なんてしなくても良いのです。ビバC++14!

 

 さて、constexprな関数がどれぐらい便利かは、お使いのコンパイラがどのC++のバージョンにまで対応しているかによります。

 constexprが使えるC++のバージョンはC++11、C++14があります。正確には「ISO/IEC 14882:2011」、「ISO/IEC 14882:2014」といいます。要するに2011年規格と、2014年規格ってことです。1998年版はC++98です。2098年?さぁ……?

 ARM系なら、基本的には最新のコンパイラはC++14に正式に対応しています。 KailのARM Compiler Version6、IARのコンパイラ、arm-none-eabi-gcc 8辺りは全部対応しています。

 

 本当は更にポンコツ迎合したC++17ってのもあるんだけど、今のところ組み込みで正式に対応してるのはないんじゃないかな。 GCCは先月公開された9.1で正式にC++17に対応しています。組み込みも、後2,3年ぐらいしたらC++17対応してくるかと思いますが。 C++20対応はあと6,7年ぐらいしたら出てくるんじゃないの。

 

static_assertでテストを簡略化しよう

 組み込みって、チープな環境でのテストで嫌になりますよね。テスト走らそうにも、メモリが足りないしね。

 しかし、どれだけ面倒だろうが、C言語では、関数の実装が本当に正しいかは、実機やシミュレータで走らせて確認するしかありませんでした。

 C++はこれをコンパイル時に確認するstatic_assertを提供してくれます。

#include <cstdint>  // uint32_t
#include <cassert> // static_assertを使う為にinclude

constexpr uint32_t sum(uint32_t x)
{
  uint32_t s = 0;
  for(uint32_t i =0 ; i < x; i++)
    s += i;
  return s;
}

static_assert(   sum(0) == 0 ,"sum(0) == 0");
static_assert(   sum(1) == 1 ,"sum(1) == 1"); 
static_assert(   sum(10) == 55 ,"sum(10) == 55");
static_assert(  sum(1000) == 1000*1001/2, "sum(1000) = 1000*1001/2");

 static_assert ( 成立するべき式 , "エラーメッセージ" ) と記述します。C++17ではエラーメッセージの省略ができます。うらやましい。

 これをコンパイルすると、例えばこんな感じになります。

test.cpp:13:25: error: static assertion failed: sum(1) == 1
 static_assert(   sum(1) == 1 ,"sum(1) == 1");
                  ~~~~~~~^~~~

 sum(1)が1にならない!と言っていますね。原因は i < x; の部分ですね。正確には、i <= xです。バグってのはこういうしょーもない所に入り込む物です。

 このように、実機に持ち込まなくても、コンパイル時にconstexprな関数の動作を確認することができます。言い換えれば、ある入力に対する結果が確定する処理は、コンパイル時にテスト可能です。

 組み込みはコンパイル時に動作が決まらないんだよ、糞野郎!という魂の叫びが聞こえますが、果たして本当にそうでしょうか?

 無論、組み込みである以上、ペリフェラルの状態に依存することも多いでしょう。しかし、動作の全てがコンパイル時に未定義なのでしょうか?

 例えば、UARTから値を受け取って、コマンドを実行する場面を考えましょう。確かに、通信相手とUARTペリフェラル次第で、動きは確定できません。UARTから値を受け取る処理は、実機検証するしかないでしょう。

 しかし、「コマンドの実行」まで動作未定義でしょうか?むろん、ペリフェラル依存のコマンドもあるかも知れませんが、コンパイル時に確定する動作もあるのではないでしょうか?

 全体としてはコンパイル時に決まらない動作でも、部分的にはコンパイル時に確定するはずです。そう言った内容は、constexprな関数で抜き出して、先にC++コンパイラの上でテストすることができます。

 

 (ペリフェラルの動作をmocで作っちゃえば、static_assertでテストできそうとか思いましたが、実機って思った様に動かないので、やめた方がいいでしょうね。)

 

template constexprな関数を使おう

 先に挙げたbit関数の例は、複数ビットを指定することができます。C++14版も載せておきましょう。

// C++ 11用
constexpr uint32_t bit(){return 0;}
template<typename... ARGS> constexpr uint32_t bit(size_t a,ARGS... args){
  return (1 << a) | bit(args...);
}

// C++ 14用
template<typename... ARGS>
constexpr uint32_t bit(ARGS... args){
  uint32_t v = 0;
  for(const auto a: {args...})  // 鉞投げても良いのよ
    v |= 1 << a;

  //  意味の分からない書き方をするなら、こう
  //  (void)std::initializer_list<int>{(void(v |= 1 << (int)args),0)...};

  return v;
}

FOO->EN = bit(1,15,20);

 

 さて、templateという謎の機能が出てきました。

 テンプレートやスケルトンと言ってなんとなーくイメージが湧いてくれれば良いのですが、これを簡単に語れというのは、ぶっちゃけ無理です。

 ざっくり言ってしまえば、関数の設計書で、template<typename... ARGS>のARGSを型を指定することで、実際の関数を作る機能です。

 色々探してみて、読みやすいかなーと思った記事を紹介しておきます。以下の記事で理解できなければ、「C++ 関数テンプレート」「C++ テンプレート アンパック」とかで検索してください。

 

template<数値> constexprな関数を使おう

 先ほどは、「型で」templateを指定していましたが、実は型意外にも整数でtemplateを作ることができます。

//Vに100を足す
template<int V>
constexpr int add100(){ return V + 100;}

static_assert( add100<1>() == 101 ,"");

 これを使って、bit関数を再定義すると以下の様になります。

template<unsigned int... ARGS>
constexpr uint32_t bit(){
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

FOO->EN = bit<1,15,20>();

 これのメリットは変数を渡せなくなったことです。

 このbit関数って、組み込み的に考えたら、高確率で定数ですよね。 しかし、間違えてbit関数に変数を受け入れてしまうと、そこがbit関数呼び出しになってしまう。しかし、コンパイルエラーになる訳じゃないから、そのもったいないを見抜けない。

 この書き方だと、bit<変数>とは書けませんから、コンパイル時にミスに気がつきます。なお、constexprな変数=定数は入れることができます。

 

template constexprな変数を使おう

 先ほどの「bit<1,15,20>();」ですが………、面倒くさくないっすか?「()」書くの。だってさー、()っていらないじゃーん。ぶっちゃけ。

 そういう場合は、template constexprな変数を使いましょう。

template<unsigned int... ARGS>
constexpr uint32_t __bit(){   // __を追加
  uint32_t v = 0;
  for(const auto a: {ARGS...})  v |= 1 << a;
  return v;
}

template<unsigned int... ARGS>
constexpr uint32_t bit = __bit<ARGS...>(); //template constexprな変数bit

FOO->EN = bit<1,15,20>;

 無駄な「()」が必要なくなりましたね!

 

気分はstatic!

 templateとconstexprを理解した皆さんは、C++がいかにCより組み込み屋にとって便利か、理解できたかと思います。

 しかし、まだ、使いこなしているとは言いがたいです。

 最後のキーワード、それはstaticです。C言語にも、ファイルスコープを表すstaticがありましたが、ここでのstaticはクラスの中で使うstaticです。

 staticはC++のclassやstructを名前空間に変えてしまう、魔法の単語です。

 例えば、各ペリフェラルの電力供給管理をするペリフェラルのPOWレジスタがある、という場面を考えてみましょう。各ペリフェラルの電力は、POWレジスタの適当なビットに対応してるとします。例えば、以下の様な情報として記述しておくとしましょうか。

struct DMA
{
  static constexpr uint32_t POW = bit<3>;
};

template<unsigned int N> struct PWM
{
  static constexpr uint32_t POW = bit<6 + N>;
};
using PWM0 = PWM<0>;
using PWM1 = PWM<1>;


template<unsigned int N>  struct UART
{
  static constexpr uint32_t POW = bit<12 + N>;
};
using UART0 = UART<0>;
using UART1 = UART<1>;
using UART2 = UART<2>;
using UART3 = UART<3>;

 ここで重要なのは、DMA,PWM,UART全ての構造体が、「static constexpr POW」を持つという所です。これを利用すると、POWレジスタを設定する関数は以下の様に作れます。

template<typename... ARGS>
constexpr uint32_t  __get_POW(){
  uint32_t v = 0;
  (void)std::initializer_list<int>{(void( v |= ARGS::POW),0)...};
  return v;
}

template<typename... ARGS>
inline void powerOn(){
  FOO-> POW = FOO-> POW | __get_POW<ARGS...>();
}

//使い方
powerOn<PWM0,UART2,UART3>();

 「(void)std::initializer_list{(void( v |= ARGS::POW),0)...};」ここがミソですね。swallowと呼ばれるテクニックを使っていますが、それを可能にしているのが、全ての構造体が「static constexpr POW」を持つという条件です。

 確かにこの構造体を先に定義するのは面倒ですが、一度定義してしまえば、別プロジェクトでも使い回せます。あくまでconstexprな定数でしかないので、余計なバイナリを生成する心配はありません。

 ビットフィールド指定と違って、constexprな関数の__get_POWでビットを計算するので、read-modify-writeも1回しか発生しません。それでいて、ソースコードを後から読んだときでも、PWM0、UART2、UART3の3モジュールの電源を入れてるんだなと、理解しやすいですよね。可読性もバッチリです。

 

まとめ

 どうでしたでしょうか、C++が理解できたでしょうか? うん、知ってる、 わからんよね!!

 ぶっちゃけると、C++は深淵を覗けば、何処までも深みにはまります。他人のコードを見ると、魔法みたいなことをしてることが多々あります。

 私もC++なんて全然分かりません。最近になってinline namespaceとか知ったぐらいだしね。C++は謎の記号が多くて、検索しにくく、何の文法かも分からんことがあります。C++の全てを理解してる人間なんて、この世にいないんじゃないかとすら思います。理解してるのは、魔人か何かです。

 しかし、「Cで書けるんだから、C++なんて価値もないクソ」ではなく、「C++はよく分からないけど、組み込みに便利らしい」と、少しでも歩み寄って頂けたなら本望です。

 今回は紹介しませんでしたが、RAIIなど、Cでは使えない組み込みで有用なテクニックが他にも色々あるので、調べてみると良いでしょう。

基礎からしっかり学ぶC++の教科書 C++14対応

基礎からしっかり学ぶC++の教科書 C++14対応

ロベールのC++入門講座

ロベールのC++入門講座

  • 作者:ロベール
  • 発売日: 2007/11/15
  • メディア: 単行本(ソフトカバー)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

Effective C++ 第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTI)

今更ながら、文字列を型にしたい

 やーやーやー

 なんで私、最近こんな黒魔術に手を出してるんだろうね。最近、相当変態的な方向性でC++を悪用 利用しようと企ててるから、こんな羽目になってますよ。

 と言うのもですね、こう、色々と面倒くさい諸問題を回避しようとしてたら、黒い方法を思いついてしまったんだ。いや、私は悪くない。Goサインを出した人が悪いんだ。

 

 しかし、C++11、C++14で相当敷居が下がったって言っても、型だとか、constexprってやっぱり、こう、闇の軍団か中3女子的なイメージあるじゃない。タイトルみたいなことは、Sprout使ったらすぐできそうなんだけど、政治的ごにょごにょにより…

 ポンコツ…いや、豚骨な私が手を出して良い領域じゃないと思うんですよ。私はC++を完全に理解した日は来るのだろうか。

 闇の軍団的にはできて当たり前なのかも知れないけど、こう、ファイアをやっと唱えられましたレベルの私だと中々ね。

 

やりたいこと

 以下の型を宣言したい。

template<char... chars>struct char_type
{
  static void print(){
    (void)std::initializer_list<int>{(void(std::cout << chars),0)...};
    std::cout << std::endl;
  }
};

constexprな関数から作る場合

 C++14なら豚骨にも簡単にできた。

 関数から作ることができるので、constexprな配列の名前が定義されてたら、同じように作ることが可能。

template<int N> struct make_alphabet_type
{
private:
  static constexpr char get(int n){return (char)('a'+n);}
  template<typename Seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<get(seq)...>;
  };
public:
  using type =typename make_char_type<std::make_integer_sequence<size_t,N>>::type;
};

int main(int argc, char *argv[])
{
  using a5_t = make_alphabet_type<5>::type;
  a5_t::print(); // abcdeと出力される
  return 0;
}

 実体は何処にもないけど、constexprの計算で作ったchar配列の型を作ることができたよ!

 

文字列リテラルから作りたい

 GCC拡張のtemplateユーザー文字列リテラルが使えるなら簡単なんだけど、これどうすれば良いんだろ。一度どこかに名前を作れれたりすればいいんだけど。ラムダで何とかならないかと思ったけど無理でした。

 BOOST_PP_ENUMとか使えばできるっぽいけど。

 BOOST_PP_ENUMと同じことを手でやってしまえば、固定長だけど、出来ないことは無くて、しかも私の目的としてはこれで十分なんだけど。

//GCCの場合はこれ
template<char... chars>
constexpr char_type<chars...> operator ""_chartype(){return char_type<chars...>();}

template<char... chars>struct _char_type{
private:
  static constexpr char str[] = {chars...};
  static constexpr size_t array_size = sizeof...(chars);
  static constexpr size_t count(size_t i){
    return i == array_size? array_size:
      str[i] == '\0' ? i : count(i+1);
  }
  
  static constexpr size_t length = count(0);
  
  template<typename seq>struct make_char_type{};
  template<typename I,I... seq>struct make_char_type<std::integer_sequence<I,seq...>>{
    using type = char_type<str[seq]...>;
  };
public:
  using type = typename make_char_type<std::make_integer_sequence<int, length>>::type;
};
template<int N>
constexpr char get_char(const char (&array)[N],int index){
  return index < N? array[index] : '\0';
}

#define GET_CHAR_TYPE(x) _char_type<get_char(x,0),get_char(x,1),get_char(x,2),get_char(x,3),get_char(x,4),get_char(x,5),get_char(x,6)>::type

int main(int argc, char *argv[])
{
  using abc_t = GET_CHAR_TYPE("abc");
  abc_t::print();
  return 0;
}

constexprでfloatのバイナリ値を取得したい

 constexprで計算したfloatの値を、バイナリをそのままにuint32_tに変換して配列の一部に保存したいという場面に出くわしました。

 templateの構造体で定義すれば良いようにも思えますが、floatが入る位置や数は未定で、やりにくいのです。

 というわけで、float→uint32_tというバイナリ解釈の変更をしたいのですが、なんと、constexprでunion型って、アクティブでない要素を計算に使えないんですね。

union b32_t{
  uint32_t i;
  float f;
  constexpr b32_t(float v):f{v}{}
  constexpr b32_t(uint32_t v):i{v}{}
};

constexpr uint32_t fbToUi32(float f)
{
    b32_t b(f);
    return b.i; // iがアクティブでないのでエラー
}

 んー。まじか。

 リトル/ビッグエンディアンぐらいは考慮しないといけないかなーとか思ってたけど、それ以前の問題だった。たぶん、C++コンパイラのconstexpr処理系では、unionは普通に二つの要素を持つオブジェクトとして処理されてるんだろうね。

 C++20からは制限が解除されるとからしいけど、C++11とかC++14なので関係ない。

 GCCだと、cmathの関数がconstexprになってるから、logbとかsignbitとかを使えば再構築できるけど、残念ながら私が使っている処理系では無理。 中3女子ことBolero MURAKAMIさんのSproutライブラリを使えれば良いのですが、政治的諸事情により使いにくい。困った。

 

 あ、なお、この辺もC++20でconstexprがつく様になるらしいっすよ。今の私にゃ関係ないけどね。

 

 

float→uint32_tを自前で実装

 諦めて自分でfloatの値に対応するバイナリを手動で書いてしまえばいいのだが、残念ながら私はクソニートだ。ここで意志を曲げて部屋から出て行く様では、老後が心配になってしまう。親に立ち向かう勇気を持って、この問題に立ち向かう義務がある。

 さて、こうなってしまっては自分でfloatの値を解析して、uint32_tを再構築するしかないだろう。

 float(IEEE 754準拠)は符号、仮数部、指数部の3つからなる。仮数部は2のn乗の部分で、実際にはn+127の値が入ります。指数部は1から2未満の値です。

 まぁ、そんなに難しかぁないさ。単純にそれぞれについて解析していけば良いでしょう。

 ただし、幾つかfloatには特殊な値が存在があります。+0、-0、∞、-∞、NaN、非正規化数である。これらは別個に処理…

constexpr bool is_pos0(float f){ return f ==  .0f; }
constexpr bool is_neg0(float f){ return f == -.0f; }

int main(int argc, char *argv[])
{
  static_assert(is_pos0(.0f));
  static_assert(!is_pos0(-.0f)); // エラー
  static_assert(!is_neg0(.0f));  // エラー
  static_assert(is_neg0(-.0f));
  static_assert(!is_pos0(1.0f));
  static_assert(!is_neg0(-1.0f));
  return 0;
}

 

 あるぇえ?-0と+0の区別が付かない………。あれ、バイナリ同じ物が生成されるの?いや、b32_tで出力してみたらちゃんと符号ビットが違うし………

 ………

 あははは!そうだよね、0は0だよね!誰だよ、0に正と負があるとか言った奴!ねぇよ、んなもん!

 よし、見なかったことにしよう。

 ただし、幾つかfloatには特殊な値が存在があります。0、∞、-∞、NaN、非正規化数である。これらは別個に処理する必要があります。

 一先ずはC++14で実装。

#include <limits>
#include <cstdint>

using float_limits = std::numeric_limits<float>;

constexpr float FLOAT_POS_INFINITY = float_limits::infinity();
constexpr float FLOAT_NEG_INFINITY = -float_limits::infinity();

constexpr bool is_zero(float f){ return f == .0f; }
constexpr bool is_posinf(float f){ return f == FLOAT_POS_INFINITY;}
constexpr bool is_neginf(float f){ return f == FLOAT_NEG_INFINITY;}
constexpr bool is_nan(float f){ return f != f;}
constexpr bool is_denormal(float f){
  //こんなんでいいのかな?
  return f != 0.f 
      && f < float_limits:: min_exponent 
      && f > -float_limits:: min_exponent;
}

constexpr uint32_t fbToUi32(float f){
  if(is_zero(f)){
    return 0;
  }else if(is_posinf(f)){
    return 2139095040;
  }else if(is_neginf(f)){
    return 4286578688;
  }else if(is_nan(f)){
    return 2143289344;
  }else if(is_denormal(f)){
    //デバイスの制限上0にしています。エラーでも良いかも
    return 0;
  }

  const uint32_t sign = f < 0? 0x80000000:0;
  if(f < 0){ f = -f; }

  uint32_t exp = 127;
  if(f < 1){
    while(f < 1){
      f *=2;
      exp--;
    }
  }else{
    while(f >=2){
      f *=0.5;
      exp++;
    }
  }

  uint32_t frac =0;
  f -=1;
  uint32_t _frac = 1 << 22;
  float sub = .5;
  while(f != 0.0 && _frac != 0){
    if(f >= sub){
      frac |= _frac;
      f -= sub;
    }
    sub *= .5;
    _frac = _frac >> 1;
  }
  
  return sign | (exp << 23) | frac;
}

 どうせコンパイル時にしか使わないんだし、仮数部の探索とかを二分探索とかにする必要はないでしょう。

 一応何個かテストしてみたら、それっぽく正しい値が出てるっぽいよ。とりあえずはこれでいっかな。後は必要ならC++11にすっか。

Wordを捨ててTeXでドキュメントを自動生成しようじゃないか

 やーやーやー

  •  シミュレータや合成ツールに金出したら、もう金がないので、チップの検証環境を自作
  •  金がないので、RTL自動生成を自作
    •  気がつけば10モジュールぐらい担当してた
  •  金がないので、RedmineとGitbuket、Rocket.Chat、TestLinkを導入して、開発環境と検証環境と連携するシステムを自作

 

 と、命令された訳でも無いのに、ひたすら文句を言い続けてたら、いつの間にかこんな状況になってしまった。ついでの過去の罪状として、プログラムの開発環境が貧弱と文句を言っていたら、いつの間にか統合開発環境(IDE)を自作していた。器用貧乏を地で行っている気がしてならない。

 

 現状を現状のままに受け入れる力が、社会人に最も重要な能力。みんなは黙って働こう。

 

 

ドキュメントを自動生成したい

 プロダクトに関わると、どうしても設計資料とは別に、利用者の為のドキュメント(データシート)を、どこかの時点で用意する必要に迫られる。

 数ページ済む様なデータシートならどうとでもなるが、1000pにもなりそうなデータシートなんて、手で書いていられない。そもそも、プロトタイプではAだったが、具合を見て変わる予定という情報は匂いすぎる。予言者でなくとも、ドキュメントがバグる未来が見える。

 仕様書やデータがあるなら、ドキュメントは自動生成してしまった方が、安心安全だ。しかし、金はない。仕方がない、またしても余計な仕事を勝手に抱えようじゃないか

 

 では、どうやってドキュメントを自動生成するか。

 途中工程によっても違うだろうが、極論をしてしまえば、ドキュメントは最後にPDFになりさえすれば良い。自動生成プログラムから直にPDFを吐き出すことも不可能ではないが、手段はおおよそ以下の二つだろう。

  • Wordを生成し、WordからPDFにする
  • PDFに変換出来る言語(TeXなど)を生成する

 

Wordを使う選択肢

 私は、最初は諸々の政治的な理由からWordの自動生成を試した。

 しかし、Wordの自動生成は無理と言わんが、片手間に出来るレベルじゃないと言って良い。理由を語ろう。

 それぞれの場面において、ドキュメントを作成する為のフォーマットガイドはあるだろう。例えばフッターだったり、ヘッダーだったり、フォント、表の配置、行間等など、それを従ってなくては怒られる。 書籍を出す訳でもないのに、フォーマットを確認する費用で、何円の価値が生まれるのか、私には甚だ疑問だが。

 一般的に、処理とデータ(文章)と、フォーマット(文章の見た目)は別個に管理することが、見通しも、プログラマの精神衛生的にもとてもよろしい。しかし、Wordを自動生成をする場合、これらをゴッチャにして、ガイドを満たす様にプログラムを組まなくてはならない。反吐が出る。

 仮にスタイルのテンプレートファイルを使うとしても、細かいルールが全てスタイルの設定で解決する訳ではない。細かい計算をする必要はある。例えば、表は画面幅いっぱいとかいうルールは、スタイルで解決しない。だいたい、そもそものスタイルテンプレートファイルが変更されたら、どうするというのだ。

  

 更にまだ問題はある。

 ドキュメントの全てを自動生成出来るはずもない。かならず、手作業でドキュメントを作る部分は存在する。

 巨大な自動生成したドキュメントなら、別冊で良いだろう。では、2、3文の程度の、断片的な自動生成可能な情報は?1つ程度の表は?数値は?単語は?手作業で作るドキュメントに追加したい、小さな自動生成情報はどうするというのだ?

f:id:nodamushi:20190323205333p:plain:w320

 こういうものも、出来れば自動生成しておきたい。しかし、これをWordで上手く解決する手段を、私は思いつかなかった。何か良い手段があれば、是非教えて頂きたい。

 どうしてもWordファイルに拘り、手作業で書くドキュメントと、自動生成の融合を図り、Wordの様なバカ向け……WYSIWYGに拘るなら、Blogの様なWYSIWYGなシステムを提供し(なお、この記事はMarkdownで書かれてる)、Wordファイルを出力するシステムを作るしかないだろう。

 お金があるなら、それはとても良いことだ。ベンダーにお金を払えば良い。それらのシステムが出来ないことは、絶対にありえない。Office365はブラウザで動くのだから。

 しかし、お金がないからこういう問題に頭を悩ます訳で。

 むろん、私が死ぬまで働いて解決出来るなら、私一人で死ねば良い。歯車一つ壊れる程度は、コラテラル・ダメージでしかない。だが、私一人死んだからと言って、システムが完成する未来は見えない。完成するにしても、間に合うことは絶対にない。そのぐらい大きな規模のシステムになる。

 

 

ドキュメントにTeXを使う選択肢

 

 Wordで自動生成と手作業ドキュメントの統合は諦めた。ならば、残る選択肢は以下の三つだ。

  • 自動生成を諦める
  • 統合だけを諦める(Wordを自動生成するという選択肢は残る)
  • もう、Wordを使わない

 もちろん、自動生成を諦めるという選択肢は論外だが、どれにせよ、全員で痛みを共有することになる。

 ところで、なぜ、Wordファイルでなくてはならないのかというのは、大抵、査読等の理由だ。しかし、そもそも、自動生成されたWordを修正をしたところで、意味が無い。真に修正すべきはデータベースと自動生成プログラムであり、Wordファイルではない。更に言えば、毎回自動生成されるのだから、Wordファイルに履歴は残らない。そうなると、WordでPDFを編集出来るのだから、もはやWordに拘る理由がない。後は政治的な理由のみだ。

 どの手段を選ぶにせよ、自動生成するプログラムがWordを吐き出す正義はなくなった。

 ではWord以外の、何を生成すべきか、何でPDFを生成すべきか、だ。

  • TeX
  • Re:VIEW
  • troff

 まだあるかも知れないが、私が知っているのはこの程度だ。

 新しい物好きな私は、Re:VIEWにしようかなとも思ったのだが、よく読むと、結局の所LaTeXを作成してPDFにするらしい。レイアウトの細かい調整をするには結局TeXを弄る必要がある。だったら、学生の頃よく使ってたし、しょぼい処理系を作ったこともあるTeXでいっかということで、TeXを生成することにした。

 そして、TeXの場合、マクロを提供するという形で、自動生成ドキュメントと、手作業によるドキュメントの統合が可能になる。「初期のモードは\initmode です」と書いておけば、コンパイル時に\initmode が適切な文字列に変わるのだ。

 ならば、もう選ぶべき手段は、Wordを使わない、である。容易な手段が転がっているのに、それを使わないというのか。

 WYSIWYGなんて、もういいじゃないか。マイクロソフトに保護に抱かれて気持ちよかっただろう?コンピュータを、人間の直感の為に改良してくれて、とても安心出来ただろう?コンピュータという気味が悪いナニカに、親しむことが出来ただろう?でも、それは必ずしも、コンピュータをコンピュータとして、効率よく使っているとは限らない。いい加減、コンピュータをコンピュータとして使おうじゃないか。

  f:id:nodamushi:20190323215020p:plain

 と、いうわけで、上司に昼飯時にぼやいてみたら、ノリに乗ってくれて、そのまま公式に認められてしまった。素晴らしい上司である。

 

 

TeXを何処まで自動生成するか

 さぁ、TeXを使うと決まった。

 TeXならテキストファイルを書き出すだけなので、ライブラリも特に何もなくても作れる。とても楽である。 と言って、何も考えずに「\section{ほげ} \begin{table}[h] ~~~」などと吐き出すのは、やめた方がいい。

 文章やドキュメント構成を全てプログラムで行うと、校正でダメ出しされるときが面倒くさい。プログラムを修正し、再生成しなくてはならない。運用次第ではあるが、それぞれのメンバーごとに、ファイルを自動生成する様な仕組みなら、ちょっとやっていられない。

 そもそも、TeXはほぼ何でも出来るのだ。言ってしまえば、データさえあれば、ドキュメント構成は全てTeXで処理することだって可能だ。

 ドキュメントの構成を全てTeXに任せてしまうのなら、自動生成するプログラムはデータをシリアライズして、書き出すだけである。TLCのリストみたいな感じに書き出しておくと、扱いやすい。

\def\data{%
  {peripherals}{%
    {%
      {name}{DMA}%
      {address}{{4981504}{4C0300}}%
      {registers}{%
        {%
          {name}{CTRL}%
          {address}{{4981504}{4C0300}}%
          {fields}{%
            .............以下略............
}

 これだと、自動生成する側のやることはほとんど無くて、管理が楽である。やることと言えば、_&^等の文字をエスケープするのと、10進数、16進数、2進数の数値データを用意してあげる位だ。 (n進数変換はTeXでやろうとしたのだけど、TeXのカウンターは32bit符号付き整数なので、大きな数字はやりにくい。出来ないことは無いけど。)

 ドキュメントにダメ出しが入っても、プログラムを修正する必要も、再生成する必要もなく、TeXを再コンパイルするだけで良い。実に楽だ。

 じゃぁ、これで万事解決かというと、そうでもない。

 

 話は突然変わるが、皆さんは約千行のプログラム、と聞いてどんな印象を持つだろうか?

 無論、プログラムというのはその機能や、管理のしやすさと言った面から評価すべきであり、行数は大した価値を持つべきではない。それは千も承知の上で、ただただ、千行のプログラム、で考えてみて欲しい。

 まぁ、半日~1日ぐらいで書き殴ったプロトタイプかな、という印象ではなかろうか。

 で、今回、私はTeXのマクロを1000~2000行程書いたが、気がつけば2週間過ぎている。マジか。

 慣れていないから、というのもその通りなのだが、ひじょーにデバッグしにくい。いや、マジで。高々if文で躓くし、expandafterで、noexpandで、relaxで、table環境の\hlineで、tabularxで、ありとあらゆるところでゲロを吐く羽目になった。

 一度躓くと、エラーが何処で起こってるのか分からない。プログラミングなら、エラーが起こった場所はブレークポイントなりで解析出来るが、TeXの場合はどういう展開になるのか見てくしかない。

 

 というわけで、素人に毛が生えた程度で何でもTeXでやってやるぜ、というのはやめた方がいい。いたぶられ続けて、段々トランスして、気持ちよくなってくる快感を味わいたいなら止めはしないが。アンッ♡

 どの程度をTeXで処理し、どの程度をプログラムでやるのか、その辺は技量と運用方法と性癖をよく考えながら作る必要がある。

 

 

TeXの環境を整備しよう

 TeXを使います!TeXを自動生成しました!はい、後はみんな頑張って!よっしゃ、お終い!というほど話は簡単ではない。TeXを使わせるなら、TeXを使うなりのシステムを用意する必要がある。

 少なくとも、みながTeX素人なら、以下の内容について言い出しっぺが準備する必要がある。(私も正直TeXの素人に毛が生えた程度だが)

 

  • どのTeXエンジンを使うかの選定と、インストール方法の定義
  • TeXの使い方の講習
  • ドキュメントガイドに沿ったクラスファイルの作成
  • 表、画像などを統一する為のマクロ(スタイル)の提供
  • その他、チーム特有の問題を解決する為のスタイルの提供
  • チームでファイルを分割してコンパイルする環境整備
  • Git管理の手法
  • Lintツールの導入

 

 

LuaLaTeXを使おう

 TeXかLaTeXか新規に選択しろ、というなら普通にLaTeXを選べば良い。ConTeXtは安定してないのでどうなんだろうね。わからん。

 LaTeXが提供するマクロを捨てて、TeXだけで闘う理由は特に思い当たらない。別に原理主義に陥る必要は全くない。

 次にどのTeXエンジンを使うか、である。単にTeXといっても、幾つかの処理系があり、処理系ごとに特色が微妙に異なる。同じOSで、だいたい同じような機能は提供してても、Windows、Mac、Linuxで操作が微妙に異なる様な物だ。

 これは作成するドキュメントガイド次第ではあるが、細かくフォントの指定がされているなら、LuaTeXを使うことをお勧めする。

 10年ま………ゲフン、ゲフン、学生だった頃に使ってたpLaTeXを最初弄っていたのだが、クッソメンドイ。やってられん。XeTeXも試してみたのだが、どうも日本語のフォントを切り替えるのが、上手くいかなかった。

 で、LuaTeXなのだが、これがクッソ楽だった。とりあえず、\newjfontfamilyでフォント定義しておいて、必要な場面で使えばいいだけなのだ。

 過去の資産も何もない状態なら、LuaTeXはとても良い選択肢だろう。

 

 

 ただ、幾つかの致命的注意点が存在する。

 まず、致命的にクソ遅い。pTeXなら、ペペペっと終わる処理が、LuaTeXだとぺーーーぺーーー(固まる)ぺーーーーって感じだ。たぶん、4,5倍は遅い。気分的には10倍以上遅い。精神衛生を侵し、SAN値を削ってくる。命に関わるといってよい

 最終的なドキュメントはLuaLaTeXで処理して、普段はフォント設定無視して、pLaTeXで結果を見るのが良いと思う。むろん、時々ちゃんとLuaLaTeXで処理して、確認しないと後で痛い目を見そうだが。普段からずっとLuaTeXを使う場合でも、フォント設定は切っておいた方がいい。少しは速くなる。

 そのため、提供するマクロにLuaを使わない方がいい。Luaを使うと、デバッグがしんどいマクロ作成が非常に楽になるのだが、私はSAN値が削れることに耐えられない。チマチマLaTeXに修正中である。

 

 次に、致命的にメモリを食う。とりあえず、500pぐらいのドキュメントを自動生成したのだが、余裕で2Gbのメモリを消費した。1000pぐらいになったら、そもそも32bitでメモリ足りるのか疑問である。

 最後に、仕様が安定していない。私はTeXLive2018を使っているが、TeXLive2019ではLuaのバージョンが5.2から5.3になるらしい。このとき、Luaの後方互換は破壊的であるそうだ。事故る未来が見える。

 さらに、同じTeXLive2018でも、ISO版(2018年4月版)と、常に最新版が入るインターネットインストーラ版で挙動が異なる。特に、何故かフォントに游明朝の太字(YuMincho Demibold)を使うと、インターネットインストーラ版ではメモリエラーが発生する。これはW32TeXでもだった。年間を通して、エンジンは同じらしいので、パッケージのバグだとは思う。

 従って、LuaTeXを使う場合、私から言えるアドバイスは以下である。

  • インストール日付に依存せず、全員同じ環境になるインストールの仕組みを定義せよ。TeXLive2018ならISO版を使うことを推奨する。(2019以上の場合は知らない)
  • アップデートは検証を行った後で、許可をせよ
  • TeXLiveは32bitしかインストールされないので、64bitエンジンのインストール手段を定義せよ。メモリ足りなくなってから泣いても知らん
  • PCのメモリは8Gbじゃ足りない
  • 提供するマクロはLuaに依存しない方がいい

 

 

クラスファイルを定義しよう

 ドキュメントの見た目を定義するのがクラスファイルである。\documentclass{jsarticle}とか書いてる所のjsarticleの部分だ。

 余白やフォント、ヘッダーフッター、行間、chapter、sectionの文字などを設定するのだが、全て0から定義するのは大変すぎる。無理せず\LoadClassで提供されている既存のクラスファイルを読み込み、必要に応じてハックしたり、設定を書き換えるのが良い。

 なお、間違ってもjsarticle.clsなどの実際のファイルを書き換えるべきではない。ライセンス的にどうなんだという話と、個人の環境に依存してしまうのが怖い。必ず、\LoadClassで読み込んでから必要な関数を上書きしたりしよう。

 大体次の内容を作れば十分だろう。

  • 表紙
  • 目次ページのカスタマイズ
  • ヘッダー、フッター
  • Confidentialのオプション
  • フォント
  • chapterやsectioの表現
  • 表、図、式のcaptionでの表現(図:1-1とか)
  • 表、図、式と文字の行間
  • item環境の●や■の文字
  • enumerate環境の連番表示の仕方
  • easylistの調整
  • listingsの設定(フレームやフォントなど)

 

 

ところで、Windowsフォントについて

 ガイドにWindowsのフォントが指定されていて、私は驚愕した。PDFにする以上、普通はフォントを埋め込む。それにWindowsフォントを使うのか!?と。ライセンスは大丈夫なのかと。

 しかし、それは私が学生だった10ね………数年前の話であり、今は状況が違っている。2017年にWindows標準フォントについて、ついにMicrosoftが沈黙を破り、その指標を公開していた

 読んでみた限り、PDFを暗号化し、編集不可能にしておけば、Windows標準フォントに関しては、Windows上でPDFを作成する限り、PDFに埋め込んで良いと思われる。(必ず原文を当たり自分で確認すること)

 ※Wordに付属して付いてくるフォントは標準フォントではない。HG●●フォントとか。

 

スタイルファイルを定義しよう

 クラスファイルを作ったら、次はスタイルファイルの定義だ。

 どんなマクロが必要かはチームによって違うだろうが、少なくとも以下は用意したい。

 

  • 図を読み込むマクロ(コマンド一つで表示できるように)
  • 複数の図を読み込むマクロ(図1(a)みたいに参照できるように)
  • キャプションを引数として受け取る表環境(上にキャプションを付けるのか、下に付けるのか固定)
  • 表、図、式へのref(図1、表1、式(1)みたいに接頭辞が必要ならそれも強制的に付けてしまえ)
  • inputやsubfile、includegraphicsなどのファイル読み込みを、現在のファイルからの相対パスで読めるコマンドで出来るコマンドの用意。

 

 特に、複数人でファイルを分割して作業する場合、input系のマクロは事故る未来しか見えないので、必ず現在のファイルからの相対パス化したい。\currfiledirを引数の前に付加するだけで良い。

 これらを準備した上で、各チームごとに必要なマクロを定義しよう。例えば、ペリフェラルのレジスタを参照するマクロとかを私は用意した。

 個人的には、PAD図をTikzで簡単にかける様にしたい。けど、Tikz全然分からない。  

 

ファイルの分割について

 inputを使うよりもsubfileを使った方がいい。subfileで分割すると、個別にコンパイルが出来るので、全部をコンパイルすると死ぬ程遅いLuaTeXでも、致命傷程度の速度でコンパイル出来る。

 そのために、プリアンブルだけ定義したtexファイルは一番最初に用意しておこう。

 

 

RedPen

 今時、Lintもなしにドキュメントを書くなんて考えられない。なお、このブログは特にLintは使っていない。(オイ

 Wordなら、Wordが色々とウザいお節介を焼いてくれるが、TeXだと、色々お膳立てしなければならない。

 Markdownで使ってるTextLintLaTeX拡張があるが、どうも使いにくかった。調べてみたら、RedPenがデフォルトでLaTeXに対応しているので、これを導入することにした。

 ただ、このRedPenは実行にJavaが必要なのが悩み所。一般人はジャバってなーにと言うレベルだ。特に今は無償で公式のJREのみのインストーラが無くなったのだから、余計に説明が面倒だ。まぁ、TextLintだってnode.jsという謎の危険が危ないナニカが必要なので、大差はない気もするが…

 RedPenにはサーバー機能があるので、社内サーバーを用意するか、ファイルサイズがでかくても、exe化した方がいい。通信して外に出しても良いというなら、公式のRedPenサーバーReadmeに従って、Herokuを使ってみてもいいだろう。どうするかは、運用次第だが、とにかく、Javaは一般人にとって、百害あって一利無しである。

 また、サーバーにする場合は、コマンド一つでサーバーに対してリクエストを発行し、返ってくるJSONを解析してエラー箇所を表示する仕組みを作る必要がある。 全員がEmacsかAtomを使ってくれるなら、プラグインで解決出来るが、TeXWorksだと、どうしてもそういう準備は必要だ。

 

終わりに

 うーわ、久しぶりのブログ長っ。1万文字だとよ。

 というわけで、いつもの発作でまた余計な仕事を抱えた私が、やってみたことをまとめてみた。ドキュメントを自動生成したいけど金がない、システムに金かけるのは無駄、という人々の手掛かりになれば幸いである。そして、いずれはドキュメントの自動化に金を払ってもらう足がかりにしよう。

 作ったんだから、死ぬまでオメーが管理しろ、というのであれば…………辞めるしかないかな。

簡単なHTMLの表をXMLに変換する方法

 どーも。

 Markdownとかorg-modeとかで表で書いてた情報と、同じ情報をXMLで書かなきゃいけなくなったという場面に遭遇しました。

 これらはGitBucketで表示していたので、要するにHTMLの表→XMLへの変換をしたい。

 かといって、今回の為だけにプログラム書くのも面倒くさいし、既存の物で何とかしてみよう。

前提条件

 必ず、ExcelかLibreOffice CalcもしくはGoogle SpreadSheetが使える環境であること。インターネットに接続出来ること。

 そして、生成するXMLは以下の様にシンプルな構造であること。

<全体>
 <行>
  <列1>中身 </列1>
  <列2>中身 </列2>
 </行>
 <行>
  <列1>中身 </列1>
  <列2>中身 </列2>
 </行>
</全体>

 

 データはネスト要素を持たない。

<行>
  <列1>
    <struct> <!-- 列が更に構造を持つのはちょっと辛い -->
    </struct>
  </列1>
</行>

 不可能ではないけど、表の段階で何処まできちんと分離してあるかによるかな。

関数名 引数 説明
hoge uint32_t moge ほげる
piyo const string& puyo ぴよる
taro たろる

<functions>
 <function>
  <name>hoge </name>
  <arguments>uint32_t moge </arguments>
  <descriptions>ほげる </descriptions>
 </function>
 <function>
  <!-- ブログ上、以下略 -->
 </function>
</functions>

手順

 手順は以下です。

  1. 表をExcel等に貼り付ける
  2. 1行目をタグの名前に変更する
  3. CSVとして保存する
  4. CSV to XML ConverterでXMLに変換

スプレッドシートに貼り付けてCSV化

 ブラウザ上で表をコピーすると、そのままExcelやLibreOffice CalcGoogle SpreadSheetに貼り付けることが出来ます。というか、Excelなんて持ってないので、わざわざLibreOffice入れたけど、Google SpreadSheetでも出来たことに驚いた。オフィス製品いらないねぇ…

f:id:nodamushi:20190126141518p:plain
表を選択してコピー
f:id:nodamushi:20190126131207p:plain:w240
LibreOffice Calcでペースト
f:id:nodamushi:20190126131235p:plain:w240
Google SpreadSheetでペースト

 1行目を必要なタグ名に変更する。

f:id:nodamushi:20190126131816p:plain:w320
name,arguments,descriptionに変更した

 全体を見て、ペースト結果が結合されているなどおかしい場所がなければ、CSVとして保存する。 f:id:nodamushi:20190126132114p:plain:w320

 LibreOfficeで保存する場合は、途中でフォーマットについて聞かれるので、UTF8で保存しましょう。Excelは知らない。 f:id:nodamushi:20190126134704p:plain:w320  

CSV to XML ConverterでXMLに変換

 出来たCSVをCSV to XML ConverterでXMLに変換します。

 先ほど作ったCSVファイルを入力しましょう。このサイトでXMLに変換しても、情報などはサーバー上には転送されないので安心して下さい。

f:id:nodamushi:20190126135438p:plain

 

  行を「function」で、全体を「functions」で囲いたいので、Step4を開いて、テンプレートをカスタムします。

 ちょっとしたネスト構造なら、このテンプレートを操作することで作ることも可能。なお、テンプレート中の{h1},{f2}などの1,2は列番号です。hはヘッダだと思うケド、fって何の略だろ?

f:id:nodamushi:20190126140004p:plain

 

 「Convert CSV to XML via Template」を押すとテンプレートを使って変換してくれます。

f:id:nodamushi:20190126140123p:plain

 

 後はエディタにペーストするなり、ダウンロードするなり、好きにするがよろし。

 

 

要素数が少ないなら

 ちなみに、要素数が3個、行数も10ぐらいしかなかったので、実際は私はこういう方法で解決しました。

 スプレッドシートに貼り付け、1行目を削除し、各行の間に空白の列を入れる。

f:id:nodamushi:20190126132515p:plain

 タグで挟む f:id:nodamushi:20190126132716p:plain

 左下の端をドラッグして縦に複製 f:id:nodamushi:20190126132832p:plain

 全体をコピってメモ帳に貼り付け。functionsで囲う。 f:id:nodamushi:20190126133015p:plain

 

 ただ、出来上がるXMLとやり方があまりに雑なので、もうちょっと上手なやり方ないかなと、模索してみたのがこの記事の内容です。

もっともシンプルなmalloc,freeの実装と理解

mallocはOSからメモリを動的に確保する?

 mallocをするとOSからメモリを確保出来る。

 mallocで確保したメモリはfreeでOSに返される。

 一体どこの誰だ、こんな嘘の解説を世に出したのはぁ!

 こんな説明がまかり通っているから、初心者の脳内メモリイメージが何だかよく分からない、お花畑な状態になってしまうのだ。

 なんかOSっていうスゲーのが何かしてるらしい。よくわからないけど、なんか駄目って言われてるから2回解放したら駄目らしい。使ったら解放しないと駄目って言うから解放したけど、何か動かない。なんか駄目って言われたから、やっちゃ駄目なことは分かるけど、逆に何をしても良いのか実はわかってない。

 どうも、こんな感じのイメージになっているっぽい。

 

 同じ嘘をつくなら、mallocはOSからメモリを確保しない、freeはOSにメモリを返さないと説明した方がまだマシである。

 なぜなら、mallocはOSから動的にメモリを確保する場合もあるが、動的にメモリを確保しない場合もある。そして後者の方が、理解に圧倒的に重要な項目だからだ。

 

 この記事では、OSから一切動的にメモリを確保しない極シンプルなmalloc、freeを作る。なんだかmalloc,freeがよく分からない、と思っている初心者の方々の理解の手助けになれば良いと思う。

 とにかく簡単な物が、どう動くのかを理解することが、一番手っ取り早い理解だ。

 ※本格的なmallocを実装する為の記事ではありません。

 

 

OSからメモリを動的に確保しないmallocを作ろう

 mallocにまつわる技術は中々に奥深い物があるのだが、そんな物を解説したって混乱するだけである。とにかく、ヒープだのなんだのを全て排除した、簡単で、機能としては十分なmallocとfreeを作ることを目標としよう。

 まず、一番簡単でかつ、仕様を満たすmallocとfreeの実装は以下だ。

int* my_malloc(int size)
{
  return 0;
}

void my_free(int* ptr)
{
}

 今回はわかりやすさを最優先する為に、全てをintの単位で管理する。sizeも何int分かを表す。voidポインタなんてまだキミ達には早すぎる。アライメントとか気にしなくていい。

 ただ、実際のmallocのsizeと今回のmy_mallocのsizeは単位が違うことだけは認識しておいて欲しい。

 さて、これは完全で一切バグのないmalloc、freeだ。決して問題を起こすことはない。そして、このmalloc、freeにOSは一切絡んでいないことは納得して頂けるだろう。(OSどころか、処理すらないのだから!)

 とはいえ、10という入力が来たら、長さ10のメモリのアドレスを返さなければ、流石に実用に耐えない。追加の実装が必要だ。

( ・Д・)「あ、そこでOSからメモリを持ってく…………

 持ってきません。この記事は最後までOS絡みません。

 

グローバル変数でメモリを管理

 さて、OSからメモリを取ってこないと言っても、どこかにメモリは必要。ということで、以下の様にグローバル変数memoryを用意しよう。

int memory[20];

 10個分のメモリが欲しいと言われたら、このmemoryから10個分返してあげれば、別にOSに頼らなくてもメモリを10個動的に確保したのと同じだ。

 というわけで、memoryを返してあげよう。

int memory[20];
int* my_malloc(int size)
{
  return memory;
}
void my_free(int* ptr)
{
}

 

 完璧である。さぁ、実際に使ってみよう。

 

int main(int argc, char *argv[])
{
  int *a = my_malloc(10);
  int *b = my_malloc(10);

  a[0] = 1;    a[1] = 2;
  b[0] = 10;   b[1] = 20;

  printf("a[0]=%d, a[1]=%d\nb[0]=%d, b[1]=%d\n", a[0], a[1], b[0], b[1] );
  my_free(a);  my_free(b);
  return 0;
}

 

 まぁ、言うまでもなく、これの結果は以下の様になる。

a[0]=10, a[1]=20
b[0]=10, b[1]=20

 問題は1回目のmy_mallocも2回目も、同じポインタを返しているから、同じ場所を書き換えてしまうのだ。

a,bのアドレスと範囲

使用した場所を記録する

 毎回違う場所を返せる様にするには、いったい何処が使われているか記録しておかなければならない。

 これをどのような理論、データ構造で実装するかが、mallocとfreeの最重要な部分である。が、今回はわかりやすさが何よりも優先である。

 グローバル変数usedを用意し、そこに使っているか使っていないかの状態を保存しよう。usedの値が0なら未使用、1なら使用中である。

 size分の領域が必要な場合、0番目から1つ1つ、使われているかいないかを確認し、使われていなければ、使用中(1)をマークして返す。

int memory[20];
int used[20]={0};

int* my_malloc(int size)
{
  if ( size <= 0 || size > 20 ) return 0;
  
  for (int i = 0; i < 20 - (size - 1); i++){
    if ( !used[i] ) {
      //size分の領域が連続して空かを確認してから返す
      int not_used = 1;

      for (int k = 0; k < size; k++)
        if ( used[i + k] ) not_used = 0;

      if ( not_used ) {
        // 空だった場合は
        // used[i] ~ used[i + size - 1] を全て1にして
        // &memory[i] を返す
        for (int k = 0; k < size; k++)  used[i + k] = 1;
        return memory + i;
      }
    }
  }
  return 0;
}

 (continue, break, gotoを使う誘惑に駆られたが、わかりやすさ優先の為に我慢した)

 これで、先ほどのmainを実行してみると………

a[0]=1,  a[1]=2
b[0]=10, b[1]=20

 エクセレンッ!完璧である。

usedのお陰で、aとbが違う場所に割り当てられた

 と、言いたいところだが、まだ問題がある。freeが実装されていない。

 このままだと、以下のコードでcの確保に失敗してしまう。

int main(int argc, char *argv[])
{
  int *a = my_malloc(10),*b = my_malloc(10);

  my_free(a); // aを解放
  int *c = my_malloc(10);// 0(NULL)が返ってしまう

  return 0;
}

 cを確保する前に同じサイズのaをfreeしているので、cは確保出来て当然だろう。

 というわけで、freeを実装したいのだが………

void my_free(int* ptr)
{
  if (!ptr) return;

  int index = ptr - memory; // ポインタの引き算
  for(int i = 0; i <    /* ????? */   ;i++)
    used[index + i] = 0; // 未使用に
}

 ちょっと待って、何メモリ分を解放すれば良いのか分からない。for文の条件式が作れない。

 Note: ポインタの引き算が分からない人へ

  ptr = &memory[index] とした場合、&memory[index] - memoryを計算すると、indexが取得できる、と言う意味。ポインタも頑張って勉強しよう!

使用量を保存し、freeを実装

 どうやらusedをクリアする為には、ptrがどれだけの大きさのブロックなのかを保存する必要もあるらしい。

 usedに使用サイズを保存しよう。

int memory[20];
int used[20]={0};

int* my_malloc(int size)
{
  if ( size <= 0 || size > 20 ) return 0;
  
  for (int i = 0; i < 20 - (size - 1); i++) {
    if ( !used[i] ) {
      int not_used = 1;

      for (int k = 0; k < size; k++)
        if ( used[i + k] ) not_used = 0;

      if ( not_used ) {
        // usedに終わりまでの距離を保存してから、&memory[i]を返す
        // ※used[i] = size, used[i + 1] = size - 1, used[i + 2] = size - 2, ....を保存する
        for (int k = 0; k < size; k++) used[i + k] = size - k;
        return memory + i;
      }
    }
  }
  return 0;
}

void my_free(int* ptr)
{
  if (!ptr) return;
  
  int index = ptr - memory;
  int size  = used[index];
  for (int i = 0; i < size ; i++)
    used[i + index] = 0;
}

 これでmallocとfreeの実装は完成だ。  

 さて、実際にサンプルコードを動かしてみよう。

int main(int argc, char *argv[])
{
  int *a = my_malloc(10),*b = my_malloc(10);
  printf("a address=%d, b address = %d\n",a,b);

  my_free(a); // aを解放
  int *c = my_malloc(10);//aの領域と同じところが確保される
  int *d = my_malloc(10);//足りないので0

  printf("c address=%d, d address = %d\n",c,d);

  return 0;
}

 結果は下の様になり、完璧な動作をしている。

a address=4225504, b address = 4225544
c address=4225504, d address = 0

c,bがmemoryに割り当てられている

 

malloc , freeを理解する

 さて、どうだっただろうか?実に簡単だったと思う。

 無論、今回作ったmalloc,freeは実用には全く耐えない。(usedの為に2倍のメモリを使うのだから目も当てられない。その上、ブログ記事の為に、わざとすぐに問題を起こせる様にしてある。)

 しかし、メモリの確保、解放がなんなのかを理解するに十分である。

 ここまで見てきた様に、メモリを動的に割り当てる、解放する行為にOSは一切関係ない

 OSからメモリを確保する、返すというのは、次の段階の話だ。キミ達が先ず理解しなくてはならないのは、OSレベルの話ではなく、アプリケーションレイヤーでの話なのだ。

 例えば、メモリ確保直後の値はどうなっているのだろう?初期化されているだろうか?

 否である。mallocやfree関数内でmemory配列に対して一切の操作はしていない。誰かが使った後だと、使いっぱなしで、次に渡される。

 

 例えば、NULL(0)の解放をするとどうなるだろう?問題が何か起こるだろうか?

my_free(0);
my_free(NULL);

 これは、実は何も問題を起こさない。なぜなら、1行目で即座にreturnしているからだ。 別に私が気を利かしたのではなく、freeがそういう定義だからである。

void my_free(int* ptr)
{
  if (!ptr) return;

 

 

 じゃぁ、0が大丈夫なら1は?

my_free(1);

void my_free(int* ptr)//ptr = 1
{
  if (!ptr) return;

  int index = ptr - memory; // ここが負になる
  int size  = used[index]; // 負のインデックスでアクセス
  for(int i = 0; i < size; i++)
    used[ i + index ] = 0;  // used [ 0 + 負 = 負 ]  = 0;という処理になる
}

 実装に依存するが、今回の実装では明らかに問題が起こる。つまり、やってはならないと分かるだろう。

 なら、次はmallocで確保した領域を途中から開放した場合はどうなるだろう。

int *a = my_malloc(10);
my_free(a + 5);
int *b = my_malloc(10);
my_free(a);
int *c = my_malloc(10);

 今回作ったmallocはとても簡単だ。動きをゆっくり追ってみて欲しい。(そのために、役に立たない簡単なmallocを作ったのだから。)

  1. aが0番目に割り当てられる
  2. my_free(a+5) を解放しようとする。used[5]=5なので、5,6,7,8,9を未使用に変更する
  3. 5~14が未使用なのでbが5番目に割り当てられる
  4. my_free(a)でused[0]=10なので、0~9を未使用に変更する
  5. 0~9が未使用なのでcが0番目に割り当てられる

 最終的には以下の図の様になる。酷い有様だ。

b,cが中途半端に重なる

 途中から解放したり、mallocで取得したアドレスをなくしたら困ると分かるだろう。

 

 では、二回解放をするとどうなるだろう。今回の実装では、直ちに影響はない。だが、暫くすると影響がある。

int *a = my_malloc(5);
int *b = my_malloc(5);
my_free(b);
int *c = my_malloc(10);

my_free(a);
my_free(a);// 今回は連続して解放しても特に問題は起こさない

my_free(b);// cを解放してしまう
int *d = my_malloc(10);

 最終的にこれは以下の図の様なメモリ配置になる。やっぱり酷い有様だ。この上cを開放しようものならdが……となり、収集がつかない。

bの2回解放の所為で、c,dが重なっている

 小さなメモリを何度も取るとどうなるだろう。

int *a = my_malloc(5);//残り容量 15
int *b = my_malloc(5);//残り容量 10
int *c = my_malloc(5);//残り容量 5
my_free(b);//残り容量 10

int *d = my_malloc(10); // 失敗

 残り容量は10なのに、dの確保に失敗する。これは空き容量の断片化が起こるからだ。

容量は足りていてもdを確保出来ない

 

 gccとかのmallocは、私が作ったのに比べずっと賢いが、基本的にはmalloc,freeは頻繁に行わず、大きな単位で行った方が良い。

 実行環境にも依存するが、小さいオブジェクトなら、普通にスタックに置けば良い(ローカル変数で定義すれば良い)。SPの移動でメモリの解放がされるので0コストだ。(組み込みだとSRAMが小さくて、すぐパンクする可能性があるので気をつけたし)

 

 

まとめ

 

 さて、malloc,freeの大事なところを理解するのに、OSからメモリを確保とか糞みたいなどうでも良いことだと理解して貰えただろうか。

 mallocをするとOSからメモリを確保出来る………。確かにその通りではある。

 だが、その話を鵜呑みにして、OSからメモリがご降臨されて、OSにメモリがご帰還なさっていく様なふんわりしたイメージをいつまでも持っていると、大事なことが理解出来ない。

 メモリの確保が行われるのは、mallocが管理しているリソースが足りなくなった時だ。常にOSからメモリを確保している訳ではない。

 mallocはOSからメモリを確保しないし、freeはOSにメモリを解放しないと覚えていた方がまだマシだ。

 今日から「mallocとfreeはメモリを確保、解放しない」と念仏の様に唱えてC言語を捨て、RustやC++を完全に理解しよう。

 

参考文献:glibcのmalloc実装

 実際のmallocがどういう実装になっているのかに興味が湧いたら、以下の記事を読んでみると良いでしょう

Glibc malloc internal

あなたのメモリはどこから来てる?malloc入門 - Qiita

 あと、今回は説明事項から省いたsizeofに関する話など。

sizeof演算子にまつわるアレコレ - Qiita

侍エンジニア塾のC言語のサンプルがヤバすぎる。 - Qiita

Cの本

 楽してとか、わかる、とか、そういうタイトルで良い本だったことはない

(古いのであまりお勧めはしないけど)

コンピューターの起源:ジャカード織機の仕組み

 富岡製糸場然り、工業の近代化において、衣服にまつわる技術は多く世に貢献してきた。

 さて、私たちが今扱っているコンピュータも元を辿っていくと、実はジャカード織機という織機に辿り着く。

 いわゆるパンチカードによるプログラミングの原型とも言えるべき機械で、パンチカードの穴の並びで布の縦糸をどう動かすかを決めることが出来た。これによって、一気に複雑な模様を短時間で作れる様になったらしい。

 

 で、そうは言われても、じゃぁ、どういう風に動くのか日本語で調べたりしてもよく分かってなかったんだけど、偶然素晴らしい動画を見つけて、よーく理解出来た。

www.youtube.com

 こういうデカい機械が、がっしょんがっしょん動くのは、いつになっても浪漫がある。

 

 さて、動画に全てを任せても良いけど、簡単に解説。

 まず、一般には縦糸と横糸を交互に上下に交差させることで布のを編む。これが平織。

f:id:nodamushi:20181209212951p:plain:w320
平織り

 これを、交互ではなくすると、模様になる。図では隙間のせいで模様に見えないけど、布にする時には隙間は詰めるので、交点の部分しか表面からは見えない。

f:id:nodamushi:20181209213134p:plain:w320
ジャカード織

 

   ジャカード織機はパンチカードの情報から、自動的にこのパターンを作り出す。

 パンチカードが無い、もしくはパンチカードの穴が開いたところでは、縦糸を動かす針が上下に動く装置に引っかかり、同じように上下に動く。(縦糸が横糸の上になる)

f:id:nodamushi:20181209211803p:plain:w320
パンチカードが無い状態

 一方で、パンチカードがあって、穴がない場合は、押し込まれ、引っかからなくなり、上下に動作しなくなる。(縦糸が横糸よりも下になる)

f:id:nodamushi:20181209213705p:plain:w320
パンチカードがある状態

   こうやって全ての縦糸を一本ずつ操作し、縦糸と横糸の上下を組み合わせて目的の模様を自動的に作り出す。すごいね。

 ていうか、模様の図からパンチカードに変換する職人もスゴイと思うわ。