MSVCでもCPPでFizzBuzzしてみた

  • 2
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

前回の記事でC言語のプリプロセッサでFizzBuzzする方法を書いたのですが、GCCとかでしか動かなくてちょっとアレだなぁ、と思ったので がんばって MSVCとかTCCとかでも動くようにしてみました。
がんばって ってところがポイントです。がんばったんです!

で、どんなコードになったの?

こんなコードになりました。

fizzbuzz.c
#include "counter.h"

#ifndef FIZZBUZZ_MAX
#  define FIZZBUZZ_MAX 100
#endif

#if   COUNTER % 15 == 0
FizzBuzz
#elif COUNTER % 3  == 0
Fizz
#elif COUNTER % 5  == 0
Buzz
#else
COUNTER
#endif

#if COUNTER < FIZZBUZZ_MAX
#  include __FILE__
#endif

このコード自体は前回の__INCLUDE_LEVEL__を使ったものとそう変わらないような気がします。
しかし、このコードでは__INCLUDE_LEVEL__の代わりにCOUNTERというマクロが使われています。

このCOUNTERというマクロはもちろん、コンパイラによって組み込まれたものではなく、僕が定義したものです。そして、その定義はcounter.hの中にあります。
このcounter.hに秘密がありそうですね。

counter.h。あるいはCPPで状態を扱うということ

前回の記事で「CPPでは変数のようにマクロの更新ができない」と書きました。
それはつまり、

#define COUNTER 0

というマクロがあったとして、

#define COUNTER (COUNTER + 1)

のようにもう一度定義しなおしても、展開結果は‘(COUNTER + 1)‘のようになってしまう、ということです。
しかも、仮に(0 + 1)と展開されたとしても、CPPではそこからさらに計算を行なって1という数値にしてくれるわけではないので、とてもFizzBuzzの出力には使えそうにありません。

前回の記事ではタウンページよろしく「そんなときに__INCLUDE_LEVEL__が便利なんですよ」と言ったわけなのですが、今回は__INCLUDE_LEVEL__の無い環境でも動くようにしなければいけないので、使うことはできません。

そこでどのようにするのかと言うと‥‥。

「御託はいいからさっさとコードを見せろ!」

はい。

いやまあ、言う程すごいことはしてないんですよ。
強いて言えばちょっとがんばっただけです。

それではcounter.hのコード。

counter.h
#ifndef COUNTER_H_
#define COUNTER_H_

/*    1の位の値 */
#define COUNTER_VALUE_1 0
/*   10の位の値 */
#define COUNTER_VALUE_2 0
/*  100の位の値 */
#define COUNTER_VALUE_3 0
/* 1000の位の値 */
#define COUNTER_VALUE_4 0

#define COUNTER 0

/* 引数として与えられたものを、展開してから結合するマクロ */
#define JOIN1(x) x
#define JOIN2(x, y) JOIN2_(x, y)
#define JOIN2_(x, y) x ## y
#define JOIN3(x, y, z) JOIN3_(x, y, z)
#define JOIN3_(x, y, z) x ## y ## z
#define JOIN4(x, y, z, w) JOIN4_(x, y, z, w)
#define JOIN4_(x, y, z, w) x ## y ## z ## w

#endif /* COUNTER_H_ */

/*  1の位から処理 */
#if   COUNTER_VALUE_1 == 0
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 1
#elif COUNTER_VALUE_1 == 1
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 2
#elif COUNTER_VALUE_1 == 2
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 3
#elif COUNTER_VALUE_1 == 3
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 4
#elif COUNTER_VALUE_1 == 4
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 5
#elif COUNTER_VALUE_1 == 5
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 6
#elif COUNTER_VALUE_1 == 6
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 7
#elif COUNTER_VALUE_1 == 7
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 8
#elif COUNTER_VALUE_1 == 8
#  undef COUNTER_VALUE_1
#  define COUNTER_VALUE_1 9
#elif COUNTER_VALUE_1 == 9
#  undef COUNTER_VALUE_1
/* 10位に繰り上がる場合 */
#  define COUNTER_VALUE_1 0
#  if   COUNTER_VALUE_2 == 0
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 1
#  elif COUNTER_VALUE_2 == 1
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 2
#  elif COUNTER_VALUE_2 == 2
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 3
#  elif COUNTER_VALUE_2 == 3
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 4
#  elif COUNTER_VALUE_2 == 4
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 5
#  elif COUNTER_VALUE_2 == 5
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 6
#  elif COUNTER_VALUE_2 == 6
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 7
#  elif COUNTER_VALUE_2 == 7
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 8
#  elif COUNTER_VALUE_2 == 8
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 9
#  elif COUNTER_VALUE_2 == 9
#    undef COUNTER_VALUE_2
#    define COUNTER_VALUE_2 0
/* 100の位に繰り上がる場合 */
#    if   COUNTER_VALUE_3 == 0
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 1
#    elif COUNTER_VALUE_3 == 1
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 2
#    elif COUNTER_VALUE_3 == 2
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 3
#    elif COUNTER_VALUE_3 == 3
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 4
#    elif COUNTER_VALUE_3 == 4
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 5
#    elif COUNTER_VALUE_3 == 5
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 6
#    elif COUNTER_VALUE_3 == 6
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 7
#    elif COUNTER_VALUE_3 == 7
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 8
#    elif COUNTER_VALUE_3 == 8
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 9
#    elif COUNTER_VALUE_3 == 9
#      undef COUNTER_VALUE_3
#      define COUNTER_VALUE_3 0
/* さすがに1000の位まで対応しなくていいよね? */
#      define COUNTER_VALUE_4 1
#    endif
#  endif
#endif

/* 桁の数に合わせて結合するようにする */
#if   COUNTER_VALUE_2 == 0 && COUNTER_VALUE_3 == 0 && COUNTER_VALUE_4 == 0
#  undef COUNTER
#  define COUNTER JOIN1(COUNTER_VALUE_1)
#elif COUNTER_VALUE_3 == 0 && COUNTER_VALUE_4 == 0
#  undef COUNTER
#  define COUNTER JOIN2(COUNTER_VALUE_2, COUNTER_VALUE_1)
#elif COUNTER_VALUE_4 == 0
#  undef COUNTER
#  define COUNTER JOIN3(COUNTER_VALUE_3, COUNTER_VALUE_2, COUNTER_VALUE_1)
#else
#  undef COUNTER
#  define COUNTER JOIN4(COUNTER_VALUE_4, COUNTER_VALUE_3, COUNTER_VALUE_2, COUNTER_VALUE_1)
#endif

長いです。
しかも、長いわりに中身がないという、プログラマの三大美徳にひっかかりそうものになっています。

読めば分かりますが、展開結果にCOUNTERが出てこないようにするためと計算の無いCPPのために、10進数に数値を分解してインクリメントを自力で実装しています。
状態を表現するのが難しいCPPとはいえ、何だかとても残念なコードなのでした。

MSVCでの実行

Visual Studio立ち上げるの面倒だったので、clコマンドの使えるプロンプトを起動して、fizzbuzz.ccounter.hの保存されたディレクトリに移動して、

$ cl /EP fizzbuzz.c | grep -v -e "#" -v -e "^$"

でFizzBuzzが見れます。ちなみにMSVCでは#includeの深さが1024までいけるっぽいので、

$ cl /EP /DFIZZBUZZ_MAX=1000 fizzbuzz.c | grep -v -e "#" -v -e "^$"

のようにして、1000までFizzBuzzできます。

MSVCたんいじめるの楽しい! ✌(’ω’✌ )三✌(’ω’)✌三( ✌’ω’)✌

また、TCCでは再帰のレベルが32までしかサポートされてないっぽいので、

$ tcc -E -w -DFIZZBUZZ_MAX=30 fizzbuzz.c | grep -v -e "#" -v -e "^ $" -v -e "^$"

とするといいかと思います。

最後に

コンパイラいじめダメゼッタイ