2015-11-27

C++30周年を記念してCFrontのバグ調査をしてみた

Finding Bugs In The First C++ Compiler - What does Bjarne Think!

CFrontの30週年を記念して、C++の設計者にして最初の実装者であるBjarne Stroustrupの実装したC++実装、CFrontに静的解析ツールをかけてバグを洗い出してみた記事が上がっている。

C++の最初のコンパイラーのバグの調査:Bjarneは何考えてたんだ!

C++は先月30周年を記念したので、PVS-Studio開発部署は自前の静的コード解析ツールを使って、最初のC++コンパイラーであるCFrontのバグを探してみようと思い立った。これは奇妙なお祝いの仕方のように思われるだろう、とくに、C++の創始者であるBjarne Stroustrupを問い詰めるわけだから。彼の返答も載っている。

C++の30年の歩み、Bjarne Stroustrup on the 30th anniversary of Cfront (the first C++ compiler)

Bjarne Stroustrupによって開発されたC++は、C with Classesと認知されていた[訳注: これは謝り、C++はC with Classesの経験を元に新たに設計された]。C++の30周年を祝って作成されたこの時系列表は、この言語が1979年に始まって、1985年の10月14日に、BjarneのThe C++ Programming Language初版本とともにリリースされたと書いている。

この時系列表によれば、1985年のCFrontの初リリースから、開発が終了する1993年までに、3度のリリースがあったようだ。

Cpreを元にしたCFrontは、完全なパーサーと、シンボルテーブルと、クラスや関数などのツリー構築を備えている。C++の不思議な制限の多くは、CFrontの実装上の制限に由来している。その理由は、CFrontはC++からCへの変換を行うからだ。つまり、CFrontとはC++プログラマーにとっての神聖なるアーティファクトなのだ。そこで、30周年を記念して、PVS-Studioを使って最初のバージョンをチェックしたくなった。

筆者はCFrontのコードを入手するために、Bjarne Stroustrupに連絡を取った。筆者はコードの入手だけでも長い話になるだろうと考えていたが、とても簡単だった。

ソースコードはオープンで、以下から誰でもダウンロードできる。

C++ Historical Sources Archive — Software Preservation Group

Bjarneによれば、CFrontを検証するのは難しいだろうとのことだ。曰く、

これはとても古いソフトウェアで、1MB 1Mhzのマシンで動作するよう設計されていて、オリジナルのPC(640KB)でも使われていた。これは一人の人間(私)の仕事の一部として開発されていたのだ。

そして、このプロジェクトを検証するのは不可能だと判明した。この当時、クラス名と関数名を分けるのに、ダブルコロン(::)ではなく単一のドット(.)を使っていたのだ。例えば、

inline Pptr type.addrof() { return new ptr(PTR,this,0); }

弊社のPVS-Studioアナライザーはこのコードを解析できない。そこで、筆者は同僚に、コードを見て、このような箇所を手で修正するように依頼した。一応動いたが、まだ問題はあった。アナライザーにコードをチェックさせてみると、混乱して解析を停止する箇所が多々ある。

とにかく、我々はこのプロジェクトを検証した。

結論から言うと、我々は重大な問題を発見できなかった。PVS-Studioが問題を発見できなかった理由は、

  • プロジェクトサイズが小さい。143ファイル、10万行のコードにすぎない
  • コードの品質が高い
  • PVS-Studioはコードの一部を認識できなかった

発見されたバグ

とはいえ、かのStroustrup本人のバグが発見されることを期待している読者をがっかりさせないためにも、コードをみてみよう。

コード1

typedef class classdef = Pclass;

#define PERM(p) p->permanent=1

Pexpr expr.typ(Ptable tbl)
{
    ...
    pclass cl ;
    ...
    cl = (Pclas) nn->tp;
    PERM(cl);
    if(cl == 0) error('i', "%k %s'sT missing", CLASS,s);
    ...
}

PVS-Studio warning: V595 The 'cl' pointer was utilized before it was verified against nullptr. Check lines: 927, 928. expr.c 927

clポインターはNULLに等しい可能性がある。if (cl == 0)によるチェックがその可能性を示唆している。問題は、このポインターはチェックの前に参照されている。問題はPERMマクロの中で発生している。

マクロを展開すると、

cl = (Pclass) nn-tp;
cl->permanent=1;
if (cl == 0) error('i', "%k %s'sT missing", CLASS,s);

コード例2

同じ問題。ポインターが参照された後にチェックされている。

Panme name.normalize(Pbaseb, Pblock bl, bit cast)
{
    ...
    Pname n;
    Pname nn;
    TOK stc = b->b_sto;
    bit tpdf = b->b_typedef;
    bit inli = b->b_inline;
    bit virt = b->b_virtual;
    Pfct f;
    Pname nx;
    if (b == 0) error('i',"#d->N.normalize(0)",this);
    ...
}

PVS-Studio warning: V595 The 'b' pointer was utilized before it was verified against nullptr. Check lines: 608, 615. norm.c 608

コード例3

int error(int i, loc* lc, char* s ...)
{
    ...
    if (in_error++)
        if (t!='t' || 4<in_error) {
            fprintf(stderr, "\nUPS!, error while handling error\n");
            ext(13);
        }
    else if (t == 't')
        t = 'i';
    ...
}

PVS-Studio warning: V563 It is possible that this 'else' branch must apply to the previous 'if' statement. error.c 164

筆者はこれに問題があるのかどうか判別できなかったが、コードのインデントが正しくはない。elseは直近のifに対応する。そのため、このコードは見た目通りに実行されない。

インデントを直すと、以下のようになる。

if (in_error++)
    if (t!='t' || 4<in_error) {
        fprintf(stderr, "\nUPS!, error while handling error\n");
        exit(13);
    } else if (t == 't')
    t = 'i';

コード例4

extern
genericerror(int n, char* s)
{
    fprintf(stderr,"%s\n",
        s?s:"error in generic library function",n);
    abort(111);
    return 0;
}

PVS-Studio warning: V576 Incorrect format. A different number of actual arguments is expected while calling 'fprintf' function. Expected: 3. Present: 4. generic.c 8

フォーマット指定が"%s"なので、文字列は表示されるが、変数nは使われない。

残念ながら(あるいは幸運にも)、筆者はこれ以上の問題を見つけられなかった。アナライザーは他にも警告を出していて、興味深いものもあるが、それほど深刻な問題ではない。

例えば、アナライザーは一部のグローバル変数名が気に入らない様子だ。

extern int Nspy, Nn, Nbt, Nt, Ne, Ns, Nstr, Nc, Nl;

PVS-Studio warning: V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'Nn' variable. cfront.h 50

他の例としては、ポインターの値をfprintf()関数で表示する際に、Cfrontは%iを使っている。現代版では、%pがある。とはいえ、筆者の識る限り、30年前に%pはなかったので、コードは正しい。

筆者はまた、この当時のthisポインターは別の方法で使われていたことに注目した。以下のような例だ。

expr.expr(TOK ba, Pexpr a, Pexpr b)
{
    register Pexpr p;

    if (this) goto ret:
    ...
    this=p;
    ...
}

inline toknode.~toknode()
{
    next = free_toks;
    free_toks = this ;
    this = 0;
}

見てわかるように、thisの値を変えるのは禁止されていなかった。今はthisポインターを変更するのは禁止されている。そのため、thisをnullと比較するのは現代では理屈に合わない。

筆者はまた、特に興味深いコード片を発見した。

/* this is the place for paranoia */
if (this == 0) error('i',"->Cdef.dcl(%d)",tbl);
if (base != CLASS) error('i', "Cdef.dcl(%d)",base);
if (cname == 0) error('i',"unNdC");
if (cname->tp != this) error('i',badCdef");
if (tbl == 0) error('i',"Cdef.dcl(%d,0)",cname);
if (tbl->base != TABLE) error('i',""Cdef.dcl(%n,tbl=%d)",
                              cname,tbl->base); 

Bjarne Stroustrupの返答

さて、Bjarneは30年前のエラーに対しどう返答するのか。以下が彼の返した返事だ。

  • CFrontはCpreからブートストラップされたが、完全な書きかえがあった。CFrontにはCpreのコードは一行もない
  • use-before-test-of-0はもちろんダメだが、興味深いことに、私が当時使っていたマシンとOS(DECと研究用UNIX)には、page zero write protectがあったので、このバグによる問題が実際発生したならば見逃されずにいただろう
  • if-then-elseバグは変だ。ソースを読んだところ、単なるインデント間違いではなく、実際の間違いであった。しかし、それほど問題ではない。違いは、異常終了するまえのエラーメッセージの些細な間違いでしかない。見つけられなかったのも無理はない。
  • そうだ。もっと読みやすい名前を使うべきだった。このコードを他の人間が長期間保守することは想定していなかった(それと私はタイプが下手だった)
  • そうだ。%pは当時なかった。
  • そうだ。thisの仕様は変更された。
  • そのパラノイアテストはコンパイラーのメインループに仕掛けてある。もしソフトウェアかハードウェアに不具合があったならば、そのテストのうちのどれかは失敗するだろう。少なくとも一度、そのコードはCFrontをビルドするツールのコード生成のバグを発見した。思うに、すべての大規模なプログラムは、「不可能」なエラーを発見するための「パラノイアテスト」を含むべきである。

結論

CFrontの価値は計り知れない。プログラミングの開発に多大な影響を与えたし、未だに開発され続けているC++言語を生み出した。C++を作って開発したBjarneの作業には感謝してもし尽くせない。この素晴らしいコンパイラーを検証出来なのはとてもよかった。

Andrey KarpovはProgram Verification Systemsの共同創始者かつCTOである。この会社の主な活動は静的コード解析ツール、PVS-Studioの開発である。AndreyはVisual C++エキスパートとして4回のMicrosoft MVP賞を獲得し、C++ Hintsウェブサイトを運営している。

パラノイアテストが興味深い。

例えば、Chromiumにも含まれている。

また、URLを忘れたが、どこかのオンラインゲームが、ゲームクライアントのメインループに絶対に失敗しないチェック(簡単な数値計算と結果の比較)を淹れてみたところ、少なからぬ割合のユーザーがチェックに失敗したという。

また、Microsoft Windowsのエラー報告で、xor eax,eaxのような絶対に失敗するはずのないコード箇所でクラッシュした報告を受け取った(ユーザーのCPUにそのようなエラッタはない)ので、調査してみたところ、そのユーザーのPCはショップで組み立て済みで販売されているもので、過剰にオーバークロックされていたとの話もある。

江添ボドゲ会@12月の日程調整

12月に江東区塩浜にある自宅でボドゲ会を開催したいので、日程調整のために調整さんを作ってみた。

12月の休日にボドゲをしたい人は調整をしてください。

ボドゲ会 | 調整さん

2015-11-26

カタン アメリカの開拓者たちの感想

「カタン アメリカの開拓者たち」を遊んでみたので、その感想を書いてみる。

アメリカカタンは、カタンと名前が付いているものの、全くの別ゲーだ。まず、ランダムタイルはなく、北アメリカの形をした固定ボードを使う。一部の資源タイルの数字だけ変動する。

ゲーム序盤は東側に変動する数字チップが載っているのだが、西側に都市を立てるにつれ、最も東側の数字チップが西に移動していく。これにより、ゲーム後半は東海岸が不毛の地と化す。ゴールドラッシュによる東から西への開拓移動を表現しているのだろう。

資源は、木、麦、鉄、石炭、牛である。羊ではなく牛なところがアメリカっぽい。

ゲームで建設できるものは:幌馬車、幌馬車を動かす、線路、列車、列車を動かす、発展カードである。幌馬車の建設には幌の部分に牛革が必要なのか、牛が必要である。実にアメリカっぽい。

カタンのように6面ダイスを2つ転がして、出た目の合計値と同じ資源タイルから資源が算出されるのは変わらない。何も取れない場合、金貨が1金手に入る。また、盗賊の代わりにアウトローがいる。実にアメリカっぽい。機能は盗賊と同じだ。7を振っても、資源を捨てる必要はない。

また、プレイヤーは複数の都市と物資を持っている。都市と物資は組になっていて、都市を設置すると物資が解放される。

ゲーム序盤は、幌馬車を建設し、幌馬車を移動させて、まだ都市を立てていない場所に移動させ、そして都市を設置する。都市の設置は建設ではなく、したがって建設コストはかからない。幌馬車をまだ都市が未建設の場所まで移動させるだけで良い。

都市を設置すると、物資が解放される。都市間と線路でつないで、列車を建設して、まだ物資が置かれていない他人の都市に移動させると、解放された物資をその都市に置くことができる。

一部の細かいルールは省略したが、これが、ゲームの基本的な流れだ。物資を一番早く運び終えたプレイヤーが勝利する。

さて、この「物資」なのだが、都市を建設した際に排出され、他人の都市に置くことで取り除くことができて、全部取り除くと勝利する。物資を置かれた都市には特に利益が発生しない。はたしてこの物資はなんなのだろうか。ゴミだろうか。ゴミに違いない。

こうして、私がアメリカカタンをプレイした時、我々は物資のことをゴミと読んでいた。「都市をさっさと立ててゴミを出したい」、「はやくゴミを運んで片付けたい」、「まだゴミが残っている」などなど

細かいルールとして、一人の手番が終わるたびに、非手番プレイヤーが順番に建設のみを行える、手番外建設フェイズが走るのだが、このフェイズの活用方法がよく分からなかった。7を振っても資源のバーストは起きないので、考えられるのは、線路を引いて妨害するか、発展カードを引いて自分の手番で即座に使えるようにするぐらいしかない。

結論としては、アメリカカタンはカタンではないしカタンという名前を使うのは既存のブランドを利用した名前詐欺でしかない。ゲーム自体は悪くなかったが、どうもあまり深く考えずに取り入れている要素も多いように思った(手番外建設フェイズなど)

2015-11-25

C++標準化委員会の文書のレビュー: P0080R0-P0089R0

P0080R0: Variant: Discriminated Union with Value Semantics

variantライブラリの提案。

P0081R0: A proposal to add sincos to the standard library

sinとcosを同時に計算してくれるsincosライブラリの提案。

ある値に対してsinとcosを同時に両方共計算したいことはよくある。同じ値を二度引数として別の関数に渡すのは間違いのもとである。また、x86などの一部のアーキテクチャには、sinとcosを同時に計算してくれる効率的な命令が用意されている。

GCCにあるsincosは、結果をポインターで受け取るが、ポインターが絡むとエイリアスなどの問題が出てくるので、提案されている文法は、単に値をpairで返す。

#include <utility>
namespace std {

pair<double, double> sincos(double x) noexcept;

// その他、floatなどのオーバーロード関数
}

std::tieを使えば、pairは意識せずにすむ。

#include <tuple>

void f( double x )
{
    double s, c ;

    std::tie( s, c ) = std::sincos( x ) ;
}

[PDF] P0082R0: For Loop Exit Strategies (Revision 1)

forループから、条件式がfalseになって抜けた時と、breakで抜けた時に実行されるブロック文を記述できるif for文の提案。

if for ( ... ; 条件 ; ... )
    {
        // for文のブロック文
    }
{
    // 条件がfalseになって抜けた時に実行される   
}
else
{
    // breakで抜けた時に実行される。
}

この論文筆者が主張するには、以下のようなコードより読みやすいらしい


for ( ; 条件 ; )
{
    // for文のブロック文
}

if ( !条件 )
{
    // 条件がfalseで抜けた
}
else
{
    // breakで抜けた
}

どうも必要性がわからない。そういう複雑なfor文は関数として切り分ける方が読みやすいし、lambda式のある現在、ライブラリベースの実装でも良い気がする。

[PDF] P0083R0: Splicing Maps and Sets (Revision 2)

std::listには、spliceという機能がある。これを使えば、あるlistコンテナーで管理されている要素を、所有権ごと別のコンテナーに移動することができる。


    std::list a { 1, 2, 3 } ;
    std::list b { 4, 5, 6 } ;

    a.splice( a.end() , b ) ;

    // a = { 1,2,3,4,5,6}
    // b = { }

たといint型のようにコピーやムーブの処理が軽くても、メモリの動的な確保と解放のコストは大きい。spliceは、所有権ごと要素を移すことで、不要なコストを削減できる。

しかしmapとsetには、このようなspliceがないし、簡単に提供できない。

そこで、連想コンテナーから要素の所有権を切り離せるextract、所有権が切り離された要素を管理するnode_ptr、node_ptrを受け取るinsertを追加する提案。

[PDF] P0084R0: Emplace Return Type

emplaceが構築したオブジェクトへのリファレンスを返すようにする提案。

emplaceは、コンテナーに挿入すべきオブジェクトを渡すのではなく、オブジェクト構築時の引数を渡す。しかし、構築後のオブジェクトを操作したい場合、コンテナーからそのオブジェクトをわざわざ引っ張り出してこないといけない。

struct X
{
    X( int, int, int ) ;

    void do_something() ;
} ;

int main()
{
    std::vector<X> v ;

    // 構築
    v.emplace_back( 1, 2, 3 ) ;

    // 構築後のオブジェクトを引っ張りだして操作
    v.back().do_something() ;
}

emplaceが構築後のオブジェクトへのリファレンスを戻り値として返すようにすれば、このコードは簡略化できる。

v.emplace_back( 1, 2, 3 ).do_something() ;

提案では、emplace_frontとemplace_backを変更するよう提案している。従来のfrontとbackは戻り値を返さないが、これはどうせオブジェクトを実引数で渡すので、コピー/ムーブされたオブジェクト

P0085R0: Oo... adding a coherent character sequence to begin octal-literals

新しい8進数リテラル0oを追加する提案。

0o11 ; // 10進数で9
0O11 ; // 9

既存のプレフィクス0から始まる8進数リテラルは、現代人には馴染みが薄く混乱のもとである。このリテラルは、まだコンピューターが原始的で、パーソナル・コンピューターといえば個人が8bit CPUや16bit CPUを使って自力で作っていた時代からの名残である。

この提案では、新しい8進数リテラルのプレフィクス0o(数字ゼロにアルファベットの大文字小文字のオー)を追加する。

新しい8進数リテラルの追加によって、8進数リテラルへの注目度を上げ、この新しい8進数リテラルを解説する時は、古い8進数リテラルへの注意も併記して問題認識を高め、いずれ既存の8進数リテラルをdeprecated扱いにして、除去することを目指している。

プレフィクス0oの8進数リテラルは、すでに、haskell, OCaml, Python 3.0, Ruby, Tcl Version 9でサポートされていて、ECMAScript 6もサポートする予定であるという。

[PDF] P0086R0: Variant design review

[PDF] P0087R0: Variant: a type-safe union without undefined behavior (v2)

[PDf] 0088R0: Variant: a type-safe union that is rarely invalid (v5)

ライブラリベースの型安全unionを実現するvariantライブラリの設計事情と、議論が別れている設計の差異について。

variantはinvalid/emptyな状態を許容すべきかどうか。その状態をクエリー可能にすべきかどうか。

純粋な数学的観点からinvalid/emptyなvariantは認めるべきではないという意見と、その場合、ユーザーが自前でダブルバッファリングをしなければならないので結局使いづらい。variantがダブルバッファリングを提供すべきだ。例外を投げないムーブのある型の場合、メタプログラミングでvariantのダブルバッファリングは除去できるし、将来的にはTransactional Memoryであらゆる型のダブルバッファリングが除去できるなどと豪語する壮大なお花畑意見まである。

[PDF] 0089R0: Quantifying Memory-Allocatiom Strategies

メモリ確保にローカルアロケーターを使うことによるパフォーマンス向上のベンチマークと考察

ローカルアロケーターを使い、個々のオブジェクトのデストラクターを呼ばずに、一括して解放することによる、パフォーマンスの向上の余地は十分にあると主張している。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-20

Mac OS Xで削除がとても難しいファイルを作成する方法

2013年の記事だが、Mac OS XとそのHFS+はこの上なくクソなので何があっても驚きはしないが、Mac OS Xで削除しにくいシンボリックリンクファイルを作成する方法があるらしい

OS X and the Unremovable File - galvanist

HFS+上で、削除しにくいシンボリックリンクを作成できる。

# be root, for example with: sudo -i
str1=$(python -c "print '1' * 255")
str2=$(python -c "print '2' * 255")
str3=$(python -c "print '3' * 255")
str4=$(python -c "print '4' * 253")
mkdir -p  $str1/$str2/$str3/$str4
ln -s ftw $str1/$str2/$str3/$str4/L

さて、このように作った以上、OS X v10.9では以下のコマンドでは削除できない。


# still as root...
unlink 1*/2*/3*/4*/L
unlink $str1/$str2/$str3/$str4/L
rm -rf 1*
rm -rf $str1
rm -rf $str1/$str2/$str3/$str4
rm -rf $str1/$str2/$str3/$str4/L
(cd $str1/$str2/$str3/$str4; unlink L)
(cd $str1/$str2/$str3/$str4; rm -rf L)

すべて、以下のようなエラーとなる。(読みやすさのために[...]で省略)

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# rm -f L
rm: L: No space left on device
root# df -H
Filesystem      Size   Used  Avail Capacity   iused     ifree %iused  Mounted on
/dev/disk1      250G   108G   142G    44%  26385563  34601956   43%   /
[...]

念の為、システムコールを直接呼び出すコードを書いてみる。/tmp/fixit.cにおいてあるとする。

#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[]) {
    printf("Unlink returned %i\n", unlink("L"));
    perror("Error was");
    return 0;
}

実行してみるが、

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# gcc -o /tmp/fixit /tmp/fixit.c 
root# /tmp/fixit 
Unlink returned -1
Error was: No space left on device

ENOSPCだと。unlink(2) Mac OS X Developer Tools Manual Pageに返すとは書かれていないエラーだぞ。

状況は複雑だ。

  • 一般ユーザーが作ったのならば、一般ユーザーは、rm -rfで消すことができる
  • 一般ユーザーが作ったのならば、rootは消すことができない。おかしい
  • rootが作ったのならば、rootは消すことができない
  • rootが作ったのならば、通常通りの権限により、一般ユーザーは削除が行えない
  • rootが作ったものであっても、chmod -hとchown -hで権限を変えれば、一般ユーザーにも消せるはずである
  • rootが作ったものを、chmod -hとchown -hしようとすると、ENOSPCが返る。
  • rootが作ったものに対し、"mkdir -p some/containing/paths; mv 1111* some/containing/paths/"は動くが、その後、"rm -rf some"しても動かない。

Workaround

なぜか、パスはシンボリックリンクを作るには短いが、削除するには長すぎるようだ。パスを短縮すれば消せる。

root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# mv /private/tmp/1* /private/tmp/one
root# pwd
/private/tmp/one/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# rm L
root# ls
root# rm -rf /tmp/one
root# 

workaroundは十分なのか。

そういうわけで、一応workaroundはあるのだが、しかし疑問がある。

  • アンチウイルスソフトウェアはマルウェアがこのような方法で保存されていた場合、ちゃんと消せるよう作られているのか? アンチウイルスソフトウェアはすでにchflags(2)の問題に対処しなければならないが、こういうパスと複数のchflagsで構成されていた場合はどうか
  • マルウェアや悪意ある人物がディスクをこのようなもので埋め尽くした時に、rm -rfの代替品となるツールは存在するのか?

ちなみに、これは2013年の記事だが、Yosemiteまでは同じ問題があるそうだ。El Capitainだと、ファイルパスが長すぎて作成できないエラーが返るらしい。そもそも、Mac OS XのカーネルであるXNUのファイルパスの上限が2024文字なのに2025文字のファイルパスを作成できてしまうのがおかしいようだ。

C++標準化委員会の文書のレビュー : P0070R0-P0079R0

P0070R0: Coroutines: Return Before Await

現在のコルーチン提案では、関数がコルーチンかどうかを宣言から判定できない。そのような判定をさせるタグ機能は議論の結果却下された。しかし、awaitかyieldをみるまで関数がコルーチンかどうかわからないのは不便なので、コルーチン関数では、通常のreturnキーワードを使ったreturn文のかわりに、特別なco_returnキーワードを使ったreturn文の使用を強制させるようにしようという提案に対して、その必要はないとする論文。

理由は、MSVCやGCCは、すでに最適化のために、関数の全体を見るまで意味解析を行わないようになっているので、そんな技術的制約上の機能は必要ないとのこと。

P0071R0: Coroutines: Keyword alternatives

現在のコルーチンの提案では、awaitとyieldに当たるキーワードが未定である。理想世界においては、awaitとyieldというキーワードは機能をこれ以上なく明白に指し示したものである。しかし、現実世界においては、awaitやyieldのような既存のコードと衝突するようなキーワードを導入できない。

これに対して、いくつかの解決策が示されている。

ソフトキーワード:std名前空間に暗黙に宣言されている名前で、他の名前と衝突しない場合、キーワードとして認識される。

マジック関数: std名前空間内にある特別な関数で、コンパイラーが認識する。

問題は、文法が関数呼び出し式になってしまうので、すこし汚い。

await expr ;

が、

std::await( expr ) ;

になってしまう。

その他のキーワード案: exportキーワードが余っているではないか。yieldexpr/yield_exprはどうか。coyield/co_yieldはどうか。

論文では、awaitとyieldに変わる良いキーワードはないとした上で、ソフトキーワードかマジック関数のどちらかを採用すべきだとしている。

[PDF] P0072: Light-Weight Execution Agents

スレッドよりも制約の強い実行媒体(Execution Agent、スレッドプール、ファイバー、GPGPU、SIMDなど)を扱うために、実行媒体とその保証について定義する提案。

[PDF] P0073R0: On unifying the coroutines and resumable functions proposals

コルーチンとレジューム可能関数を統合したことについて

P0074R0: Making std::owner_less more flexible

std::onwer_lessという関数オブジェクトがある。これは、2つのshared_ptrとweak_ptrが、同じオブジェクトを所有しているかを判定してくれる機能を提供している。しかし、このインターフェースが悪いため、とても単純な例でしか動かない。

  shared_ptr<int> sp1;
  shared_ptr<void> sp2;
  shared_ptr<long> sp3;
  weak_ptr<int> wp1;

  owner_less<shared_ptr<int>> cmp;
  cmp(sp1, sp2);   // error, doesn't compile
  cmp(sp1, wp1);
  cmp(sp1, sp3);   // error, doesn't compile
  cmp(wp1, sp1);
  cmp(wp1, wp1);   // error, doesn't compile

最初の例は、owner_less<shared_ptr<void>>を使うことで解決できるが、単純にそれだけを使ってもコンパイルが通らない。なぜならば、sp1はshared_ptr<void>にもweak_ptr<void>にも変換可能だからだ。そのため、明示的に変換してやらなければならない。

owner_less<shared_ptr<void>> cmpv;
cmpv(shared_ptr<void>(sp1), sp2);

これは冗長でわかりにくいだけではなく、一時オブジェクトを作り出し、参照カウンターを増減させる。

shared_ptr::owner_beforeとweak_ptr::owner_beforeはどちらもshared_ptr<A>とweak_ptr<B>といった異なる型同士での比較を許しているのに、owner_lessは無用の制限をかけている。

owner_lessを改良して、上のコードが全てコンパイルが通るようにする提案。

[PDF] P0075R0: Template Library for Index-Based Loops

インデックスベースループの並列版をライブラリとしてParallelism TSに付け加える提案。

void ( int * p1, int * p2, std::size_t size )
{
    std::for_loop( std::seq, 0, size,
        [&]( auto i )
        {
            p1[i] = p2[i] ;
        }
    ) ;
}

このコードは以下のように書くのと同等だ。

void ( int * p1, int * p2, std::size_t size )
{
    for ( std::size_t i = 0 ; i != size ; ++i )
    {
        p1[i] = p2[i] ;
    }
    
}

他のParalellism TSのアルゴリズムのように、std::secをstd::parに変更すると、並列実行版にある。

for_loop_stridedは、インクリメントの刻み幅を設定できる。

for_loop_strided( std::par, 0, 100, 2, []( auto i ) { } ) ;

iは0, 2, 4, 6とインクリメントされていく。

提案はfor_loopとともに使えるreductionとinductionをサポートしている。

reductionはOpenMPの文法を参考に、純粋にライブラリベースで使えるように設計されている。

float dot_saxpy(int n, float a, float x[], float y[]) {
    float s = 0;
    for_loop( par, 0, n,
        reduction(s,0.0f,std::plus<float>()),
        [&](int i, float& s_) {
            y[i] += a*x[i];
            s_ += y[i]*y[i];
        });
    return s;
}

reductionは、reduction( var, identity, op )のように使う。それぞれ、reductionの結果をうけとるlvalue, reduction操作のためのidentity value, reduction操作となる。

reductionの個別のsのコピーはfor_loopの関数オブジェクトに追加の引数として与えられる。for_loopはvariadic templatesを利用していて、reductionやinductionを受け取れるようになっている。最後の実引数が関数オブジェクトとなる。

ループのイテレーションごとに異なる可能性のあるsのコピーが渡され、それに対してreduction操作、最後にすべてのsのコピーがsに対してreduction操作が行われる。結果として、以下のコードを実行したのと同じ結果(ただしシーケンスとreductionの順序の制約がゆるい)が得られる。

float serial_dot_saxpy (int n, float a, float x[], float y[]) {
    float s = 0;
    for( int i=0; i<n; ++i ) {
        y[i] += a*x[i];
        s += y[i]*y[i];
    }
    return s;
}

簡便化のために、identitiy valueがreduction操作に合わせてT()やT(1)など無難なものになった上に、reduction操作も固定されている、reduction_plusなどが用意されている。上記の例は、reduction_plus( s ) と書くこともできる。

inductionもreductionと同じように指定する。

float* zipper(int n, float* x, float *y, float *z) {
    for_loop( par, 0, n,
        induction(x),
        induction(y),
        induction(z,2),
        [&](int i, float* x_, float* y_, float* z_) {
            *z_++ = *x_++;
            *z_++ = *y_++;
        });
    return z;
}

この分野の知識が乏しいのでinductionの意味がよくわからない。以下のコードとシリアル実行という点を除いては同じ意味になるようだが、単にlambda式でキャプチャーしてはダメなのだろうか。

float* zipper(int n, float* x, float *y, float *z) {
    for( int i=0; i<n; ++i ) {
        *z++ = *x++;
        *z++ = *y++;
    }
    return z;
}

[PDF] P0076R0: Vector and Wavefront Policies

Parallelism TS(STLのアルゴリズムに並列実行版のオーバーロードを追加する規格)に、新しい実行ポリシーとしてvecとunseqを追加する提案。

unseqはseqより実行時特性の制約がゆるいが、実行は単一のOSスレッド上に限定される。vecはシーケンスの制約がunseqより強い。vecはSIMDなどのベクトル演算を想定している。

P0077R0: is_callable, the missing INVOKE related trait

指定した型が指定した実引数型で呼び出して指定した戻り値の型を返すかどうかを調べられる、is_callable traitsの提案。

template <class, class R = void> struct is_callable; // not defined

    template <class Fn, class... ArgTypes, class R>
      struct is_callable<Fn(ArgTypes...), R>;

    template <class, class R = void> struct is_nothrow_callable; // not defined
    template <class Fn, class... ArgTypes, class R>
      struct is_nothrow_callable<Fn(ArgTypes...), R>;

以下のように使う。

void f( int, double ) ;

constexpr bool b = std::is_callable< f ( int, double ), void >::value ; // true

is_callable< Fn( args ... ), R >は、INVOKE( declval<Fn>(), declval<args>()..., R )が合法かどうかを返す。

上記の条件に加えて、無例外保証も調べるis_nothrow_callableもある、

[PDF] P0078R0: The [[pure]] attribute

[[pure]]属性を追加する提案。

[[pure]]属性は関数に指定することができる。指定された関数はpureとなる。pure関数には様々な制約がある。

pure関数は、与えられた入力に対して出力を返す。同じ入力が与えられた場合、必ず同じ出力を返す。副作用を発生させてはならない。戻り値の型がvoidであってはならない(純粋に数学的な関数は常に戻り値を返す)

現在の文面案は以下の通り。

関数fがpureである場合、数学的な関数を実現している。すなわち、(a) 同じ値の実引数に対して、fは常に同じ答えを返す。(b) fはクライアントコードと実引数リストと戻り値のみを使ってやり取りする。(c) 常に呼び出し元に戻る。(d) fはその実行外において観測可能な副作用を発生させない。関数gの本体の中の文Sがpureである場合、Sが実行されたならば、pure gの挙動から外れた挙動を示してはならない。pure関数、あるいは文の反対は、impureである。すべての関数と文は、pureであると指定されない限り、impureとなる。

[ 例:関数fは、以下の場合、pureではない。

  • 実引数が値で渡されていない
  • グローバルメモリへの読み書きアクセス
  • 戻り値の型がvoid
  • impure関数を呼び出す
  • 呼び出し元や他のスレッドがfによる変更を感知できること、たとえば、

    1. 関数にローカルではない変数やデータ構造に依存するか書き変える
    2. 動的にメモリーを確保する
    3. 例外をcatchしないで外に投げる

pure関数には様々な利点がある。pure関数を使った式は除去したりmemoizationにより簡易化できる。同期やスレッドセーフ、コード順序の変更も大胆に行えるので、最適化の余地が広がる。pure関数はテストしやすい。

pure関数には問題もある。副作用を発生させることができないので、I/Oや動的メモリ確保ができないし、impure関数を呼び出すこともできない。コンパイラーはpure指定された関数はpureであるという前提のもとコード生成を行うので、pure関数に副作用を発生させるバグがある場合、不可思議な挙動を示し、特定が困難になるかもしれない。

論文は既存のプログラミング言語におけるpure関数の実装例も紹介しているので興味のある人は読むと良い。

P0079R0: Extension methods in C++

統一感数記法に変わる軽量な拡張メソッドの提案。

統一感数記法は、メンバー関数呼び出しとフリー関数呼び出しの文法を統一し、どちらで呼び出しても良いようにするものであった。

struct A { } ;
void foo( A & ) ;

struct B { void foo() ; }

int main()
{
    A a ;
    a.foo() ; // OK

    B b ;
    foo(b) ; // OK 
} 

目的としては、ジェネリックなコードで、型によってメンバー関数とフリー関数によって呼び出す文法を変えなければならないのは面倒なので、どちらで呼び出してもコンパイラーが勝手に変換してくれるようにしようというものだ。

しかし、この機能はあまりにも大きすぎる変更なので、既存のコードに問題を引き起こす恐れがある。そこで、この提案では、もう少し軽量な、明示的なopt-inが必要となる機能、拡張メソッドを提案している。

拡張メソッドは、フリー関数で、第一引数として明示的なthisポインターを取る。仮引数名は必ずthisとする。

struct A { } ;

// 拡張メソッド
void foo( A * this, int ) ;

int main()
{
    A a ;
    a.foo( 0 ) ; // OK
}

オーバーロード解決では、メンバー関数と拡張メソッドでは、メンバー関数のほうが優先される。


struct A
{
    void f() ;
} ;

void f( A * this ) ;

int main()
{
    A a ;
    a.f() ; // メンバー関数
}

拡張メソッドは、アクセス指定においては特別扱いされない。

struct A
{
private :
    int x ;
} ;

void f( A * this )
{
    this->x ; // エラー
} 

コンセプトと組み合わせることで、メンバー関数を持っている場合のみ呼び出す拡張メソッドが書ける。

template < typename T >
concept bool has_foo()
{
    return requires( T & t )
    {
        { t.foo() } -> void ;
    } ;
}

void foo( has_foo * this )
{
    this->foo() ;
}

C#にある拡張メソッドを参考にしているらしい。

なんだか素朴な案だ。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-18

Minecraftクライアントからdockerの管理ができるminecraftサーバー: Dockercraft

docker/dockercraft

公式の未改変Minecraftクライアントから接続してDockerの管理ができるMinecraft互換サーバー、Dockercraftが公開されている。

Dockercraft

Dockercraftの実行方法

1. Minecraftのインストール: Minecraft

Minecraftクライアントは改変していないので、公式リリースをそのまま使える。

2. Dockercraftイメージをpullするかビルドする。

docker pull dockercraft

もしくは、

git clone git@github.com:docker/dockercraft.git
docker build -t dockercraft dockercraft

3. Dockercraftコンテナーを実行する

docker run -t -i -d -p 25565:25565 \
-v /var/run/docker.sock:/var/run/docker.sock \
--name dockercraft \
dockercraft

Docker remote APIを使うには/var/run/docker.sockをコンテナー内でマウントする必要がある。

Minecraftサーバーのデフォルトのポートは25565だ。他のポートがいいのであれば、"-p <port>:25565"

4. Minecraft > Multiplayer > Add Serverを開く

サーバーアドレスはDockerホストのIP。デフォルトのものを使っているのであればポート番号を指定する必要はない。

Docker Machineを使っているのであれば、"docker-machine ip <machine_name>"

5. サーバーにjoinする。

少なくともひとつのコンテナーが世界に見えるはずだ。それはDockercraftサーバーをホストしているものである。

レバーとボタンを操作することで、コンテナーをスタート、ストップ、リムーブできる。Minecraftのチャットウインドウから一部のDockerコマンドがサポートされている。チャットウインドウはTキー(デフォルト)か/キーで表示される。

実装

Mnecraftクライアント自体は改変していない。操作は全てサーバーサイドで行われている。

Minecraftサーバーには、Cuberite.orgを用いた。C++で書かれた独自のMinecraft互換ゲームサーバーだ。GitHubレポジトリ

サーバーはLUAで書かれたスクリプトをプラグインとして受け付ける。そこでDocker用のものを書いた。(world/Plugins/Docker)

残念ながら、このプラグインと通信するいいAPIがない。ただしwebadminがあり、プラグインは"webtabs"に応答できる。

Plugin:AddWebTab("Docker",HandleRequest_Docker)

つまり、プラグインは、http://127.0.0.1:8080/webadmin/Docker/Dockerに送られたPOSTリクエストを見ることができる。

Goproxy

Docker remote APIのイベントはGoで書かれた小さなデーモン(go/src/goproxy)によってLUAプラグインに送られる。

func MCServerRequest(data url.Values, client *http.Client) {
    req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/webadmin/Docker/Docker", strings.NewReader(data.Encode()))
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    req.SetBasicAuth("admin", "admin")
    client.Do(req)
}

デーモンにリクエストを送るため、LUAプラグインからgoproxyバイナリを匹数付きで実行することもできる。

function PlayerJoined(Player)
    -- refresh containers
    r = os.execute("goproxy containers")
end

貢献

Dockercraftをハックしたいならば、Docker's contributions guidelinesを参照。

これで、仕事中にMinecraftで遊んでいても、「Dockerの管理をビジュアライズしてくれる便利なツールなんです」という言い訳がたつ。

「なるほど、しかしなぜ大規模なクラフトをする必要があるんだね?」
「Docker操作を自動化するために必要な論理回路を組んでいます」

2015-11-16

C++標準化委員会の文書のレビュー: P0060R0-P0069R0

P0060R0: Function Object-Based Overloading of Operator Dot

operator .をオーバーロード可能にする提案。

この提案は、リファレンスを返す提案とは異なり、コンパイラーが関数オブジェクトを生成する提案となっている。

operator .をオーバーロードしてるユーザー定義型がある。

struct X
{
    // なにもしない
    template < typename T >
    void operator .( T && ) { }
}

そのような型の非staticデータメンバーの名前検索(たとえば.some_name)をして、名前が見つからない場合、コンパイラーは以下のような型を生成し、そのオブジェクトをoperator .のオーバーロード関数の実引数に渡す

struct synthesized_function_type {
    template<class T>
    auto operator()(T&& t) -> decltype(t.some_name) noexcept(t.some_name) {
        return t.some_name;
    }
};

その結果、以下のように書ける。

X x ;
x.hoge ; // OK
x.hage ; // OK

メンバー関数の場合は、以下のような型が生成される。

struct synthesized_function_type {
    template<class T, class... Args>
    auto operator()(T&& t, Args&&... args) -> decltype(t.some_name(std::forward<Args>(args)...)) noexcept(t.some_name(std::forward<Args>(args)...)) {
        return t.some_name(std::forward<Args>(args)...);
    }
};

これを使って、例えばスマートリファレンスを作成できる。

template < typename T >
class Ref
{
    T t ;
public :
    template < typename F >
    auto operator . ( F && f )
    {
        return f( t ) ;
    }

    template < typename F, typename ... Args >
    auto operator . ( F f, Args && ... args )
    {
        return f( std::forward<Args>( args ) ... ) ;
    }
} ;

struct Point
{
    int x, y ;
    void func( int x ) { }
} ;

Ref<X> r ;

r.x ;
r.y ;
r.func( 0 ) ;

P0061R0: Feature-testing preprocessor predicates for C++17

SG-6が推奨している機能テストマクロに含まれている、__has_includeと__has_cpp_attributeを正式にC++17に提案している。

__has_includeは指定したヘッダーファイルがあるかどうかを調べることができる。__has_cpp_attributeはattributeが存在するかどうか調べることができる。

#ifdef __has_include(<optional>)
#   include <optional>
#else
#   include "my_optional"
#endif 

P0062R0: When should compilers optimize atomics?

atomicではすべての最適化が無効になると考えている者もいるが、atomicは最適化できる。たとえば、load( x, memory_order_x ) + load( x, memory_order_x )は、2 * load( x, memory_order_x )に変換することができる。しかし、非volatileなatomic loadが使われてはいないからといって、消してしまうのは異論が多い。

この論文では、c++std-parallelで議論された、異論のある最適化についてまとめている。

// 途中経過表示用の変数
atomic<int> progress(0);

f() {
    for (i = 0; i < 1000000; ++i) {
        // 処理
        ++progress;
    }
}

このようなコードがあるとする。atomic変数progressは処理の進行具合を記録していて、他のスレッドはこの変数を参照してユーザーに処理の進行具合をプログレスバーで表示している。

過剰に最適化するコンパイラーは、以下のようにコードを変換するかもしれない。

atomic<int> progress(0);
f() {
    int my_progress = 0; // レジスタ割り当てされた非atomic変数
    for (i = 0; i < 1000000; ++i) {
        // ...
        ++my_progress;
    }
    progress += my_progress;
}

コードがこのように変換されると、他のスレッドがprogressを参照した時に、0か999999しか見えない。

他には以下のような例がある。

x.store(1); // アトミック操作
while (...) { … } // ループ

コンパイラーは以下のように変換するかもしれない。

while (...) { … }
x.store(1);

これも規格上問題ないが、ループを始める前にアトミック操作を行って他のスレッドに通知したいと考えるプログラマーの意図とは異なるだろう。

Peter Dimovによれば、過剰な最適化は好ましいという。例えば、shared_ptrのカウンターの増減を除去できる。

Paul McKennryによれば、上記のプログレスバー問題を解決するのによく使われる手法は、アトミック変数をvolatile修飾することだという。しかし、これでは問題のない好ましい最適化まで抑制されてしまう。コンパイラーがそこまで過剰に最適化を行わない場合はパフォーマンスの損失につながる。

論文筆者のHans Boehmの考えでは、コンパイラーはアトミック変数を過剰に最適化するべきではない。属性などで過剰に最適化させる方法を提供すべき。としている。

過剰な最適化は、shared_ptrの例のようにとてもよいこともあれば、プログレスバーの例のようにとても悪いこともある。コンパイラーにとって、良い悪いは判断できない。判断できないのであれば、どちらがわにも倒さずにコードをそのままにしておくべきだ。

とはいえ、過剰な最適化の利点はあるので、そのための方法を提供すべきだ。

論文は最後に、規格の文面に追記する注釈案を載せている。

P0063R0: C++17 should refer to C11 instead of C99

C++規格が参照するC規格をC99からC11に引き上げる提案。

単に参照先を買えるだけではない変更も考察されている。シグナルハンドラー内ではthread_local変数は使えない。新たに追加されたCヘッダーに対してはC++規格からは参照しない。Atomicsについては、_Atomic(T)がatomic<T>になっていると嬉しい。C言語に新しく追加された有益な機能のC++版がないなど。

[PDF] P0065R0: Movable initializer lists, rev. 2

initalizer_list<T>のbegin/endの戻り値の型は、const T *である。そのため要素をムーブできない。この提案は、非constなT *を返すbegin/endを持ったown_initializer_list<T>を提案している。

初期化リストから推定される型Tが、非トリビアルなムーブコンストラクターを持っている場合は、型はown_initializer_list<T>になる。そうでない場合は、initializer_list<T>になる

// std::initializer_list<int>
auto x = { 1, 2, 3 } ; 
// std::own_initializer_list<std::string>
auto y = { std::string("hello") } ;

[PDf] P0066R0: Accessors and views with lifetime extension

一時オブジェクトをリファレンスに束縛すると、一時オブジェクトの寿命がリファレンスの存在期間中延長されるが、現行の規程は問題が多い。

std::string const & f( std::string const & ref ) { return ref ; }

int main()
{
    // 寿命が延長されない。
    auto const & ref = f( std::string("hello") ) ;
}

initializer_listだけはこのような一時オブジェクトの寿命延長をコンパイラーマジックによって行っている。

一時オブジェクトの寿命延長を関数やクラスのサブオブジェクトを経由してもユーザーが行えるようにする寿命修飾子を提案しているのだが、どう考えても非現実的な机上の空論のクソ機能にしか思えない。ABIに変更をもたらすのもクソだ。真面目に読んで損をした。

P0067R0: Elementary string conversions

整数と文字列、浮動小数点数と文字列間の相互変換のみを行うライブラリ、to_string, from_stringの提案。

JSONやXMLなどのテキストベースのフォーマット利用が拡大している現代では、数値と文字列間の高速な変換が必要である。変換はinternationalizationを行う必要はない。

そのためには、以下の要件を満たす必要がある。

  • フォーマット文字列の実行時パースを行わない
  • インターフェース上動的メモリ確保をしない(大抵のインターフェースは必要とする)
  • localeを考慮しない
  • 関数ポインターを経由した関節アクセスを行わない
  • バッファーオーバーランを防ぐ
  • 文字列をパースするとき、エラーと有効な数値は区別できる
  • 文字列をパースするとき、空白文字などが静かに無視されたりしない

既存の標準ライブラリで提供されている方法はすべて、localeに依存している。

また、変換処理は、教科書通りの実装では速くない。高速な実装方法が多数ある。

この提案では、極めて原始的なインターフェースを採用している。char *型のイテレーターベースのインターフェースだ。数値を文字列に変換するにはto_string、文字列を数値に変換するにはfrom_stringを使う

char * to_string(char * begin, char * end, T value, その他の引数) ;

T型の数値valueを文字列に変換したものが、[begin, end)に書き込まれる。戻り値は書き込んだ最後の要素の一つ次のポインターになる。文字列を書き込むスペースが足りない場合、endが返される。スペースが足りない場合に書き込まれる内容は未規定である。

整数型は以下のような関数になる。

char * to_string(char * begin, char * end, T value, int base = 10);

baseの範囲は2から36までで、10から36までの数値はaからzで表現される。valueが負数の場合、マイナス符号が先頭に書き込まれる。

浮動小数点数型は、変換する文字列のフォーマットごとに、以下のような2つの関数が提供されている

31415e-4のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value);

valueが負数の場合はマイナス符号が先頭に書き込まれる。文字列は10進数で、整数部が少なくともひと桁あり、オプショナルで小文字eに続いてexponentが記述される。表現は最小文字数かつ、from_stringによって元のvalueが正確に復元できるものとなる。valueが無限大の場合、infか-infとなる。NaNの場合、nanか-nanとなる。

3.1415のようなフォーマットの文字列に変換

char * to_string(char * begin, char * end, T value, int precision);

小数部の桁数はprecisionになる。

文字列を数値に変換するfrom_stringは以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, 残りの引数 );

戻り値はパターンにマッチしない最初の文字へのポインター。

整数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec, int base = 10);

浮動小数点数型に変換する場合は以下の通り

const char * from_string(const char * begin, const char * end, T& value, std::error_code& ec);

文字列に変換する際のinfやnanも正しく認識される。

[PDF] P0068: Proposal of [[unused]], [[nodiscard]] and [[fallthrough]] attributes

タイトル通り、[[unused]], [[nodiscard]], [[fallthrough]] attributeの提案。

[[unused]]

[[unused]]が指定されたエンティティは、何らかの理由で使われていないように見えるという意味を持つ。これによって、実装による未使用警告を抑制できる。

int x ; // 警告、xは未使用
[[unused]] int x ; // 警告が抑制される

[[nodiscard]]

[[nodiscard]]が指定された関数は、戻り値がdiscardされてはならないことを意味する。戻り値が捨てられた場合、警告が出る。

[[nodiscard]] bool do_something_that_may_fail() ;

int main()
{
    do_something_that_may_fail() ; // 警告、戻り値が捨てられてい

    // OK
    if ( do_something_that_may_fail() )
    {
    }
    else
    {
    }
}

[[nodiscard]]が型に指定された場合、その型を戻り値の型に使う関数が[[nodiscard]]指定される。


[[ nodiscard]] class error_code { ... } ;

// 関数fは[[nodiscard]]指定される
error_code f() ;

[[fallthrough]]

[[fallthrough]]は文のように記述される。これをfallthrough文という。

[[fallthrough]] ;

[[fallthrough]]はcaseラベルの直前に書く。これによって、複数のケースを突き抜けたコードに対しての警告を抑制できる。

switch( x )
{
case 0 :
    do_something() ;
    // 警告、fallthrough
case 1 ;
    do_something() ;
    [[fallthrough]]
    // 警告なし
case 2 ;
    do_something() ;

} 

いい提案だ。

[PDF] P0069R0: A C++ Compiler for Heterogeneous Computing

GPU用に現在提案されているGPUのサポートも視野に入れた機能をC++コンパイラーに実装した経験の報告書。

開発したのコンパイラー、HCC(Heterogeneous C++ Compiler)は、Clang/LLVMベースのコンパイラーだ。HCCはホストプロセッサー用のコード(x86)とアクセラレーター用のコード(AMD GPU/HSA)を同時に生成する。HCC自体は公開されていないようだ。

問題となるのは、コードのどこの部分をどちらのプロセッサー用のコードで生成するかだ。すべてのコードをすべてのプロセッサー向けにコード生成を行うと、コンパイル時間が増大する上に、プロセッサーごとの特性もあるので、逆に遅くなる。

C++提案では、特定の領域に対して機能を制限してコンパイラーにヒントを与える機能があるが、これはあまり役に立たなかったと報告している。

HCCがかわりに開発したのは、プログラマーがコンパイラーがコード中の並列部分を指示できる機能云々

なんだかOpenMPのようだ。

モダンなGPUはC++の機能のほとんどを実行できるようになった。たとえば、間接的な関数呼び出し、バイト単位のメモリアクセス、基本型、アライメントされていないメモリへのアクセス、スタック、gotoなどだ。

HCCはC++のほとんどの機能をアクセラレーター向けにコード生成できる。例外はtry/catchブロックなどだ。try/catchブロックの実装は技術的に可能であるが、現在のところ実装していない。論文著者は、アクセラレーター向けにC++のサブセットを定義する必要はないと報告している。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-09

C++標準化委員会の文書: P0050R0-P0059R0

[PDF] P0050R0: C++ generic match function

variantやoptionalなど、type erasureを使って複数の型を格納するsum typeは、それぞれが独自のvisitorインターフェースを提供している。例えば、std::experimantal::variantはvisitを、Boost.variantはapply_visitorを提供している。

ライブラリごとに異なる方法で要素にアクセスするのは汎用的ではない。そこで、この提案では、汎用的にtype erasureされた要素にアクセスできるmatch関数テンプレートを提案している。

matchは以下のように宣言されている。

template <class R, class F>
R match(ST const&, F&& );

戻り値の型Rがないバージョンもある。

matchはライブラリによるcustomization pointであり、ライブラリが対応させる。

たとえば、Boost.variantをmatchに対応させる場合は、以下のような実装になる。

namespace boost {
template <class R, class F, class ...Ts >
R match(variant<Ts...> const& v, F&& f)
{
    return apply_visitor(std::forward<F>(f), v); }
}

std::variantでvisitを使うには、以下のように書く。

std::experimantal::variant< int, double > a = 2 ;
std::experimental::visit( []( auto x ) { }, a ) ;

これをmatchで書き直すと、以下のようになる。

std::experimental::variant< int, double > a = 2 ;
std::experimental::match( a, []( auto x ) { } ) ;

variantのvisitがvisitorを先に書くのに対し、matchは後に書く。

また、P0051が提案するoverloadライブラリを使うと、以下のように書くこともできる。

std::experimental::match( a, overload( 
    []( int x ) { },
    []( double x ) { } ) ;

複数のsum typeに対するvisitorも書ける。

std::experimental::variant<int, X> a = 2;
std::experimental::variant<int> b = 2;
std::experimental::visit(overload(
    [](int i, int j )
    {
    },
    [](auto i, auto j )
    {
        assert(false);
    }
   ), a, b);

現在、戻り値の型の決定方法、customization pointとしてどの関数を洗濯するかmatchより既存のvisitの方がわかりやすいか、引数の順序などの議論があるらしい。

[PDF注意] P0051: C++ generic overload function

選択した関数オブジェクト群を保持する関数オブジェクトを返し、呼び出すとオーバーロード解決して最適関数を呼び出してくれるoverload関数テンプレートの提案。

auto o = overlord(
    []( int ) { },
    []( double ) { },
    []( std::string ) { } ) ;

o( 0 ) ; // int
o( 0.0 ) ; // double
o( "hello" ) ; // std::string

簡易的な実装は以下の通り。

template < typename ... Functors >
struct overloader ;

template < >
struct overloader < >
{
    void operator () () { }
} ;

template < typename Functor0, typename ... Functors >
struct overloader< Functor0, Functors ... >
    : Functor0, overloader< Functors ... >
{
    overloader( Functor0 && functor0, Functors && ... functors )
        : Functor0( std::forward<Functor0>(functor0) ),
          overloader< Functors ... >( std::forward<Functors>( functors ) ... )
    { }

    using Functor0::operator () ;
    using overloader< Functors ... >::operator () ;
} ;

template < typename ... Functors >
overloader < Functors ... >
overload( Functors && ... functors )
{
    return overloader< Functors ... > ( std::forward<Functors>(functors) ... ) ;
}

渡されたすべての関数オブジェクトから派生し、すべての関数オブジェクトのoperator ()をusing宣言で最も派生したクラスのスコープに持ち込むことにより、オーバーロード解決を実現している。

この実装では、関数オブジェクトが関数ポインターやメンバー関数ポインターの場合は動かないが、ラッパーを挟めばオーバーヘッドなしに対応できる。提案では関数ポインターやメンバー関数ポインターもサポートするようになっている。

興味本位でざっと実装したら以下のようになった。

// std::invoke
  template<typename Functor, typename... Args>
  typename std::enable_if<
    std::is_member_pointer<typename std::decay<Functor>::type>::value,
    typename std::result_of<Functor&&(Args&&...)>::type
  >::type invoke(Functor&& f, Args&&... args)
  { 
    return std::mem_fn(f)(std::forward<Args>(args)...); 
  }
   
  template<typename Functor, typename... Args>
  typename std::enable_if<
    !std::is_member_pointer<typename std::decay<Functor>::type>::value,
    typename std::result_of<Functor&&(Args&&...)>::type
  >::type invoke(Functor&& f, Args&&... args)
  { 
    return std::forward<Functor>(f)(std::forward<Args>(args)...); 
  }

  template<typename Return, typename Functor, typename... Args>
  Return invoke(Functor&& f, Args&&... args)
  {
    return invoke(std::forward<Functor>(f), std::forward<Args>(args)...);
  }

// simple callable wrapper
template < typename Functor, typename ... Args >
struct sfun_impl
{
    Functor f ;

    sfun_impl( Functor f ) : f( f ) { }

    auto operator() ( Args && ... args )
    {
        return invoke( f, std::forward<Args>(args)... ) ;
    }
} ;

// function pointer
template < typename R, typename ... Args >
sfun_impl< R (*)( Args ... ), Args ... >
sfun( R (*p)( Args ... ) )
{
    return sfun_impl< R (*)( Args ... ), Args ... >( p ) ;
}

// member function pointer with class pointer
template < typename R, typename C, typename ... Args >
sfun_impl< R (C::*)( C *, Args ...), C *, Args ... >
sfun( R (C::* p)( C *, Args ...) )
{
    return sfun_impl< R (C::*)( C *, Args... ), C *, Args ... >( p ) ;
}

// member function pointer with class reference
template < typename R, typename C, typename ... Args >
sfun_impl< R (C::*)( C &&, Args ...), C &&, Args ... >
sfun( R (C::* p)( C &&, Args ...) )
{
    return sfun_impl< R (C::*)( C &&, Args... ), C &&, Args ... >( p ) ;
}

// just to pass class type T
// lazy conditional can be used instead.
template < typename T >
void sfun( T && ) ;


template < typename T >
using sfun_if =
    typename std::conditional<
        std::is_function< typename std::remove_pointer< typename std::remove_reference<T>::type >::type >::value ||
        std::is_member_function_pointer<T>::value || 
        std::is_member_object_pointer<T>::value, 
            
        decltype(sfun( std::declval<T>() ) ), T >::type ;

// primary template
template < typename ... Functors >
struct overloader ;

// terminator
template < >
struct overloader < >
{
    void operator () () { }
} ;

template < typename Functor0, typename ... Functors >
struct overloader< Functor0, Functors ... > : sfun_if<Functor0>, overloader<Functors...>
{
    overloader( Functor0 && func0, Functors && ... funcs )
        : sfun_if<Functor0>( std::forward<Functor0>(func0) ),
          overloader<Functors ...>( std::forward<Functors>(funcs) ... )
    { }

    using sfun_if<Functor0>::operator () ;
    using overloader<Functors...>::operator () ; 
} ;



template < typename ... Functors >
overloader< Functors ... >
overload( Functors && ... functors )
{
    return overloader< Functors ... >( std::forward<Functors>(functors) ... ) ;
} 

また、提案では、登録順で呼び出し可能な最初の関数を呼び出す、first_overloadも提案している。


auto fo = first_overload (
    []( void * ) { },
    []( double ) { },
    []( int ) { } ) ;

fo( 123 ) ; // double

これは、おそらくtupleのようなものに格納した上で、is_callable(実装方法はN4446を参照)のようなメタ関数を適用するのだろう。

便利なライブラリだ。

[PDF] P0052R0: Generic Scope Guard and RAII Wrapper for the Standard Library

汎用RAIIラッパーとライブラリ版finallyの提案。

ヘッダーファイル<scope>をincludeすることで使える。

scope_exit

scope_exitは、ライブラリ版のfinallyだ。ブロックスコープを抜ける際に指定した関数オブジェクトを実行してくれる。

void f()
{
    scope_exit s( [](){/* 処理 */} ) ;

    // デストラクターが自動的に処理を実行してくれる
}

自動ストレージ上に確保する習慣をつけさせるため、make_scope_exit関数も用意されている。

unique_resource

汎用RAIIラッパー


void f()
{
    auto fp = make_unique_resource( fopen( ... ),
        []( FILE * fp )
        {
            if ( fp != NULl )
                fclose( fp ) ;
        } ) ;

        fread( ..., fp.get() ) ;

    // デストラクターがデリーターを実行してくれる
}

はやくほしい。

P0053R0: C++ Synchronized Buffered Ostream

同期バッファつきostream、osyncstreamの提案。

ストリーム出力は競合を起こさないと規定されているが、結果については規定されていない。そこで、出力をバッファして、途中に他の出力を挟まずに一括して出力できる機能の提案。


{
    std::osyncstream bout( std::cout ) ;
    bout << "hello," << "world" << std::endl ;
    // boutのデストラクターはバッファを一括して出力
}

P0054R0: Coroutines: Reports from the field

現在提案中のコルーチン、レジューム可能関数を実装しているコンパイラーがでてから丸一年になる。実際の使用経験から得られた知見をもとに、設計を見直す提案。様々な変更が提案されている。

P0055R0: On Interactions Between Coroutines and Networking Library

ネットワークライブラリの非同期処理は、futureによる継続ベースの処理と、コールバック関数による処理ができるが、コルーチンを使うとオーバーヘッドがさらに削減できるのでコルーチンに対応させる提案。

また、直接関係ないが、コルーチンは[[nodiscard]]の恩恵を受けるということが書かれていて面白い。await f()と書くべきレジューム可能関数fを、f()のみで使った場合はコンパイルエラーにできる。

P0056R0: Soft Keywords

ソフトなキーワードを導入する提案。

C++標準化員会の会議では、よく、「いいヤツは全部使われてるから、変なヤツを使わなきゃならん」という発言が行われる。ヤツというのは、キーワードのことだ。

最近の提案に含まれているキーワードを、既存のコードベースと比較するだけでも、await, concept, requires, synchronized, module, inspect, whenは、すでに変数名やフィールド名や引数名や名前空間名として使われている。

そこでこの提案では、ソフトキーワードを提案している。ソフトキーワードは、std名前空間に暗黙に定義されるエンティティである。

void yeild(int) ;

auto f()
{
    yeild( 1 ) ; // ::yeild
    std::yeild( 2 ) ; // キーワード
}

名前の衝突がない場合、using宣言を使うことでスコープ解決演算子を省略できる。

auto f()
{
    using std::yeild ;
    yeild( 3 ) ;
}

しかし、using宣言を書くのも面倒だ。提案では、ソフトキーワードに対する名前検索が何も見つけられなかった時に、ソフトキーワードだと認識するようにする提案もしている。

auto f() { yeild( 1 ) ; // std::yeild } void yeild( int ) ; auto g() { yeild( 2 ) ; // ::yeild }

ただし、この変更を受け入れた場合でも、テンプレートの場合は、依存名の解決のために、std::が必要だ。


template < typename T >
auto f( T t )
{
    yeild( 0 ) ; // 非依存名、std::yeild

    std::yeild( t ) ; // 依存名
}

typenameやtemplateと同じで、Two-Phase lookupについてはすでに知られているので、テンプレートコードを書くプログラマーならば対処できる些細な問題だろう。

ただし、using namespace stdを使った既存のコードが壊れる可能性がある。これを防ぐには、using directiveはソフトキーワードには働かない例外条項を付け加えればよいという提案もしている。

確かに便利だが、ものすごいツギハギ感がある。

[PDF] P0057R0: Wording for Coroutines (Revision 3)

コルーチンの文面案。

まだ、awaitとyeildのキーワードが固まっていない。

久しぶりに文面案を見ると、だいぶ変わっている。

まず、キーワードとして、await-keywordとyeild-keywordがある。このキーワードはプレイスホルダーで、将来的には何らかのキーワードが割り当てられる。

await式は、オペランドの式の実行が完了するまで、コルーチンの評価を停止させる。

auto read_from_disk()
{
    await read_gigabytes_of_file() ;
}

yeildキーワードは、await式を適用した後に、コルーチンのpromiseオブジェクトpに対して、p.yeild_value(yeilded value)を呼び出す。つまりシンタックスシュガーだ。yeild式は、generatorを作るのに使える。

auto gen( )
{
    for ( int i = 0 ; i < 10 ; ++i )
        yeild i ;
}

しかし、戻り値の型がよくわからない。await式のみが使われているとき、await式とyeild式が両方使われているとき、yeild式のみが使われているときで、型が変わる。どうも、その型はコルーチンpromiseを実装した型で、どうもユーザーが実装しなければならないようだ。

[PDF] P0058R0: An Interface for Abstracting Execution

様々な実行媒体を隠匿して扱えるライブラリの提案。

実行媒体には、スレッドプールを始めとして、ファイバーやGPUスレッドやSIMDなど様々なものがあるが、この提案では、それらの多種多様な実行媒体を効率的に隠匿できる設計にしていると主張している。

[PDF] P0059R0: Add rings to the Standard Library

リングアダプターの提案。既存のコンテナーをリングと呼ばれる固定サイズのキューとして扱えるコンテナアダプター。

既存のキューアダプターは、dequeやlistの上に構築されていて、動的にサイズを増加させる。これはメモリを断片化させる問題がある。リングアダプターは、arrayやvectorといった連続したストレージを使うコンテナーのアダプターで、動的にサイズを増やすことがない。

すでにBoostにはcircular_bufferがあるが、これはあまりにもでかすぎる。双方向である上にランダムアクセスイテレーターまで提供している。

インターフェースは既存のqueueを模倣している。ただし、pushとemplaceに加えて、try_pushとtry_emplaceが追加されている。これはリングが満杯の時に呼び出すと失敗する。

arrayがデフォルトのコンテナーになっているfixed_ringと、vectorがデフォルトのコンテナーになっているdynamic_ringがある。dynamic_ringという名前だが、サイズを実行時の初期化時に指定できるだけで、サイズがあとから伸長されることはない。後からサイズを増やしたければ、すでにあるqueueにvectorを指定して使えばいいだけだ。

ドワンゴ広告

明日はFallout 4が解禁されるために有給を取る。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-11-05

GoogleのエンジニアがAmazonで片っ端からType-C USBケーブルをレビューしている

Amazon.com: Profile for Benson Leung

GoogleのChromebook PixelのエンジニアであるBenson Leungが、アマゾンで売られているType-C対応を謳っているUSBケーブルとアダプターを片っ端からレビューしている。

なぜそんなことをしているのか。Googleの製品であるChromebook PixelはUSB Type-Cによる充電ができるが、巷に出回っている自称USB Type-C対応の製品の多くが、USB規格に違反していたり、十分な性能がない欠陥品だったりするからだ。

そもそも、USB Type-C規格は、3A, 5V, 15Wの電力供給ができる。3Aの電力供給は、途中の経路がすべてUSB Type-C対応の製品である場合に限る。例えば、途中にUSB 2.0などのレガシーケーブルを挟む場合は、USB Type-C規格準拠のケーブルは、それを検知し、3Aが供給できない状態にあることを通知するようになっている。

問題は、世の中の自称USB Type-C対応のUSBケーブルとアダプターには、Type-C規格で定められた方法で3A対応を謳いながら、実際には3Aもの電流を流せない粗悪な製品が多い。

そのような粗悪なUSBケーブルを使って充電を行うと、充電ができないか、充電はできるがUSBケーブル、ハブ、USBホストが損傷する危険がある。

以下はそのような粗悪な製品のレビューの一例である。

Amazon.com: Benson Leung's review of For the Christmas Gift !Monba Type c to US...

私はBenson、GoogleでChromebook PixelとPixel C開発部に所属している。このケーブルを購入して、様々なUSB micro-b typeのケーブルを使ってChromebook Pixelで検証してみたが、このアダプターはChromebook Pixelを充電できない。

詳しく調べてみると、このケーブルはUSB Type-C規格バージョン1.1に正しく準拠していない。規格はusb.orgのdevelopers/usbtypecにある。

このアダプターがChromebook Pixel 2015を充電dけいないのは、アダプターがC-Cラインを浮かせているからだ。規格ではRpがvbusをpullupしてケーブルがレガシーアダプターかケーブルであることを通知するよう規定している。

"USB Type-C Specification Release 1.1.pdf"という名前のドキュメントのセクション4.11と以下の注釈を参照されたし。

1. USB Type-CからUSB 3.1 Standard-Aケーブル、USB Type-CからUSB 2.0 Standard-Aケーブル、USB Type-CからUSB 2.0 Micro-Bアダプター、USBホストに接続されているUSB Type-C captiveケーブルに接続されているUSB Type-Cプラグは、56 kΩ ± 5% を使うことによって、V BUSとGNDにおけるIR低下に対応すること。

この製品はUSB Type-CをUSB 2.0 Micro-Bに接続するものであるから、製造者は56kΩの抵抗をpullupとして使わなければならない。このアダプターは使っていない。

Pixelと互換性のあるUSB Type-C規格に準拠したUSB Type-Cアダプターを探しているならば、このアダプターを使ってはならない。

ようやくまともな製品を発見した時のレビューは、記述に義憤が感じられる。

Amazon.com: Benson Leung's review of FRiEQ Hi-speed USB 3.1 Type C Cable - 3.3f...

これは私がアマゾンで発見できた最初のUSB規格準拠のSuperSpeedケーブルだ。

Amazon.com: Benson Leung's review of Type C, iOrange-E™ 6.6 Ft (2M) Braid...

このiOrange-E USB Type-AからType-Cへのケーブルは、サードパーティ(GoogleやAppleから提供されているものではないという意味)によって製造されたUSB製品における私の期待を上回るものである。

コネクターの品質はとても良い...略

技術面としては、このケーブルはUSB Type-C規格バージョン1.1に準拠している。これは正しくvbusに56kΩ pullupによって、正しくレガシーUSBケーブルのType-Cであると通知している。充電用としては、このケーブルはBC 1.2充電速度に対応している。最大で2.4A 5Vだ。

アマゾンで買える他のケーブルは、3A充電を対応していると主張しているが、USB Type-C規格に準拠しておらず、3A充電に対応しているかどうかを識別する抵抗を使わず、3Aに対応しないType-Aコネクターがもう一方に接続されているかどうかを確かめるすべがない。3A充電対応を謳うA-C変換ケーブルはすべて、レガシーUSBハブ、PC,充電器を破損する恐れがある。このiOrange-Eのケーブルは正しく規格に従っているため、その恐れはない。

さて、まとめると、これはいいケーブルだ。ようやくType-C規格に準拠したサードパーティケーブルが見つかった!

調査には以下の機器を使っているらしい。

USB-PD Sniffer - The Chromium Projects

2015-11-03

Linus Torvals、クソコードにブチギレ

Linux-Kernel Archive: Re: [GIT] Networking

Linus TorvalsがGCCの独自拡張を使った整数演算のオーバーフロー検知コードがあまりにクソすぎるためにブチギレしている。

On Wed, Oct 28, 2015 at 3:32 PM, David Miller <davem@xxxxxxxxxxxxx> wrote:

リリースサイクルのこの後半に入れるのはちょっと怖いと思われるかもしれないが、小規模なドライバーの修正をあちこちに施しただけだよ。

マジかよテメーら、こりゃクソだ。

コンフリクトはGCCの新しいクソヘッダーファイルのせいなんだが、俺がブチギレてるのはそこじゃなくてこいつがクソなせいだ。

net/ipv6/ip6_output.cの以前のコードはこれだ。

mtu -= hlen + sizeof(struct frag_hdr);

そして、こいつが新しい、「改良された」コードだ。最近流行りのコンパイラーマジックな組み込み機能を使っていて、その機能がない場合に備えて泥臭いラッパー関数を使ってる。

if (overflow_usub(mtu, hlen + sizeof(struct frag_hdr), &mtu) ||
mtu <= 7)
goto fail_toobig;

この上記のコードに対して、

(a) 読みやすい

(b) 効率的(しかもコンパイラーマジックまでサポートしてやがるぜ)

(c) とても安全

なんて考える奴は無能だしこの場にいる資格すらねぇ。

上記のコードはクソだ。しかもクソコードを生成しやがる。見た目がクソでしかも不必要なクソだ。

このコードは、簡単に、たった一つの、わかりやすい条件分岐だけで書けるし、しかもコンパイラーはもっとマシなコードを吐き出す上に、コードは見た目にマシでわかりやすい。なんでこうしないんだ。

if (mtu < hlen + sizeof(struct frag_hdr) + 8)
goto fail_toobig;
mtu -= hlen + sizeof(struct frag_hdr);

これは同じ行数だし、何をするか誰も知らんキチガイみたいなヘルパー関数使う必要がないし、実際に何をするか明白だ。

こっちの明白版の方が読むのも簡単だし理解するのも簡単だ。反論はあるか?

いやマジで、なんで2つも条件分岐を使うマヌケなコードと、ピカピカの真新しいコンパイラーサポートを必要とする非標準の関数を使って、まともに見えないコードでクソコードを吐かせる必要があるのか、誰か教えてくれよ。

他では使う必要がないピッカピカのこの関数は、単にコンパイラーがシコってるだけだろ。

ああ、それから、"hlen + xyz"という式がオーバーフローしたら、やっぱりオーバーフローは起こるだろ。だが、"overflow_usub()"コードにも同じ問題がある。というわけでその心配をしているのならば、そもそも最初から間違ってるってこった。

この手の完全にアホくさいクソが存在する理由がわからん。

いや、この完全にキチガイな上にrc7というこの時期にコンフリクトを起こすようなものを俺がpullするわけがねーし、読めないほどクソになる理由すらないんだが、どういうこった?

このコードは、新しい"overflow_usub()"コードを使いたいために設計されたかのように思える。関数を使う言い訳としてな。

頭イカれてる言い訳にすらならんぞ。

申し訳ございませんが、こちらと致しましてもこのようなよろしくないコードのために好ましくないインターフェースを追加するわけにはまいりません。

まあ、これがネットワークレイヤーの仲にいたならば、俺も気が付かなかっただろうよ。だが俺が気づいたからには、こいつは絶対pullしねぇ。いいかお前らみんなに知っていてもらいたいんだが、こういうコードは絶対受け付ける気がない。こういうコードが最近流行りのオーバーフロー検出を使っているから「安全」だとか「セキュア」だとか考える奴は、場違いにも程があるし、冗談にしても笑えねぇ。

このクソが成し遂げた唯一のことは、まともな人間が読めないコードを作り出しただけだ。

取り除け。俺はこんなクソをもう二度と見たくねぇ。

Linus。

2015-11-01

MicrosoftがPDBフォーマットの構造体定義のソースコードを公開

Microsoft/microsoft-pdb

MicrosoftがPDBファイルを扱うのに使っているMSVCのソースコードの一部をMITライセンスで公開している。ソースコード、cvinfo.hの中身の大半は、構造体定義のようだ。

このレポジトリは、MicrosoftにおけるPDB(プログラムデータベース) シンボルファイルフォーマットの情報を載せている。

PDBフォーマットはこれまで公式のドキュメント化されておらず、他のコンパイラーやツールセット(例えばClang/LLVM)がWindows上で動作したり、Visual Studioデバッガーとのやり取りを困難にしていた。MicrosoftはオープンソースコンパイラーがWindowsプラットフォームで動作するのを支援する。

このレポジトリの中身の大半は、VC++コンパイラーツールセットにおける実際のソースコードである。ソースコードは究極のドキュメントである(^_^;)。役に立てば幸いだ。もし、他の情報が必要であれば、Issuesを投げて知らせてもらいたい。

シンボルファイルフォーマットが今まで公式に非公開だったことに驚いている。