株式会社クリアコード > ククログ

ククログ


ノータブルコード11 - 空になるかもしれないCのマクロの値を正規化

最近、GNU/Linux上のGCCとVisual C++のビルドでは同じ挙動なのにMinGWのビルドでだけ挙動が異なる件を調べていた須藤です。MinGWが提供するヘッダーファイルを見ていたら「お!」と思うコードがあったので11回目のノータブルコードとして紹介します。

MinGWはGCCでVisual C++でビルドしたようなバイナリー(Windowsが提供するランタイムで動くバイナリー)を出力するためにいろいろ頑張っています。標準入出力まわりもその1つです。ただ、Windowsが提供する標準入出力機能はANSI Cで定義されている標準入力機能と仕様が異なることがあります。たとえば、snprintf()の第二引数の挙動が違います。Windowsでは第二引数は書き込める最大文字数(終端の\0を含まない)ですが、ANSI Cでは終端の\0を含んだバイト数です。(ANSI Cの仕様はこれであってる?)

たとえば、次のプログラムで違いが出ます。

#include <stdio.h>

int
main(void)
{
  char buffer[5];
  snprintf(buffer, 5, "hello");
  printf("%.*s\n", 5, buffer);
  return 0;
}

Windows上では次の結果になります。

hello

Debian GNU/Linux上では次の結果になります。

hell

この例は実は今回の話とはまったく関係ない(!)のですが、MinGWには__USE_MINGW_ANSI_STDIOというマクロで標準入出力機能の実装を切り替えることができます。__USE_MINGW_ANSI_STDIOというマクロをユーザーが指定することもあるので正規化していました。次のコードです。

/* We are defining __USE_MINGW_ANSI_STDIO as 0 or 1 */
#if !defined(__USE_MINGW_ANSI_STDIO)
#define __USE_MINGW_ANSI_STDIO 0      /* was not defined so it should be 0 */
#elif (__USE_MINGW_ANSI_STDIO + 0) != 0 || (1 - __USE_MINGW_ANSI_STDIO - 1) == 2
#define __USE_MINGW_ANSI_STDIO 1      /* was defined as nonzero or empty so it should be 1 */
#else
#define __USE_MINGW_ANSI_STDIO 0      /* was defined as (int)zero and non-empty so it should be 0 */
#endif

ここで私が「お!」と思ったのは次の部分です。

#elif (__USE_MINGW_ANSI_STDIO + 0) != 0 || (1 - __USE_MINGW_ANSI_STDIO - 1) == 2

これは、__USE_MINGW_ANSI_STDIOとして0以外の値(空の値も含む)を指定されたら真になります。(数値以外の値が指定されていたらエラーです。)

たとえば、0が指定されていれば次のようになって偽になります。

#elif (0 + 0) != 0 || (1 - 0 - 1) == 2

#elif 0 != 0 || 0 == 2

たとえば、1が指定されていれば次のようになって真になります。

#elif (1 + 0) != 0 || (1 - 1 - 1) == 2

#elif 1 != 0 || -1 == 2

たとえば、2が指定されていれば次のようになって真になります。

#elif (2 + 0) != 0 || (1 - 2 - 1) == 2

#elif 2 != 0 || -2 == 2

たとえば、空の値が指定されていれば次のようになって真になります。これがおもしろかったんです!

#elif (+ 0) != 0 || (1 - - 1) == 2

#elif 0 != 0 || (1 - (-1)) == 2

#elif 0 != 0 || 2 == 2

二項演算子に見えた式が単行演算子になって全体として妥当な式になります。Cのマクロはあまり柔軟性がありませんが、こんな風に書くと01に値を正規化できるんだなぁと感心しました。コメントがないとすぐにはピンとこないコードだと思うのでそんなに使う機会はない気がしますが。。。

今回はMinGWのヘッダーファイルにあるコードで「お!」と思った正規化方法を紹介しました。

ところで、そろそろみなさんも自分が「お!」と思ったコードを「ノータブルコード」として紹介してみたくなってきませんか?ということで、このブログへの寄稿も受け付けることにしました。まだ仕組みは整えていないのですが、とりあえず、 https://gitlab.com/clear-code/blog/issues にMarkdownっぽいマークアップで書いた原稿を投稿してもらえばいい感じに調整してここに載せます。寄稿したいという人がたくさんいるならもう少しちゃんとした仕組みを整えようと思っていますので、興味のある人はご連絡ください。寄稿してもらった記事の著作者は作者本人のままですが、ライセンスはCC BY-SA 4.0GFDL(バージョンなし、変更不可部分なし、表表紙テキストなし、裏表紙テキストなし)のデュアルライセンスにしてください。参考:ククログのライセンス

それでは、次のノータブルコードをお楽しみに!

2020-08-06

«前の記事: Groongaのウインドウ関数を使って特定範囲の値を集計する 最新記事 次の記事: Thunderbird 78に未対応のアドオンのThunderbird 78対応の現状と課題について »
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|