2017-09-29

C++のfilesystemライブラリが膨大すぎる

C++17の参考書はほぼ書き終えて、あとはfilesystemライブラリの解説を残すだけになっている。

EzoeRyou/cpp17book: textbook for C++17

ファイルシステムライブラリは、ext4とかbtrfsのようないわゆるファイルシステムに対するライブラリではなく、ディレクトリとファイルに対する操作を提供するライブラリだ。具体的にはファイルパスの文字列処理や、ファイルのコピーやリネーム、ディレクトリ構造のトラバースといった雑多なファイルシステム操作を提供する。

C++のファイルシステムライブラリは、原則としてPOSIX互換になっている。POSIXの規定するライブラリをモダンなC++風のライブラリとして設計したものだ。例えば、カレントディレクトリにあるファイルをすべて表示するプログラムは以下のように書ける。

#include <filesystem>
#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
    using namespace std::filesystem ;

    directory_iterator iter("."), end ;
    std::copy( iter, end, std::ostream_iterator<path>(std::cout, "\n") ) ;
}

C++らしく、ディレクトリー構造をイテレーターでたどることができる。イテレーターなのでアルゴリズムに突っ込むこともできる。

参考書としてファイルシステムライブラリを解説するにあたって、量が膨大すぎるという問題がある。POSIXのファイルシステム操作をカバーするライブラリなのだから膨大になるのは仕方がないが、まともに解説したのでは100ページを確実に超える解説が必要になる。

しかも、絶対パスを得るabsolute(path)とかcopyとかcreate_directoryとかcreate_symlinkとか、自明で雑多な関数が多すぎる。

これらの関数をいちいち解説しても、リファレンスにしかならない。リファレンスならばC++コンパイラーに付属のドキュメントを読めばいいはずだ。

そこで、今書いている参考書では、ファイルシステムライブラリのすべてを解説するのではなく、概要を解説しようと思う。

しかし、概要と言ってもやはり量が膨大で、エラー処理もあるし、なによりクラスpathの初期化だけで、C++が規定している文字列エンコードの話から始めなければならない始末だ。

path::value_typeがどのような文字型をもつのかは未規定で実装に依存する。pathはできるだけポータブルなコードを書けるように、文字コードの変換をサポートしている。


int main()
{
    using namespace std::filesystem ;

    // ネイティブナローエンコード
    path p1( "/dev/null" ) ;
    // ネイティブワイドエンコード
    path p2( L"/dev/null" ) ;
    // UTF-16エンコード
    path p3( u"/dev/null" ) ;
    // UTF-32エンコード
    path p4( U"/dev/null" ) ;
}

C++では、char型がネイティブナローエンコードとUTF-8エンコードの両方の文字型を兼ねているので、char型はネイティブナローエンコードであると解釈される。ネイティブナローエンコードがたまたまUTF-8ならばUTF-8を渡してもいいが、そうではない場合、おそらく動かない。


int main()
{
    using namespace std::filesystem ;

    // ネイティブナローエンコードとして解釈される
    path p(u8"ファイル名") ;
}

このコードは、ネイティブナローエンコードがUTF-8ではない場合、動く保証がない移植性の低いコードだ。

ファイルシステムライブラリは、移植性が高いUTF-8文字列でファイルパスを扱う方法としてu8path(Source)が用意されている。


int main()
{
    using namespace std::filesystem ;

    path p = u8path(u8"ファイル名") ;
}

こういった細かい落とし穴を解説していくだけでページ数がかさむ。早く完成させたい。

2017-09-28

Googleの公開したAbseilライブラリの価値がわからない

先日、GoogleはAbseilというライブラリを公開した。

abseil/abseil-cpp: Abseil Common Libraries (C++)

その中身を見てみたが、どうもその価値がよくわからない。

その大半は、C++14/17風の標準ライブラリのC++11による実装だ。C++14はすでにGCCもClangも実装し終えており、C++17の完全な実装も時間の問題だ。このようなライブラリを使うことはむしろ最新のC++規格の普及を妨げる。

曰く、「Abseilは完全なC++14/17実装では標準ライブラリのtypedefになる」と。しかし、それで問題が解決するのであればさっさとC++コンパイラーのバージョンを挙げたほうがよい。

containerには変わり者がある。inlined_vector<T, N>はstd::stringのSSO(Small String Optimization)をvectorで実装したものだ。N個の要素のストレージはvectorのオブジェクトの中に確保されるので、動的メモリ確保がいらない。N個を越える要素を扱うときには動的にメモリが確保される。注意しなければならいのは、N個を超えた要素を扱うときはvector内部に確保したN個のストレージは無駄になるということだ。

fixed_arrayはクラスオブジェクトに256バイトのストレージを確保し、その範囲に収まる場合はそちらを使うことで動的メモリ確保を回避するクラスのようだ。

我々はC++の最新の規格を実装したC++コンパイラーをいち早く使えるように努力すべきであり、それにはMicrosoft WindowsとRHELを使わないことがまず第一歩となる。

2017-09-25

C++の未定義の挙動で呼ばれないはずの関数が呼ばれる場合

Krister Walfridsson’s blog: Why undefined behavior may call a never-called function

以下のようなコードをClangでコンパイルすると、

#include <cstdlib>

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  return system("rm -rf /");
}

void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}

Clangは以下のような最適化されたコードを吐く。


main:
        movl    $.L.str, %edi
        jmp     system

.L.str:
        .asciz  "rm -rf /"

これは以下のようなコードと同じだ。


#include <cstdlib>

int main() {
    return system("rm -rf /") ;
}

なぜなのか。

もちろん未定義の挙動のためだ。static変数Doは、static変数なのでまず0で初期化される。値が0の関数ポインターを関数呼び出しした場合、挙動は未定義だ。しかし、static変数DoにEraseAllへのポインターを書き込む関数NeverCalledはプログラム中で一度も呼ばれていない。なぜこのコードでEraseAllが呼び出されてしまうのか。もちろん未定義の挙動のためだ。

関数ポインターを経由した関数の呼び出しはコストがかかる。できれば関数ポインター経由ではなく直接呼び出したい。呼び出す関数がわかっているのであればインライン展開もできる。

さて、Clangが変数Doの取りうる値について全プログラムを調べたところ、0と&EraseAllのいずれかであることが判明した。値が0の場合、関数ポインター経由の関数呼び出しの挙動は未定義になる。未定義の挙動はありえないのでその場合は除外してよい。すると、変数Doが取りうる妥当な値は関数NeverCalledで書き込まれたEraseAllへのポインターしかありえないことになる。すると、Do()はEraseAll()と同じだとみなしてよい。これによって呼び出す関数が判明したので、インライン展開もできる。

という理由によって、未定義の挙動を利用した最適化の結果、Clangでは本来呼ばれないはずの関数が呼ばれてしまう。

このような取りうる値について全プログラム中を調べた結果の最適化は、virtual関数呼び出しを可能な文脈では通常の関数呼び出しにするなど、有益な最適化に繋がっている。

教訓としては、未定義の挙動を引き起こさないようにしようということだ。

ちなみに、元記事の筆者は、追加の記事でさらなる最適化の可能性に言及している。

Krister Walfridsson’s blog: Follow-up on “Why undefined behavior may call a never-called function”

最初のコードに以下のコードが追加された場合

static int LsAll() {
  return system("ls /");
}
void NeverCalled2() {
  Do = LsAll;
}

return Do() ;は、以下のように最適化されることが理論上可能だ。


if (Do == LsAll)
  return LsAll();
else
  return EraseAll();

これは条件分岐をしているので一見無意味な最適化のようにみえるが、もし関数のインライン展開ができて条件分岐を上回る最適化になるのであれば、最適化として適切になる。

現在、Clangはこの最適化をしていないが、GCCでvirtual関数呼び出しの最適化を-fdevirtualize-speculativelyオプションを指定して行わせた場合、virtual関数呼び出しについては似たようなコードを吐くので、可能性としてありえなくはない。

2017-09-19

W3Cよさらば! 遂に協力の方途尽く W3C、邪悪なDRMを採択し 我がEFF堂々退場す

World Wide Web Consortium abandons consensus, standardizes DRM with 58.4% support, EFF resigns / Boing Boing

EFFはW3Cが可決したWebにおけるDRM標準規格の取り下げを訴えた。W3Cにおいて可決された規格の取り下げの訴えは初めてのことである。W3Cは会議の結果、メンバーのうちの過半数である58.5%の賛成をもって、過半数によるコンセンサスとしてDRM規格の取り下げを却下した。これはW3Cのメンバーに利用者の自由と権利をないがしろにした不自由陣営が多いためである。

これを受けてEFFは、W3CはもはやオープンなWeb規格を制定する場ではないと判断し、即日W3Cを脱退した。

An open letter to the W3C Director, CEO, team and membership | Electronic Frontier Foundation

これは正しい判断だ。私が平素から主張しているように、桜の花も散り際が肝心で、いまこそ自由精神の発揚が必要だ。Web規格は誰でも実装できるように公開されているべきで、実装できないような非公開の規定を含む規格はもはや規格ではない。邪悪な不自由な陣営に汚染されたW3CはもはやWebの標準規格を定める場所ではない。

ところで、1933年の日本の国際連盟の脱退に際し、朝日新聞の「連盟よさらば」という見出しの記事の全文を読みたいのだがWeb上には詳細がみつからない。誰か心当たりの人はいないだろうか。

2017-09-18

Linux財団のトップ、「2017年はLinuxデスクトップの年だ」というスピーチのスライド資料を映すのにMacを使って炎上

iTWire - Linux Foundation head proclaims year of Linux desktop – from a Mac

Linux財団のトップ、Jim Zemlinは、オープンソースサミット2017年の基調講演において、「2017年はLinuxデスクトップの年だ」という趣旨の発表を行ったが、そのときにスライド資料を映すのにMacを使ったため炎上している。

Jim ZemlinがMacを使っているのは今に始まった話ではない。例えば4年前の2013年には、Linux開発者として有名な Matthew Garrettにもネタにされている。

そして、今回のことも皮肉られている。

Linuxやオープンソースソフトウェアの普及と発展が目的のLinux財団のトップのJim Zemlinが、オープンソースがテーマの会合において、今年はLinuxデスクトップの年だという趣旨の発表をする際に、スライド資料の投影にLinuxカーネルを使っていないというのは、皮肉も皮肉で炎上もやむなしと言えるのではないか。

たとえば、AppleのトップであるTim CookがMacの新製品を発表するのに使うスライド資料の投影に、マイクロソフトのSurfaceでマイクロソフトのWindows OSを動かしマイクロソフトのパワーポイントを使った場合、これは炎上するだろう。フォードのトップが新車の発表会の会場にトヨタの車で乗り付けてきたらやはり炎上するだろう。

一般に、公の場で特定の団体のトップが団体の主義主張を発表するという肝心な場面で、その主義主張に真っ向から反する言動を発表中に行っているとするならば、それは悪い意味で耳目を集めるに決まっている。

なお、この文章はもちろんGNU/Linuxで書いている。2017年はおろか、もっと前からとっくの昔に、GNU/Linuxデスクトップの時代は到来しているし、ブログ執筆であろうとプレゼンのスライド資料作成であろうと、GNU/Linuxは十分実用に耐えうる。

Jim ZemlinにはよろしくLinuxデスクトップを使い自らドッグフードを食らってもらいたいところだ。

2017-09-12

江添ボドゲ会 9月17日

自宅でボドゲ会を9月17日に開催します。

江添ボドゲ会 9月17日 - connpass