x86/x64環境で開発する上で, gccとVCはどちらも非常に優れたC/C++コンパイラです. ただLinuxとWindowsのどちらの環境でも動作するようなC/C++コードを書くためには, gccとVC, およびそれらが動作するOSの違いが問題になることがあります. ここではそれらの違いについてまとめていきたいと思います.
なお説明を簡単にするためにマクロを多用していますが実際には可能なら別の手段をとるか, 名前がぶつからないような命名規則に従ったマクロ名をつけることをお薦めします.
間違いなどございましたら@herumiかメール(herumi@nifty.com)にお願いします.
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 ).
| OS | 値 |
|---|---|
| Windows 7 | 0x601 |
| Windows Server 2008/Vista | 0x600 |
| Windows Server 2003SP1/ Xp SP2 | 0x502 |
| Windows Server 2003/Xp | 0x501 |
| Windows 2000 | 0x500 |
#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
struct A {
void func(int)
{
puts(__FUNCDNAME__); // "?func@A@@QAEXH@Z"
puts(__FUNCSIG__); // "void __thiscall A::func(int)"
puts(__FUNCTION__);// "A::func"
}
};
#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などを参考にしてください.
| オプション | 用途 |
|---|---|
| -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 -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)をしておくと最適化されたコードでもそれなりにデバッガで追いかけられるのでつけておくとよいでしょう.
32bit環境で浮動小数演算でSSEを使わせるにはVCでは/arch:SSE2, gccでは-msse2 -mfpmath=sseのオプションを使います. このオプションによる浮動小数演算への影響は浮動小数点数型を参照してください. 64bit環境では浮動小数演算はデフォルトでSSEを使うようになったのでこれらのオプションは不要です.
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でも同じオプションで制御できるはずなのですが, 少し前のバージョンではバグっていて動きませんでした. 最新版で修正されているかは確認していません(修正されたようです).
#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空間に勝手にものを持ち込むのは規格違反なのですが, 暫定処理なので仕方ないでしょう.
#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 ).
そもそもトライグラフを知らない方のために少し説明するとプリプロセスよりも前に, ある文字列の置換をテキストレベルで(文字列中だけではない!)行うものです. 単なる文字列置換なので次のコードも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 GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
int f()
{
int a;
return 3;
}
#pragma GCC diagnostic pop
int main()
{
int a; /* こちらは警告がでる */
}
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_t | 4 | 4 | 8 | 8 |
| ptrdiff_t | 4 | 4 | 8 | 8 |
| size_t | 4 | 4 | 8 | 8 |
| int64_t | 8 | 8 | 8 | 8 |
| long | 4 | 4 | 4 | 8 |
| long long | 8 | 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型も参考にしてください.
| 32bit Windows | 32bit Linux | 64bit Window | 64bit Linux | |
|---|---|---|---|---|
| float | 4 | 4 | 4 | 4 |
| double | 8 | 8 | 8 | 8 |
| long double | 8 | 12* | 8 | 16* |
| Windows(FPU) | Linux(FPU) | SSE | |
|---|---|---|---|
| float | 内部53-bit | 内部64bit | 23-bit |
| double | 内部53-bit | 内部64bit | 53-bit |
| long double | 内部53-bit | 64bit | - |
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を定義します.
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どちらでも期待通りに動作しました.
/* 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
}
#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
スレッドの生成, 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();
}
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
}
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を使うとそのガードを生成しなくなります.
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のサンプルと同様 */
| 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
#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());
}
}
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(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にしたい場合があります. C++0xにはそういった命令が標準で入る予定ですが, もうしばらくは個別の関数を使うことが多いでしょう. gccのATOMIC一覧. VCのInterlocked系. gccの関数は変数の型に応じた命令が生成されるようです. VCではshortの場合は16, __int64の場合は64が関数名の末尾につくものもあります. そして64bit環境ではshortは使えなく, 32bit環境では__int64は使えません(_InterlockedCompareExchange64除く). それからVCでは_で始まるのと始まらないのがありますが, _で始まる方がintrinsicなので高速です.
| 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 |
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
}
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 |