gccとVC

x86/x64環境で開発する上で, gccとVCはどちらも非常に優れたC/C++コンパイラです. ただLinuxとWindowsのどちらの環境でも動作するようなC/C++コードを書くためには, gccとVC, およびそれらが動作するOSの違いが問題になることがあります. ここではそれらの違いについてまとめていきたいと思います.

なお説明を簡単にするためにマクロを多用していますが実際には可能なら別の手段をとるか, 名前がぶつからないような命名規則に従ったマクロ名をつけることをお薦めします.

間違いなどございましたら@herumiかメール(herumi@nifty.com)にお願いします.


対象
ここではx86/x64 CPU上で動作する32bit/64bit Linux/Windows用のgcc/VCを対象とします. 動作確認をしているコンパイラのバージョンはgcc 4.4/4.5, Visual Studio 2008 SP1, 2010です. 随時それ以外のバージョンについて言及することもあります.
定義済みマクロ
それぞれのコンパイラには, 予め処理系依存のマクロがいくつか定義されています. そのうちいくつかについて説明します.

環境特定マクロ

gccとVCのどちらでも動作するコードを記述するためには, コンパイラや環境を特定しなければならない場合があります.

環境特定マクロ
32bit 64bit
VC _WIN32, _MSC_VER _WIN64
gcc __GNUC__, __linux__, __i386__ __x86_64__

Windowsであることを特定するなら_WIN32ですが, VCであることを特定したいなら_MSC_VERの方がよいでしょう. 前者ではWindows上で動作するmingwなども含まれてしまいます. _WIN32は64bit Windowsでも定義されていることに注意してください. VCとLinuxのgccしか考慮しないなら_WIN32が定義されているか否かでWindowsとLinuxを判別してもよいでしょう.

#ifdef _WIN32
    #ifdef _WIN64
        // 64bit Windows
    #else
        // 32bit Windows
    #endif
#else
    #ifdef __x86_64__
        // 64bit Linux
    #else
        // 32bit Linux
    #endif
#endif

コンパイラのバージョン

VCでは_MSC_VERがコンパイラのバージョンを表します. VC2008(VC9)は1500, VC10は1600です.

gccでは__GNUC__がメジャーバージョン, __GNUC_MINOR__がマイナーバージョンを表します. たとえばgcc 4.3.2では__GNUC__ = 4, __GNUC_MINOR__ = 3です. あるバージョン以上を要求したい場合は __GNUC_PREREQ(major, minor)を使います.

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
   /* VC10以降 */
#endif

#if defined(__GNUC__) && __GNUC_PREREQ(4, 3)
   /* gcc 4.3以降 */
#endif

gccがC++0xモード(-std=c++0xや-std=c++11)でコンパイルされているか否かは, __GXX_EXPERIMENTAL_CXX0X__ や__cplusplusが201103L以上であるかで判別できます.


定義済みマクロの取得

gccでは-E -dMオプションをつけてると定義済みマクロ一覧が表示されます.

echo "" | g++ -x c++ - -E -dM

VCにはそういうオプションは無いようですが, マニュアルによると次のマクロが定義されているようです( Predefined Macros ).

有用なマクロ
いくつかの有用なマクロを紹介します(途中).

gcc用マクロ
_FILE_OFFSET_BITS=64
32bit環境でも4GBを超えるファイルを扱えるようにします. 巨大なファイル参照.

VC用マクロ
_WIN32_WINNT
Windowsのバージョンを表します. 定義しないと新しいVCがサポートする新しいOSのバージョンが設定され, 場合によっては古いOSで動作しない実行バイナリが生成されることがあります. たとえば古いXpやWindows Server 2003をサポートしたいなら_WIN32_WINNT=0x501とすべきです. なお_WIN32_WINNTを設定するとWINVERも同じ値に設定されます.
Windowsのバージョンとマクロの値
OS
Windows 7 0x601
Windows Server 2008/Vista 0x600
Windows Server 2003SP1/ Xp SP2 0x502
Windows Server 2003/Xp 0x501
Windows 2000 0x500
_SECURE_SCL=0
STLのiteratorなどの範囲チェックを行わないようにします. たとえばstd::vectorでは配列のアクセスで範囲チェックをさせたい場合はat()を使い, そうでない場合は[]を使うという使い分けをすると思います. しかしVCのデフォルトではリリースモードで[]を使ってもチェックが入り, 範囲外をアクセスすると例外ハンドラが呼ばれます(例外ハンドラ).
(注意) VC2010ではReleaseモードではデフォルトが_SECURE_SCL=0になりました(thanks to 0xbadfca11さん).
#include <vector>
int main()
{
    std::vector<int> v;
    v[5] = 3; /* 例外ハンドラ発動 */
}
しかしチェックのために1~2割(場合によってはもっと)速度が遅くなるし, gccではチェックしないのですから(そういったバグは別の手段で十分取り除けるならば)定義しておくとよいでしょう. なお, boost::regexのランタイムは普通_SECURE_SCL=1でコンパイルされていて, _SECURE_SCL=0のコードと混ぜると動作しないようです. したがってこのオプションを利用する場合はboost::regexのリコンパイルが必要です.
>bjam --toolset=msvc-9.0 cxxflags="-D_SECURE_SCL=0 -GS-" threading=multi link=static runtime-link=static,shared variant=release,debug stage
NOMINMAX
デフォルトではwindows.h(正確にはWinDef.h)をincludeするとmin, maxのマクロが定義されてしまい, STLのmin, max template関数とぶつかってしまいます. NOMINMAXを定義するとmin, maxマクロが定義されなくなります.
WIN32_LEAN_AND_MEAN
windows.hヘッダの一部の機能(NOCRYPT, NOGDI含む)の読み込みを抑制することでコンパイル速度を向上させます. gccと共用したいようなソースで定義して困ったことはないです. とは言ってもboostヘッダ一つ読み込めば誤差の範囲な気もしますが…. 上記NOMINMAXの他にもいくつか抑制用マクロがあるようです( Faster Builds with Smaller Header Files ).
__FUNCTION__
関数名の文字列になります. C99/C++0xでは__func__というのが導入されていますが, VCでは使えません. 代わりにこれを使ってください. これはgccでも利用可能です. なお, VCでは同様の機能にシンボル名を取得する__FUNCDNAME__, 引数の型も含めて取得できる__FUNCSIG__もあります.
struct A {
    void func(int)
    {
        puts(__FUNCDNAME__); // "?func@A@@QAEXH@Z"
        puts(__FUNCSIG__); // "void __thiscall A::func(int)"
        puts(__FUNCTION__);// "A::func"
    }
};
_CRTDBG_MAP_ALLOC
デバッグ時にヒープ管理機能を有効にします. 更にプログラム開始時に次の関数を呼び出しておくとメモリリーク箇所なども表示してくれます. Linuxのvalgrindには及びませんが, そこそこ便利です. これらの関数はReleaseモード時は消えます.
#include <crtdbg.h> // for _CrtDumpMemoryLeaks

static void MIE_MEM_Init()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_DELAY_FREE_MEM_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
}

#include <stdlib.h>
int main()
{
    MIE_MEM_Init();
    int *p = (int*)malloc(8);
}
実行結果
Detected memory leaks!
Dumping objects ->
c:\tmp\t.cpp(23) : {75} normal block at 0x00397980, 8 bytes long.
 Data: <        > CD CD CD CD CD CD CD CD
Object dump complete.
コンパイルオプション
ここではコンパイルオプションについて説明します(途中).

警告オプション

基本的にコンパイラの出す警告は0にするのが理想です. コンパイラに文句の言われないようなコードを心がけることは間違いも少なくなり, コーディング能力もあがりやすいと思います. あと, できるだけ複数のコンパイラで試すとそれぞれの警告の癖も異なるので勉強になると思います.

VCの警告オプションは最低/W3, できれば/W4をつけるのがよいと思います. VCでは警告オプションの抑制をきめ細かく行いやすいので基本的に/W4にして警告を無くし, 煩雑になりすぎるなどの理由でどうしても消したい部分だけ抑制するのがよいでしょう. ちなみにVC2005の頃は非公開っぽかったですが最大の警告は/Wallです. うっとうしいぐらいに沢山警告が出るので私は常用はしませんが, たまに使うと思いもよらないミスが見つかることはあります.

gccでは沢山のオプションがあります. 基本-Wallですが, それ以外に私は次のようなオプションを使うことがあります. これもいいよというのがありましたら教えてください. 詳細はgccのoptionなどを参考にしてください.

gccの警告オプション
オプション 用途
-Wextra いくつかの警告が増えます 昔は-Wでした
-Wformat=2 printfなどの書式文字列のチェック
-Wcast-qual constがとれるポインタへの代入の警告
-Wcast-align アライメントが正しくない可能性のあるポインタキャストの警告
-Wwrite-strings char *buf = "abc";等の警告
-Wfloat-equal 浮動小数点数の同値比較をした
-Wpointer-arith void *p; p++;みたいなの

他には-std=c89 -pedanticなどをつけるとC99で追加された機能かどうかが分かるのでVCでも動作させたいコードをチェックするときには便利です.

>cat t.c
#include <stdio.h>
#include <time.h>

void func(const struct tm *tm)
{
    char buf[128];
    strftime(buf, sizeof(buf), "%C", tm); /* 西暦2桁 */
    puts(buf);
}
>gcc -Wall -c t.c
>/* 何も言われない */

>gcc -Wall -pedantic -c t.c
>t.c:7: warning: ISO C90 does not support the '%C' strftime format
/* C90では非サポートと教えてくれる */
ちなみに上記コードはgcc -x c++ -Wall -pedantic -c t.cとC++でコンパイルすると警告が出ません. %Cが使えるのはC++0x以降であり, 現行のC++は非サポートのはずなのでちょっと変かな

依存関係抽出オプション

gcc -MM <ソースファイル>でそのファイルがインクルードしているファイルを再帰的に調べて出力します. Makefileのソースファイル間の依存関係を示す部分にそのまま使えます. gcc -Mではシステムファイルの依存関係も出力します.

>gcc -MM t.c

たとえばMakefileの最後に

depend: Makefile
    sed -e '/^# DO NOT DELETE/,$$d' Makefile > Makefile.new
    echo '# DO NOT DELETE' >> Makefile.new
    g++ -MM $(SRCS) $(CFLAGS) >> Makefile.new
    mv Makefile Makefile.org
    mv Makefile.new Makefile

# DO NOT DELETE

としておくとmake dependでMakefileがupdateされます(スペースの部分はタブに置換すること). 似たコマンドにmakedependというのもありますが標準で入ってないこともあるのでこちらのほうがよいかもしれません.


デバッグ用オプション

gccでは-gを使います. gdbなどでマクロ名も扱いたいときは-g3オプションを使うとよいです.

>cat t.c
#include <stdio.h>
#define A 5

int main()
{
    printf("A=%d\n", A);
}

// 普通の-gオプションではAの値は見えない
>gcc -g t.c && gdb ./a.out
>start
>p A
>No symbol "A" in current context.

// -g3を使うと見える
>gcc -g3 t.c && gdb ./a.out
>start
>p A
>$1 = 5

VCではReleaseモードでもC/C++のデバッグ情報の形式に/Ziなどを指定し, リンカのデバッグ情報の生成に「はい」(/DEBUG)をしておくと最適化されたコードでもそれなりにデバッガで追いかけられるのでつけておくとよいでしょう.


最適化オプション

SSE用オプション

32bit環境で浮動小数演算でSSEを使わせるにはVCでは/arch:SSE2, gccでは-msse2 -mfpmath=sseのオプションを使います. このオプションによる浮動小数演算への影響は浮動小数点数型を参照してください. 64bit環境では浮動小数演算はデフォルトでSSEを使うようになったのでこれらのオプションは不要です.


AVX用オプション

AVX命令は従来のSSE命令と基本的には混在させないことが推奨されています. CPUがAVX命令に対応していたとしても, AVX命令を有効にしないモードでコンパイルされている場合はAVXを使わない方がよいときもあるかもしれません. あるいはvzeroupperを挿入するしないを切り替えるとか.

gccでは-mavxオプションをつけるとavxを使うようになり, __AVX__が定義されます. VCでは/arch:AVXを指定するとAVXを使うようになりますが, 特別なマクロは定義されないようです(_M_IX86_FP == 3となるかと思ったが違った. これは困る).
cf. MS connect.


演算子の代替表現の抑制

C++では&, |, ^, &&, ||, ~などの演算子の代わりにand, or, xor, bitand, bitorなどのキーワードを使うことができます.

#include <stdio.h>
#include <stdlib.h>
#include <iso646.h> /* VCでは必要 */

int main()
{
    not system("no-command") or puts("die");
}
>g++ t.cpp && ./a.out
>sh: no-command: command not found
>die

ちょっと新鮮(?)ですが, Xbyakみたいなor(), and()などの関数を作りたい場合は余計な機能です(規格なので文句を言う方が悪いのですが). VCでは上記のようにiso646.hをincludeしなければ大丈夫だし, gccの場合は-fno-operator-namesをつけると大丈夫です. clangでも同じオプションで制御できるはずなのですが, 少し前のバージョンではバグっていて動きませんでした. 最新版で修正されているかは確認していません(修正されたようです).

tr1
VC2008でunordered_mapなどはtr1名前空間に存在しています. C++0xではstd空間にあるのでVC2010, VC2008でなるべく同じコードにしたい場合はたとえば次のようにする手があります.
#if defined(MSC_VER) && (_MSC_VER >= 1500)
    #include <unordered_map>
    #if (_MSC_VER < 1600)
        namespace std { using namespace tr1; }
    #endif
#elif defined(__GXX_EXPERIMENTAL_CXX0X__)
    #include <unordered_map>
#else
    #include <boost/unordered_map.hpp>
    namespace std { using boost::unordered_map; }
#endif

std空間に勝手にものを持ち込むのは規格違反なのですが, 暫定処理なので仕方ないでしょう.

日本語のコメント
gccとVCでソースを共用する場合にありがちなのがコメントにShift-JIS(cp932)を書いてトラブルになることです. たとえば次のコードはgccでコンパイルすると何も表示されません.
#include <stdio.h>

int main()
{
    // 分解能
    puts("hello");
}

理由は"分解能"の能の2byte目が\なので次の行と連結されて消えてしまうからです.

>gcc -E t.c
int main()
{


}

これは-finput-charsetオプションで入力文字コードを指定することで解決できます.

>gcc -E t.c -finput-charset=cp932
int main()
{

 puts("hello");
}

あるいはCスタイルのコメント/* ... */を必ず使うというやりかたもあります. なお, 文字列定数の中に直接Shift-JISを入れてしまうとLinux環境ではトラブルになることが多いので避けた方がよいでしょう.

逆にVC側には#pragma setlocale()が存在するのでテキストをUTF8で記述してそれで指定する, というやりかたもできるかと思ったのですが残念ながら現状ではsetlocaleプラグマにUTF8は指定できないようです( Language Strings ).

トライグラフ
??に続く文字で別の文字を表すというトライグラフはC++0xでも生き残りました. 文字列の表示が少し変になるぐらいならまだしも, 正規表現が導入されたことでいろいろトラブルになりそうです.

そもそもトライグラフを知らない方のために少し説明するとプリプロセスよりも前に, ある文字列の置換をテキストレベルで(文字列中だけではない!)行うものです. 単なる文字列置換なので次のコードもvalidです.

int main()
??<??> /* {}と解釈される */
トライグラフ
変換前 ??= ??/ ??' ??( ??) ??! ??< ??> ??-
変換後 # \ ^ [ ] | { } ~

たとえば次のコードはトライグラフが無ければマッチしてほしいものです.

#if 0
#include <regex>
#define NS std
#else
#include <boost/regex.hpp>
#define NS boost
#endif
#include <string>
#include <stdio.h>

int main()
{
    const char *re = "\\??!";
    NS::regex r(re);
    printf("re=%s\n", re);
    std::string str = "xxx!xx";
    NS::smatch m;

    if (NS::regex_search(str, m, r)) {
        printf("match=[%s]\n", m[0].str().c_str());
    } else {
        puts("ng");
    }
}

ところがトライグラフが有効なときはマッチしません. gccは今までは-trigraphオプションをつけないと有効にならず, VC9はトライグラフを無効にするオプションはありませんでした. しかし, g++ -std=c++0xでは逆にトライグラフは有効になり, VC10ではトライグラフはデフォルトで無効になりました(/Zc:trigraphsで有効になる).

トライグラフの結果
VC9 VC10 g++4.6.0 g++4.6.0 -std=c++0x
ng match match ng

VCは「もうトライグラフなんて知らない」という立場, gccはc++0xではより規格準拠にという方針なんでしょうかね. 個人的にはもう消えてよいだろうと思うのでVCの方針がよいように思います.

pragma
便利なpragmaを紹介します.

共通
#pragma once
ヘッダファイルを一度しか読み込まないように指定します. #ifndef + #define .. #endifによるガードの場合, マクロ名を簡単にしてるとうっかりバッティングしまうことがあります. これを使うとそういった危険性を防げます. C++非標準の機能ではありますが, VCとgccで使えます. なおgcc 3.4以前はシンボリックリンクの扱いに難があったようですが, 現在では大丈夫なようです.

VC用pragma
#pragma warning(disable : 警告番号)
警告レベルを変更できます. 一般的には/W4でコンパイルしていて警告がでないようにすべきですが, 中にはいろいろな事情で警告を消すのが難しいときに特定の警告のみをださないようにできます. 結構便利なのですがgccには何故かありません(gcc 4.6.0から遂に同等の機能が追加されました[#pragma GCC diagnostic]).
#pragma comment(lib, "ライブラリ名")
ヘッダのみで提供するライブラリがある特定のライブラリ(たとえばsocket系でws2_32.lib)を指定するとオプションを指定しなくても自動的にリンクしてくれます.
#pragma warning(push)
#pragma warning(pop)
#pragmaで状態を変更する前にpushでそれまでの状態を保存し, popで復元できます. ヘッダファイルで一部の警告レベルを変更したいときなどに使います.

gcc用pragma
#pragma GCC diagnostic ignored 文字列
gccは長らくファイルの一部のコードに対してのみ警告オプションを変更するという機能がなかったのですが, gcc 4.6.0から#pragmaで変更可能になりました. たとえば未使用変数があると-Wall時にunused-variableという警告が出ますが, 下記コードのようにするとf()の中の変数aが使われていないという警告を抑制できます.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
int f()
{
    int a;
    return 3;
}
#pragma GCC diagnostic pop

int main()
{
    int a; /* こちらは警告がでる */
}
attributeとdeclspec
gccの__attribute__やVCの__declspecは様々な細かい制御を可能にします(途中).
C99ではint8_t, uint32_tなどのビットサイズが固定されている整数型が規定されていますが, それ以前のC, あるいは現行のC++にはそういう型は規定されていません(C++0xには入る予定). そしてgccはC99をサポートしていますが, VCはサポートしていないため結果的に環境に応じて切り換える必要があります.

整数型

char, short, intは32bitと64bit, gccとVCのどの環境でもそれぞれ1, 2, 4byteと同じなのでそのまま使えます. longは64bit gccで8byte, それ以外の環境では4byteなので8byteを使いたいためにlongを使うのは避けるべきでしょう. Windowsでは__int64あるいはlong long(古いVCには無い)をtypedefするのがよいでしょう.

64bit定数を指定するには, gccならばC99としてならinttypes.h, C++としてなら__STDC_CONSTANT_MACROSを定義してからinttypes.hをincludeすることでINT64_C()などの64bit定数生成マクロを使えます. VCでもVC10以降はstdint.hが存在してそれをincludeするとINT64_C()が使えます. しかし, 当面は数値の末尾にLL(符号つき)かULL(符号なし)をつけることが簡単でしょう. printfなどで表示する場合は"%lld"などを使います.

厳密に言うと64bit Linuxではlongが64bitなのでLLではなくLでもよいのですが, そうするとprintfでは"%ld"を使う必要があり他の環境と切り分ける必要があり面倒です. 結局, 次のようなヘッダを用意し, 自分で新たに作る関数にはlongは使わない, というのがよいと思います.

#if defined(_MSC_VER) && (_MSC_VER <= 1500)
    typedef __int64 int64_t;
    typedef unsigned __int64 uint64_t;
#else
    /* gcc or VC10 */
    #include <stdint.h>
#endif

配列の個数, STLのsize()の返り値はintではなくsize_t(符号なし)で受けるのがよいでしょう(後者は厳密にはsize_typeですが). gccとVCのどちらでも32bitでは4byte, 64bit環境では8byteとなります. 符号つきのssize_tはC++の規格外(Posix.2)であり, 今のところVCでは定義されていません.

size_tをprintfで表示するのはなかなか悩ましいです. C99(やC++0x)ではsize_tを表示する長さ修飾子zが入ったのでそれを使えばよいです. sizeof(size_t)が4でも8でも正しく表示できます.

 size_t x = 12345;
 printf("%zd\n", x);

しかし, VCでは"z"には今のところ対応していません. 代わりに"I"を使うと同等のことができます.

 size_t x = 12345;
 printf("%Id\n", x);

ただこちらは逆にgccはサポートしていません. 結局のところ32bit環境では多少非効率になりますが, long longにキャストしてlldで受けるのが楽だと思います.
# 64bit Windowsではsizeof(long) = 4なのでlongにキャストするのは意味がありません.
私はintに入ることが分かりきってる場合なら, intにキャストして%dで受けることもよくします.

 size_t x = 12345;
 printf("%lld\n", (long long)x);
 printf("%d\n", (int)x);

ポインタ型はintやlongにキャストするのではなくintptr_tやuintptr_t, ptrdiff_tを使いましょう. printfで表示するにはsize_tと同様の問題があります.

整数型のサイズ
32bit Windows 32bit Linux 64bit Window 64bit Linux
char 1 1 1 1
short 2 2 2 2
int 4 4 4 4
ポインタ 4 4 8 8
intptr_t 4 4 8 8
uintptr_t4 4 8 8
ptrdiff_t4 4 8 8
size_t 4 4 8 8
int64_t 8 8 8 8
long 4 4 4 8
long long8 8 8 8

サンプルコード.

#if defined(_MSC_VER) && (_MSC_VER <= 1500)
    typedef __int64 int64_t;
    typedef unsigned __int64 uint64_t;
#else
    #include <stdint.h>
#endif

#include <stdio.h>
#include <iostream>

#define PUT(x) printf("sizeof(" #x ")=%d\n", (int)sizeof(x))

int main()
{
#ifdef _MSC_VER
    printf("win vc ver=%d\n", _MSC_VER);
#else
    printf("linux gcc ver=%d\n", __GNUC__);
#endif
#if defined(__x86_64__) || defined(_WIN64)
    puts("64bit");
#else
    puts("32bit");
#endif
    PUT(char);
    PUT(short);
    PUT(int);
    PUT(int*);
    PUT(intptr_t);
    PUT(uintptr_t);
    PUT(ptrdiff_t);
    PUT(size_t);
    PUT(int64_t);
    PUT(long);
    PUT(long long);
    PUT(float);
    PUT(double);
    PUT(long double);
    std::cout << -123456789123456LL << std::endl;
    std::cout << 123456789123456ULL << std::endl;
    printf("%lld\n", -123456789123456LL);
    printf("%llu\n", 123456789123456ULL);
}

浮動小数点数型

浮動小数点数に関する仕様は処理系に強く依存します. たとえばsizeof(float) = 4, sizof(double) = 8なのは32bit/64bit, Linux/Windowsに関わらず同じですが, FPUを利用している場合は実際の精度が異なることがあります. SSEを使う環境ではほぼ統一されました. しかしlong doubleについてはそもそもサイズすら異なります. VCではlong doubleはdoubleと同一です. gccではオプションによっても変わるので移植性を考えるなら使わない方がよいでしょう.

32bit環境ではfloat, doubleはFPUレジスタを使う場合とSSEレジスタを使う場合の二通りがあります. 64bit環境ではSSEが使われるようになりました. ただしgccのlong doubleについては64bit環境でもFPUレジスタを使います. 演算精度などの違いについてはfloat型とdouble型も参考にしてください.

浮動小数点数型のsizeof
32bit Windows 32bit Linux 64bit Window 64bit Linux
float 4 4 4 4
double 8 8 8 8
long double8 12* 8 16*
*は-m96bit-long-double, -m128bit-long-doubleなどのオプションで変更可能
浮動小数点数型の精度
Windows(FPU) Linux(FPU) SSE
float 内部53-bit 内部64bit 23-bit
double 内部53-bit 内部64bit 53-bit
long double内部53-bit 64bit -
FPUにおけるいずれの値も変更可能(cf 精度が違う)
ファイル入出力
ここではファイルを扱う場合の注意点について説明します.

テキストとバイナリ

Linuxではテキストファイルとバイナリファイルの区別はありませんが, Windowsにはあります. Windowsのテキストモードではデータ中の\r\n(0x0d 0x0a)は\n(0x0a)に変換されます. 巨大なファイルに対してシークをしようとするとその位置までに何個\r\nがあるかチェックしないと分からないためものすごく遅くなり得ます(直接シークできない). また^Z(0x1a)が来ると読み込みを終了します. 両方の環境で動作するものを考えるとデメリットが多いのでファイルは常にバイナリモードで扱うのがよいでしょう.

バイナリとして扱うにはfopenでは"rb", "wb"など, fstreamではstd::ios::binaryを指定します(Linuxではこれらのオプションは無視されます). stdout, stdinやstd::cinなどはテキストモードでopenされているのでそれらを使う前に変更する必要があります. mainの先頭で

  _setmode(_fileno(stdout), _O_BINARY);
  _setmode(_fileno(stdin), _O_BINARY);
  _setmode(_fileno(stderr), _O_BINARY);

としておくとよいでしょう.

バイナリモードにした場合, テキストモードと違ってfgetsやstd::getlineなどで読み込むと\rが含まれてしまうのでそれを除去する必要があります.

 std::ifstream ifs(name, std::ios::binary);
 std::string line;
 while (std::getline(ifs, line)) {
    /* 行末の\rを除去する */
    size_t size = line.size();
    if (size > 0 && line[size - 1] == '\r') {
        line.resize(size - 1);
    }
    /* 実際の処理 */
    ...
 }

こうすることでLinux, Windowsの両方の環境で, 改行が\r, \r\nのどちらのテキストファイルでも同様に扱えるようになります.

なお, これはC++ライブラリの話ですが, std::cinはデフォルトではstd::coutと同期するようになっており, それが不要な場合には非常に重たいコストとなります. cin.tie(0)を呼ぶことでその同期をやめることができます. 更にprintfなどのC系の出力との同期も不要な場合にはstd::ios::sync_with_stdio(false)も呼ぶとよいでしょう. cf. 簡易ベンチマーク, ios_base::sync_with_stdio


巨大なファイル

32bit環境で2GBを超えるファイルを扱うのは意外に面倒です. そもそもfseekの引数やftellの戻り値の型はlong(32bit)であるため, オーバーフローしてしまいます. したがってそれ以外の方法にたよらざるを得ません.

Cにはfsetposやfgetposというfpos_t型を指定することでseek可能な関数がありますが, 32bit Linuxの場合, まず_FILE_OFFSET_BITS=64を定義しておく必要があります(64bit Linuxでは不要です). こうすることでの引数に使われるfpos_tが64bit対応になります. なお, Linuxではfpos_tは整数型ではなく, 構造体でありその値を取得するには__posメンバをとる必要があります. Windowsでは32bit, 64bitに関わらず64bit整数です.

#include <stdio.h>
#include <iostream>

int main(int argc, char *argv[])
{
    if (argc == 1) return 1;
    const char *name = argv[1];
    FILE *fp = fopen(name, "rb");
    fseek(fp, 0, SEEK_END);
    long a = ftell(fp);
    std::cout << a << std::endl;
    fpos_t pos;
    if (!fgetpos(fp, &pos)) {
#ifdef _MSC_VER
        std::cout << pos << std::endl;
#else
        std::cout << pos.__pos << std::endl;
#endif
    }
}

上記コードで2GBを超えるファイルを指定すると, 32bit Windows, 32bit Linuxではfseekやftellは失敗します.

VCには64bitに対応した_fseeki64, _ftelli64などがあるので32bit Linuxを諦めるなら

#ifdef _MSC_VER
    #define fseek _fseeki64
    #define ftell _ftelli64
#endif

とし, パラメータや戻り値をint64_tで利用する手もあります. こうすると32bit/64bit Windows, 64bit Linuxで共通のコードを使えます. そうでない場合はftell, fseek系は諦めてfgetpos, fsetposのみを使わざるを得ません.

#ifdef _MSC_VER
    #define MIE_GET_64BIT_POS(pos) pos
#else
    #define MIE_GET_64BIT_POS(pos) pos.__pos
    #define _FILE_OFFSET_BITS 64 /* gccのオプションで定義すべき */
    #include <stdio.h>
#endif

これがC++では逆転して32bit Windowsではseekgなどは諦めざるをえないようです. 次のようなコードはWindowsでは2GBを超えるファイルに対して期待通りに動作しません. 詳しく言うとVC9まではseekg()が失敗します(VC10では大丈夫).

    std::ifstream ifs(name, std::ios::binary);
    ifs.seekg(0, std::ios::end);
    std::cout << ifs.tellg() << std::endl;

ただしWindowsではstd::ifstream::pos_typeという型が拡張されていて, これを使うとfpos_t + fgetpos的な使い方は可能です.

    std::ifstream::pos_type pos(0 /* 常に0 */, 5368709120LL);
    ifs.seekg(pos);
    /* これは駄目(32bitにキャストされる) */
    std::cout << ifs.tellg() << std::endl;
    /* seekpos()をとると64bitになる */
    std::cout << ifs.tellg().seekpos() << std::endl;

VCのiosfwdには

#define _POS_TYPE_TO_FPOS_T(pos) pos.seekpos()

というマクロも定義されていました.

まとめると次のようになります.

ファイルのシークと位置の取得
32bit Windows 32bit Linux 64bit Windows 64bit Linux
fseek _fseeki64ならOK NG _fseeki64ならOK OK
fgetpos OK .__posでアクセス OK .__posでアクセス
seekg(0, std::ios::end) VC10ならOK OK VC10ならOK OK
seekg + pos_type OK - OK -
tellg() seekpos()でアクセス OK seekpos()でアクセス OK

このねじれ状態はなんとかならんかな... 一応補足しておくと2GBを超えるseekがうまくいかないだけで逐次的にデータを読む場合はどの環境でも問題ありません(ポジションの取得は多少注意が必要ですが). とは言え, それぞれの環境のシステムコールやWin32 APIを軽くラップしたクラスを作って利用する方が安全な気はします.

C++11での挙動については大きなファイルを扱うを参照.


ファイルサイズの取得

WindowsにはGetFileSizeExがあるのでそれを使えばよいですが, posixのfstatも利用可能です. 下記コードは32bit/64bit, VC/gccのいずれでも動作します(もちろん32bit Linuxでは_FILE_OFFSET_BITS=64の定義が必要です).

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#ifdef _MSC_VER
#include <io.h> // for open
#endif

int main(int argc, char *argv[])
{
    if (argc == 1) return 1;
    const char *name = argv[1];
    struct stat stat;
    int fd = open(name, O_RDONLY);
    if (fstat(fd, &stat)) {
        perror("fstat");
        return 1;
    }
    std::cout << "size=" << stat.st_size << std::endl;
}

ただしVCでは/W4オプションではopenなどの(POSIXではあるが)非C/C++である関数名は使うなという警告が出ます(処理系依存なので_で始まらないといけない). そういう警告を抑制するためには_CRT_NONSTDC_NO_WARNINGSを定義します. そうすると今度はより安全な関数*_sを使えという警告が出るのでその警告も抑制したいなら更に_CRT_SECURE_NO_WARNINGSを定義します.


readsome

C++で一定のサイズでファイルを無くなるまで読みたい場合, readsome()を使うとVCではうまく行きません.

#include <fstream>
#include <iostream>

int main()
{
    const char *name = "t.cpp";
    std::ifstream ifs(name, std::ios::binary);
    long long total = 0;
    for (;;) {
        char buf[1024];
        size_t size = ifs.readsome(buf, sizeof(buf));
        if (size == 0) break;
        total += size;
    }
    std::cout << total << std::endl;
}

試してみるとわかりますが, ファイルをopen()した直後はin_avail()が0でreadsome()が0を返して終わってしまいます(規格上は正しいのかもしれませんが使えない…). 毎回readsome()の直前にpeek()しておくと大丈夫のようですが, かっこ悪いですね. 仕方がないのでreadsome()は使わずread()とgcount()を組み合わせるのがよいようです(std::stringstreamなどではVCでもreadsome()は期待通りに動作しているのですが).

    for (;;) {
        char buf[1024];
        ifs.read(buf, sizeof(buf));
        size_t size = ifs.gcount();
        if (size == 0) break;
        total += size;
    }

あるいはrdbuf()から直接sgetn()を呼ぶという手もあります. read()を使うよりこちらの方が若干効率がよいかも.

    std::streambuf *rdbuf = ifs.rdbuf();
    for (;;) {
        char buf[1024];
        size_t size = rdbuf->sgetn(buf, sizeof(buf));
        if (size == 0) break;
        total += size;
    }

上二つのコードはgccとVCどちらでも期待通りに動作しました.


時刻
現在時刻(UTC)の秒以下の精度をとりたい場合, Linuxではgettimeofday(or clock_gettime), WindowsではGetSystemTimeAsFileTimeを使います. 前者は1970/1/1からのマイクロ秒, 後者は1601/1/1からの100ナノ秒なので次の式で相互変換できます.
    /* 1601/1/1から1970/1/1までの100ナノ秒単位での数値 */
    const int64_t EPOCH_BIAS = 116444736000000000LL;
    GetSystemTimeAsFileTime => FILETIME
    gettimeofday => tv_sec = 秒;
                    tv_usec = マイクロ秒;
    (tv_sec * 1000000 + tv_usec) * 10 = FILETIME - EPOCH_BIAS;

ただ変換が少々面倒なので(若干精度は落ちますが)VCではそれをラップした_ftime_sを使う方が楽だと思います.

ただし, _ftime_s(), clock(), GetSystemTimeAsFileTime()などのAPIは単位はmsecこそあれ, 実際の精度はもっと低いことがあります(環境によります). 本当のmsecレベルの精度が欲しい場合は別の方法をとる必要があるでしょう.

なお, gettimeofdayはPOSIX.1-2008で廃止予定らしいのでclock_gettimeを使った方がよいかもしれません. その場合はリンク時に-lrtオプションが必要です.

#include <ctime>
#ifdef _MSC_VER
    #include <sys/timeb.h>
#else
    #include <sys/time.h>
    #include <unistd.h>
#endif

/* 1970/1/からのミリ秒の取得 */
void GetUTC(std::time_t *sec, int *msec)
{
#ifdef _MSC_VER
    struct _timeb timeb;
    _ftime_s(&timeb);
    *sec = timeb.time;
    *msec = timeb.millitm;
#elif defined(_POSIX_TIMERS)
    struct timespec tp;
    clock_gettime(CLOCK_REALTIME, &tp);
    *sec = tp.tv_sec;
    *msec = tp.tv_nsec / 10000000;
#else
    struct timeval tv;
    gettimeofday(&tv, 0);
    *sec = tv.tv_sec;
    *msec = tv.tv_usec / 1000;
#endif
}

プログラム名
実行時のフルパスを含むプログラム名を取得したい場合, argv[0]は当てになりません. WindowsではGetModuleFileName()があるのでそれを使えばよいでしょう(GetModuleFileNameW()の方がより安心).
#include <windows.h>
#include <stdio.h>

int main()
{
    char buf[1024];
    if (GetModuleFileName(0, buf, sizeof(buf)) != 0) {
        printf("path=%s\n", buf);
    }
}
>path=c:\tmp\t.exe

なおVCにはchar *_pgmptrやwchar_t *_wpgmptrというグローバル変数(あるいはそれを取得する_get_pgmptr(), _get_wpgmptr())があり, それがフルパスを指しているようですが, コマンドライン以外で起動したときはそうとは限らないようなので使わない方がよさそうです(試した限りでは大丈夫そうなので駄目なケースがよく分かりませんが).

Linuxでは直接フルパスを取得するAPIはなさそうなので/procを見るのが楽でしょう. (Solarisのgetexecname()みたいなの無いのかなあ).

#include <stdio.h>
#include <unistd.h> // for readlink

int main(int argc, char *argv[])
{
    char buf[1024];
    int ret = readlink("/proc/self/exe", buf, sizeof(buf));
    if (ret != -1) {
        buf[ret] = '\0';
        printf("path=%s\n", buf);
    }
}
>path=/home/shigeo/a.out

スレッド
スレッドの管理はgcc(pthread)とVC(Win32)とで大きく異なりますが, ある程度は対応するAPIがあります. 排他制御のmutexやsemaphoreも同様にあります. ただイベントや条件変数など片方にしか無いものもあります. それらをラップしたboost::threadやC++で標準化予定のものを使うのが楽でしょうが, 自分で制御することもあるでしょう.
スレッドの利用

スレッドの生成, join, 破棄などの一連の操作にはgccとVCほぼ対応するAPIが存在します.

スレッドの生成・破棄
gcc(pthread) VC
スレッドを生成する pthread_create _beginthreadex
スレッドの終了を待つ pthread_join WaitForSingleObject + CloseHandle
スレッドを切り離す(終了待ちしない) pthread_detach CloseHandle

スレッドを生成する

Windowsのスレッドの生成にはWin32 APIのCreateThread, C関数の_beginthread, _beginthreadexの計3種類あります.

CreateThreadはC/C++ランタイムライブラリを使う場合にメモリリークの可能性を伴うので使うべきではありません.

_beginthreadはスレッド終了時に自動的にスレッドのハンドルを閉じます. _beginthreadexはハンドルを閉じません. 一見前者の方が扱いが楽に見えますが, 非常にたくさんのスレッドの生成と破棄を行う場合, _beginthreadがハンドルを返す前にそのスレッドが終了して, 新たに別のスレッドがそのハンドルを割り当てられてしまっていることがあります. その場合デバッグ困難な挙動をすることになります. _beginthreadは使ってはいけません.

pthreadのpthread_createはスレッドサイズを与えることができません. 設定したい場合はpthread_attr_setstacksizeを使います.

/* スレッド生成 */
#ifdef _WIN32
    typedef HANDLE ThreadHandle;
    static const int Infinite = INFINITE;
    typedef unsigned __stdcall ThreadEntryCallback(void*);
#else
    typedef pthread_t ThreadHandle;
    static const int Infinite = 0x7fffffff;
    typedef void* ThreadEntryCallback(void*);
#endif

bool begin(ThreadHandle& hdl,
    ThreadEntryCallback entryFct, /* エントリーポイント */
    void *arg, /* callback関数に渡すユーザパラメータ */
    int stackSize = 0)
{
#ifdef _WIN32
    unsigned int threadId = 0;
    hdl = reinterpret_cast<ThreadHandle>(_beginthreadex(0, stackSize, entryFct, arg, 0, &threadId));
    return hdl != 0;
#else
    pthread_attr_t attr;
    if (pthread_attr_init(&attr)) return false;
    if (stackSize) {
        if (pthread_attr_setstacksize(&attr, stackSize)) {
            return false;
        }
    }
    return pthread_create(&hdl, &attr, entryFct, arg) != 0;
#endif
}
スレッドの終了を待つ

スレッドを待つ(join)にはgccではpthread_join, VCではWaitForSingleObjectを使います. pthread_joinしたハンドルは無効ですが, WaitForSingleObjectしても無効にならないのでCloseHandleが必要です.

pthread_joinはタイムアウトする方法が無いのでもしそういうものが必要ならスレッド終了時になんらかのフラグ(or mutex)を立てて, pthread_joinする前にそのフラグが変更されるのを監視するというやりかたになりそうです.

/* スレッド終了を待つ */
bool Join(ThreadHandle hdl, int timeoutMsec)
{
#ifdef _WIN32
    bool waitRet = WaitForSingleObject(hdl, timeoutMsec) == WAIT_OBJECT_0;
    bool closeRet = CloseHandle(hdl) != 0;
    return waitRet && closeRet;
#else
    if (timeoutMsec != Infinite) {
        fprintf(stderr, "not support timeout\n");
    }
    return pthread_join(hdl, NULL) == 0;
#endif
}
スレッドを切り離す

gccではpthread_detach, VCではCloseHandleを使います.

static inline bool Detach(ThreadHandle hdl)
{
#ifdef _WIN32
    int ret = CloseHandle(hdl);
    return ret != 0;
#else
    int ret = pthread_detach(hdl);
    return ret == 0;
#endif
}
簡易スレッドクラス

まとめてこんなクラスを作っておくと便利です. AtomicExchangeSize_t()はsize_t変数に関するatomic操作命令の命令(InterlockedExchange, _InterlockedExchange64, __sync_lock_test_and_set)を適当にラップしたものです. VCのsizeof(HANDLE)が32bitだと4, 64bitだと8なのでsize_tにキャストしてます. デストラクタで勝手にjoin(or 切り離し)するのは議論の余地はあるでしょう. boost::threadのデフォルトは自動切り離しですが, この場合生き続けているthreadが消えた変数にアクセスしてしまうバグになりやすいです. よりよい方法がないかC++0xの標準化に向けて議論があるようです.

/* 簡易スレッドクラス */
template<class Derived>
class ThreadBase {
private:
    ThreadBase(const ThreadBase&);
    void operator=(const ThreadBase&);
protected:
    thread::ThreadHandle threadHdl_;

    static void threadLoopIF(void *arg)
    {
        Derived* main = reinterpret_cast<Derived*>(arg);
        main->threadEntry();
    }
public:
    ThreadBase()
        : threadHdl_(0)
    {
    }
    ~ThreadBase()
    {
        joinThread();
    }

    bool beginThread(int stackSize = 0)
    {
        return thread::Begin(threadHdl_, (thread::ThreadEntryCallback*)threadLoopIF, &static_cast<Derived&>(*this), stackSize);
    }

    bool detachThread()
    {
        thread::ThreadHandle hdl = (thread::ThreadHandle)AtomicExchangeSize_t((size_t*)&threadHdl_, 0);
        if (hdl) {
            return thread::Detach(hdl);
        }
        return false;
    }

    bool joinThread(int waitMsec = thread::Infinite)
    {
        thread::ThreadHandle hdl = (thread::ThreadHandle)AtomicExchangeSize_t((size_t*)&threadHdl_, 0);
        if (hdl) {
            if (thread::Join(hdl, waitMsec)) {
                return false;
            }
        }
        return true;
    }
};

こんな感じで使います.

/* サンプル */
class A : public ThreadBase<A> {
    volatile bool quit_;
public:
    A()
        : quit_(false)
    {
    }
    void threadEntry()
    {
        while (!quit_) {
            puts("wait");
            sleepSec(1); // 適当なsleep関数
        }
    }
    void quit() { quit_ = true; }
};
int main()
{
    A a;
    a.beginThread();
    sleepSec(5);
    a.quit();
    a.joinThread();
}
sleep

gccではsleep(秒), VCではSleep(ミリ秒)があります. ただsleepはalarmと一緒に使ってはならないとあるのでnanosleepを使う方がよいようです.

void SleepMsec(int msec)
{
#ifdef _WIN32
    ::Sleep(msec);
#else
    struct timespec req;
    req.tv_sec = msec / 1000;
    req.tv_nsec = (msec % 1000) * 1000000;
    nanosleep(&req, 0);
#endif
}
libcではsleepはnanosleepを用いて実装されているなので大丈夫ですが…
static変数の初期化
C++で関数内のstatic変数の初期化はその関数が初めて呼ばれたときに行われます. これはマルチスレッド環境で問題を引き起こすことがあります. スレッドを生成する前に一度その関数を呼び出して初期化しておくか, あるいはなんらかの同期機構を用いてスレッドセーフにする必要があります. 後者については今のところboost::boost::call_once()を使うのが一番楽ですが, 少し個別に見てみます.
gccの場合

gccの場合は簡単です. 関数内のstatic変数の初期化はデフォルトでスレッドセーフです. たとえば次のコードを見るとわかります.

#include <stdio.h>
class A {
    int n_;
public:
    A(int n) : n_(n) { puts("cstr"); }
    ~A() { puts("dstr"); }
    void put() const { printf("a=%d\n", n_); }
};
A& GetInstance(int n)
{
    static A a(n);
    return a;
}
int main()
{
    A& a = GetInstance(3);
    a.put();
}

このソースの生成コードを見ると次のようになっています.

    /* mainの先頭(snip) */
    /* 隠れた変数が0ならば.initへ */
    cmpb    $0, _ZGVZ11GetInstanceiE1a
    je      .init
.L10:
    /* A::a_を取得 */
    movl    _ZZ11GetInstanceiE1a, %eax
    movl    $.LC2, (%esp) // "a=%d\n"
    movl    %eax, 4(%esp)
    call    printf
    /* 終了処理(snip) */

.init:
    movl    $_ZGVZ11GetInstanceiE1a, (%esp)
    call    __cxa_guard_acquire /* ガード開始 */
    testl   %eax, %eax
    je  .L10
    /* Aのコンストラクタ */
    movl    $3, _ZZ11GetInstanceiE1a
    movl    $.LC0, (%esp)
    call    puts
    movl    $_ZGVZ11GetInstanceiE1a, (%esp)
    call    __cxa_guard_release /* ガード終了 */

このように自動的にガード(__cxa_guard_acquire, __cxa_guard_release)が入ります. もし, boost::boost::call_once()などを使ったり, スレッドを生成する前にすべて初期化してるなら逆にこのガードが不要です. その場合は-fno-threadsafe-staticsを使うとそのガードを生成しなくなります.

VCの場合

C++11ではgccの挙動のようにstatic変数の初期化処理はスレッドセーフになることが求められるようになりました. しかし, 今のところVCの場合はgccのように自動的にスレッドセーフなコードが生成されることはありません.

Vista以降ならば新しく追加されたInitOnceExecuteOnceを用いて割合簡単にガードすることができます. たとえば次のような簡易template関数を作ってみました. パラメータの無いコンストラクタを持つ任意の関数のインスタンスをスレッドセーフで取得できます(パラメータが必要な場合はInitOnceExecuteOnceの第3引数に渡します. 詳細はMSDNを見てください).

template<class T>
struct InitOnce {
    static inline BOOL WINAPI init(INIT_ONCE *, void *, void **ptr)
    {
        try {
            *ptr = new T();
            return TRUE;
        } catch (...) {
            return FALSE;
        }
    }
    static inline T* getInstance()
    {
        static INIT_ONCE initOnce;
        void *ptr;
        if (InitOnceExecuteOnce(&initOnce, init, 0, &ptr)) {
            return (T*)ptr;
        }
        return 0;
    }
};
/* サンプル */
struct A {
    A()
    {
        puts("cstr");
    }
    ~A()
    {
        puts("dstr");
    }
};
int main()
{
    A *a = InitOnce<A>::getInstance();
    delete a;
}

Vista以前のWindowsではダブルチェックロッキングを用いて初期化する必要があるようです. ダブルチェックロッキングはJavaでは「メモリアクセス順序の問題で使うな」という点で有名な手法ですが, VC(2005以降)ではvolatileをつけると正しく動作することが保証されているようです( volatile, one-time initialization ). まあこの場合, ロックに使うCRITICAL_SECTIONの初期化の問題が発生するのですが….

gccで同様の関数pthread_onceでは静的な初期化ができるのでもう少し簡単に使えます.

#include <pthread.h>

template<class T>
class InitOnce {
private:
    static T *p_;
public:
    static inline void init()
    {
        try {
            p_ = new T();
        } catch (...) {
        }
    }
    static inline T* getInstance()
    {
        static pthread_once_t once = PTHREAD_ONCE_INIT;
        pthread_once(&once, InitOnce::init);
        return (T*)InitOnce::p_;
    }
};

template<class T>
T* InitOnce<T>::p_ = 0;

/* 以下Windowsのサンプルと同様 */

snprintf
sprintfはバッファオーバーランの危険性を伴うので出力先のサイズを制限できるsnprintfは便利です. しかしこの関数はgcc(というかC99のsnprintf)とVCとで微妙に違うので注意が必要です.
snprintfの違い
gccのsnprintf VCの_snprintf VCの_snprintf_s
パラメータ 出力最大サイズ(N) 出力最大サイズ(N) バッファサイズ(BN)と出力最大サイズ(N)
挙動 最大N - 1 byte
0終端される
最大N byte
0終端されるとは限らない
最大N byte
0終端される BNを超えると例外ハンドラ呼び出し
正常時戻り値 書き込まれた文字数
異常時戻り値 必要だった(0終端含む)バイト数 負の数 負の数
 /* gccのsnprintfサンプル */
 char buf[] = "1234567";
 snprintf(buf, 3, "abcd");
 /* 結果 */
 buf[0] = 'a';
 buf[1] = 'b';
 buf[2] = '\0'; /* N - 1byte書かれて0終端する */
 /* VCの_snprintfサンプル */
 char buf[] = "1234567";
 _snprintf(buf, 3, "abcd");
 /* 結果 */
 buf[0] = 'a';
 buf[1] = 'b';
 buf[2] = 'c'; /* N byte書かれて0終端しない */
 /* VCの_snprintf_sサンプル */
 char buf[] = "1234567";
 /* C++の場合, templateによりBN = sizeof(buf)が暗黙に渡される */
 _snprintf_s(buf, 2, "abcd");
 /* 結果 */
 buf[0] = 'a';
 buf[1] = 'b';
 buf[2] = '0'; /* N byte書かれて0終端する */

まずVCの_snprintfは0終端されるとは限らないため使ってはいけません. _snprintf_sに渡すパラメータはgccのsnprintfのそれの一つ小さい値を指定する必要があります. またVCでは足りなかったバッファ数を取得する方法は無いようです.

C99で導入され, VCでも使える可変長引数マクロを使って次のように定義するとよいでしょう.

#ifdef _WIN32
  #define SNPRINTF(x, len, ...) (void)_snprintf_s(x, len, len - 1, __VA_ARGS__)
#else
  #define SNPRINTF(x, len, ...) (void)snprintf(x, len, __VA_ARGS__)
#endif

例外
C++では関数の宣言で, その関数が投げる例外の型をthrow()の中で指定することができます. デフォルトでは実行時に指定された例外以外が投げられるとabortしますが, std::set_unexpected()でそのときにすべき処理を指定することもできます. ただ残念ながら, 何故かVCではその例外指定が無視されます(Exception Specifications). これはVC10でも同じです. たとえば次のコードのfunc()はAを投げると指定していしているのにも関わらずBをthrowしたため, unexpectedHandlerが呼ばれます. その中でAを投げているので最終的にAがcatchされるはずです. gccでは期待通りに動作するのですが, VCではthrow()の中身は無視されるためBをcatchしてしまいます.
#include <exception>
#include <stdio.h>

struct A {};
struct B {};

void unexpectedHandler()
{
    puts("unexpected");
    throw A();
}

void func() throw(A)
{
    puts("call func");
    throw B();
}

int main()
{
    std::set_unexpected(unexpectedHandler);
    try {
        func();
    } catch (A&) {
        puts("catch A");
    } catch (B&) {
        puts("catch B");
    } catch (...) {
        puts("other");
    }
}
// gcc(規格通りの動作)
call func
unexpected
catch A
// VC(規格外の動作)
call func
catch B
例外ハンドラ

VCの_snprintf_sではBN >= Nのときに例外ハンドラが呼ばれます. デフォルトではabortするので自前で処理したい場合は_set_invalid_parameter_handlerで指定します. ただしこのエラーハンドラは_snprintf_sだけでなく他の全てのもの(*_s系など)と共用します.

void myInvalidParameterHandler(
   const wchar_t* expression,
   const wchar_t* function,
   const wchar_t* file,
   unsigned int line,
   uintptr_t pReserved)
{
    /* 何もせずに戻る */
}

    /* デフォルトはabortする */
    char s[] = "123";
    int a = _snprintf_s(s, 3, "abcdef"); // abort

    /* 継続 */
    _set_invalid_parameter_handler(myInvalidParameterHandler);
    int a = _snprintf_s(s, 3, "abcdef");
    if (a < 0) {
        /* エラー処理 */
        fprintf(stderr, "err=%d(%s)\n", errno, strerror(errno));
    }
構造化例外

またWindowsにはC++の例外機構が導入されるより前から構造化例外(SEH)という仕組みがあります. これはたとえば0番地にアクセスして落ちたなどの通常のC++の例外ではキャッチできない処理もキャッチできます. そしてすばらしいことにハンドラを設定することでC++の例外の枠組みに載せることもできます. そのためにはコンパイルオプション/EHaと_set_se_translatorを使います. Linuxにはそのような機構は無いのですが, VC上だけでも入れておけばデバッグが少し便利になるかもしれません.

/* /EHaつきでコンパイル */
#include <windows.h>
#include <eh.h>

class Exception {
private:
    const void *const address_;
    const unsigned int code_;
public:
    Exception(unsigned code, const EXCEPTION_POINTERS *info)
        : address_(info->ExceptionRecord->ExceptionAddress)
        , code_(code)
    {
    }
    /*
        codeは
        EXCEPTION_ACCESS_VIOLATION(アクセス違反)
        EXCEPTION_INT_DIVIDE_BY_ZERO(0除算)
        EXCEPTION_STACK_OVERFLOW(スタックオーバーフロー)
        などがある.
    */
    unsigned int getCode() const { return code_; }
    const void *getAddress() const { return address_; }
};


static void translator(unsigned int code, EXCEPTION_POINTERS *info)
{
    throw Exception(code, info);
}

#include <stdio.h>

int main(int argc, char *argv[])
{
     _set_se_translator(translator);
    try {
//        printf("%d\n", 1 / (argc - 1));
        *(int*)0 = 0;
    } catch (Exception& e) {
        printf("e=%x, %p\n", e.getCode(), e.getAddress());
    }
}
intrinsic関数
gccとVCには低レベルの処理を記述可能にするintrinsic関数(通常の関数と違ってinline展開される)が用意されています. ここではそれらについていくつか紹介します.

アライメント

SSEなどのSIMD命令で利用するデータは16byteアライメントされていなければならない場合があります. gcc, VCそれぞれデータを指定されたアライメントに合わせて配置する命令があります. 下記のようなマクロを用意して使うのがよいでしょう.

#ifdef _MSC_VER
    #define MIE_ALIGN(x) __declspec(align(x))
#else
    #define MIE_ALIGN(x) __attribute__((aligned(x)))
#endif
    //
    MIE_ALIGN(16) char buf[16]; // (intptr_t(buf) % 16) == 0

malloc()の戻り値は16byteアライメントされているとは限りません. アライメントつきの関数があるのでそれを利用するのがよいでしょう. gccでは昔はmemalign()という関数でしたが現在は使われないようです.

#ifdef _MSC_VER
    #include <malloc.h>
#else
    #include <stdlib.h>
    static inline void *_aligned_malloc(size_t size, size_t alignment)
    {
        void *p;
        int ret = posix_memalign(&p, alignment, size);
        return (ret == 0) ? p : 0;
    }
#endif
    /* 16byteアライメントされた500byteのデータを確保 */
    // char *p = (char*)_aligned_malloc(500, 16);

なお, VCの_aligned_mallocで確保した領域を開放するには_aligned_freeを呼ばないといけないので注意. gccのposix_memalignで確保した領域に対してはfreeでOKです.


bsfとbsr

bsf(resp. bsr)は与えられたレジスタのLSB(resp. MSB)から探して初めて1が立ったビットの位置を返す命令です. 与えられたレジスタが0の場合は未定義ですが, ZFフラグがたちます. 巧妙なビット演算処理でよく用いられます.

/* bsfの擬似コード */
bool bsf(reg *out, reg x)
{
    for (size_t i = 0; i < sizeof(reg) * 8; i++) {
        if (x & (1 << i)) {
            *out = i;
            return true; // ZF = 0
        }
    }
    return false; // ZF = 1
}

VCには_BitScanForward, _BitScanForward64というintrinsic関数が用意されています.

/*
    上記bsf関数と同等のもの
*/
unsigned char _BitScanForward(
   unsigned long * out,
   unsigned long val
);
unsigned char_BitScanForward64(
   unsigned long * out,
   unsigned __int64 val
);

gccには__builtin_ctz, __builtin_ctzlという関数が用意されています. これはvalが0でないとき専用です. gccの方がシンプルですが, x86アセンブリレベルではZF=1を利用した最適化が可能なため, VCのインタフェースの方がより効率のよいコード生成をしやすいと思われます(参考 x86カルトクイズ Q7) 次のようなマクロを使えばよいでしょう.

#ifdef _MSC_VER
    #define mie_bsf(pindex, val) _BitScanForward( \
        reinterpret_cast<unsigned long *>(pindex), val)
    #define mie_bsf64(pindex, val) _BitScanForward64( \
        reinterpret_cast<unsigned long *>(pindex), val)
#else
    #define mie_bsf(pindex, val) \
        val ? *pindex = __builtin_ctz(val), 1 : 0
    #define mie_bsf64(pindex, val) \
        val ? *pindex = __builtin_ctzl(val), 1 : 0
#endif
 //
 // unsigned int x;
 // unsigned int out;
 // if (mie_bsf(&out, x)) {
 //     ...
 // }

と書いてましたがgccには__builtin_ffs(x)という関数がありました(built-in functions provided by GCC). x == 0のときは0, そうでないときはbsf(x) + 1を返す関数です. 次のようなasmが生成されました[2012/4/4:追記].

    bsf eax, <入力値>
    mov edx, -1
    cmove   eax, edx
    add eax, 1

bsrも同様で擬似コードは以下のようになります.

bool bsr(reg *out, reg x)
{
    for (int i = sizeof(reg) * 8 - 1; i >= 0; i--) {
        if (x & (1 << i)) {
            *out = i;
            return true;
        }
    }
    return false; // ZF = 1
}

VCには_BitScanReverse, _BitScanReverse64が, gccには__builtin_clz, __builtin_clzlがあります. 注意すべきは__builtin_clzは逆順に出力されるということです. 生成コードを見るとbsrしたあとxor 0x1fをしていました. bsrだと思って使うと1命令損しますね. その他の注意はbsfと同じなので次のようなマクロを使えばよいでしょう.

#ifdef _MSC_VER
    #define mie_bsr(pindex, val) _BitScanReverse( \
        reinterpret_cast<unsigned long *>(pindex), val)
    #define mie_bsr64(pindex, val) _BitScanReverse64( \
        reinterpret_cast<unsigned long *>(pindex), val)
#else
    #define mie_bsr(pindex, val) \
        val ? (*pindex = __builtin_clz(val) ^ 0x1f), 1 : 0
    #define mie_bsr64(pindex, val) \
        val ? (*pindex = __builtin_clzl(val) ^ 0x3f), 1 : 0
#endif

atomic操作命令

マルチスレッド環境では比較して交換するなどの処理をatomicにしたい場合があります. C++0xにはそういった命令が標準で入る予定ですが, もうしばらくは個別の関数を使うことが多いでしょう. gccのATOMIC一覧. VCのInterlocked系. gccの関数は変数の型に応じた命令が生成されるようです. VCではshortの場合は16, __int64の場合は64が関数名の末尾につくものもあります. そして64bit環境ではshortは使えなく, 32bit環境では__int64は使えません(_InterlockedCompareExchange64除く). それからVCでは_で始まるのと始まらないのがありますが, _で始まる方がintrinsicなので高速です.

atomic命令一部
x86/x64ニーモニック 32bit VC 64bit VC gcc
x++ lock xadd _InterlockedIncrement16
_InterlockedIncrement
_InterlockedIncrement
_InterlockedIncrement64
無い
x-- lock xadd _InterlockedDecrement16
_InterlockedDecrement
_InterlockedDecrement
_InterlockedDecrement64
無い
{ *p += v;
return *p; }
lock xadd 無い 無い __sync_add_and_fetch
{ *p {op}= v;
return *p; }
op = add, and, nand, or, sub, xor
lock opまたは{op} + lock cmpxchg(以下xadd以外のopは同様) 無い 無い __sync_{op}_and_fetch
{ tmp = *p;
*p += v;
return tmp; }
lock xadd _InterlockedExchangeAdd _InterlockedExchangeAdd
_InterlockedExchangeAdd64
__sync_fetch_and_add
{ tmp = *p;
*p &= v;
return tmp; }
lock and _InterlockedAnd _InterlockedAnd
_InterlockedAnd64
__sync_fetch_and_and
{ tmp = *p;
*p |= v;
return tmp; }
lock or _InterlockedOr _InterlockedOr
_InterlockedOr64
__sync_fetch_and_or
{ tmp = *p;
*p ^= v;
return tmp; }
lock xor _InterlockedXor _InterlockedXor
_InterlockedXor64
__sync_fetch_and_xor
CF = x[off]; lock bt _bittest _bittest
_bittest64
無い?
CF = x[off];
x[off] = not x[off];
lock _bittestandcomplement _bittestandcomplement
_bittestandcomplement64
無い?
CF = x[off];
x[off] = 0;
lock btr _interlockedbittestandreset _interlockedbittestandreset
_interlockedbittestandreset64
無い?
CF = x[off];
x[off] = 1;
lock bts _interlockedbittestandset _interlockedbittestandset
_interlockedbittestandset64
無い?
{ tmp = *p;
*p = newValue;
return tmp; }
xchg _InterlockedExchange _InterlockedExchange
_InterlockedExchange64
__sync_lock_test_and_set
{ tmp = *p;
if (tmp == oldValue) *p = newValue;
return tmp; }
lock cmpxchg _InterlockedCompareExchange16
_InterlockedCompareExchange
_InterlockedCompareExchange64
_InterlockedCompareExchange
_InterlockedCompareExchange64
__sync_val_compare_and_swap

cpuid

gcc 4.3以降やVC2005以降ににcpuidを発行する関数が用意されました. 今のところこの関数はgccとVCで非互換です. 次のような関数を利用するとよいでしょう.

#ifdef _MSC_VER
    #include <intrin.h>
#else
    #include <cpuid.h>
#endif
void GetCpuid(unsigned int eaxIn, unsigned int data[4])
{
#ifdef _MSC_VER
    __cpuid(reinterpret_cast<int*>(data), eaxIn);
#else
    __cpuid(eaxIn, data[0], data[1], data[2], data[3]);
#endif
}

rdtsc

speedStep技術などのせいで, 若干使い勝手が悪くなったとはいえnsec精度で経過時間を測定できるrdtscはマイクロベンチマークには便利です. VCでは__rdtsc()があるのでそれを, gccでは__asm__を使って実現できます(元は64bit Linuxで不十分でした. shinhさんのコード参照).

uint64_t getRdtsc()
{
#ifdef _MSC_VER
    return __rdtsc();
#else
    unsigned int eax, edx;
    __asm__ volatile("rdtsc" : "=a"(eax), "=d"(edx));
    return ((uint64_t)edx << 32) | eax;
#endif
//    for only 32-bit
//    uint64_t ret;
//    __asm__ volatile("rdtsc" : "=A"(ret));
//    return ret;
}

最近ではマルチコアに対応したrdtscpという命令も登場しています. VCでは__rdtscp()で使えます.

周波数の取得についてはWindowsにはQueryPerformanceFrequencyがあるのですが, Linuxにはなさそうです. 仕方がないので/proc/cpuinfoを見てみますか(適当).

なお, これらの方法で取得した値が周波数変動の技術に追従してるとは限らないのでご注意ください.

uint64_t detectCpuFreqHz()
{
#ifdef _WIN32
    LARGE_INTEGER freq;
    QueryPerformanceFrequency(&freq);
    return freq.QuadPart;
#else
    FILE *fp = fopen("/proc/cpuinfo", "r");
    uint64_t freq = 0;
    if (fp) {
        char buf[128];
        while (fgets(buf, sizeof(buf), fp)) {
            if (memcmp(buf, "cpu MHz", 7) == 0) {
                const char *p = std::strchr(buf, ':');
                if (p) {
                    freq = static_cast<uint64_t>(std::atof(p + 2) * 1e6);
                    break;
                }
            }
        }
        fclose(fp);
    }
    return freq;
#endif
}

ヘッダ

メモリバリアやSIMD命令用のintrinsic関数は, Intelコンパイラに合わせたのか, gccとVCとで共通です(便利). ただヘッダのみが異なります.

#ifdef _MSC_VER
#include <intrin.h>
#else
#include <mmintrin.h> // for MMX
#include <xmmintrin.h> // for SSE
#include <emmintrin.h> // for SSE2
#include <pmmintrin.h> // for SSE3
#include <tmmintrin.h> // for SSSE3
/* その他SSE4.1用など(省略) */
// 最近のgccではx86intrin.hをインクルードするだけでOKになりました.
#endif

メモリバリア

メモリアクセスの順序を保証します.

メモリバリア
内容 関数 x86/x64ニーモニック
この命令の前に実行された書き込みが完了したあとに, この命令に続く書き込みを行う _mm_sfence sfence
この命令の前に実行された読み込みが完了したあとに, この命令に続く読み込みを行う _mm_lfence lfence
lfenceとsfenceの両方 _mm_mfence mfence

SIMD
ここではSIMD用関数について説明します(途中).
1st:2009/07/23, last update:2017/04/25

御意見は光成滋生<herumi@nifty.com>までお願いします