いきちがいのぷろぐらむあ
PG雑記/assert系マクロ関係のメモ
の編集・凍結
(
https://6809.net/tenk/?PG%e9%9b%91%e8%a8%98%2fassert%e7%b3%bb%e3%83%9e%e3%82%af%e3%83%ad%e9%96%a2%e4%bf%82%e3%81%ae%e3%83%a1%e3%83%a2%09%e3%81%ae%e7%b7%a8%e9%9b%86%e3%83%bb%e5%87%8d%e7%b5%90
)
[
トップ
] [
一覧
|
検索
|
最終更新
]
□
[
readme
] [
雑記
] [
PROG
] [
倉庫
]
ページを凍結するにはパスワードが必要です。
B
I
U
D
H
[[]]
<br>
--
管理者パスワード:
-- 雛形とするページ --
#RecentChanges
C言語機能の比較
C言語機能テスト結果1
C言語機能テスト結果2
C言語機能テスト結果3
FrontPage
GNU General Public License
GNU 一般公衆利用許諾書 (GNU General Public License)
HeaderMenu
InterWikiName
InterWikiSandBox
MenuBar
PG雑記
PG雑記/Cコンパイラ・オプション・メモ
PG雑記/SubMenu
PG雑記/Uncrustifyのオプション
PG雑記/assert系マクロ関係のメモ
PG雑記/pyukiwikiメモ
PG雑記/pyukiwikiメモ/2006-02-19
PG雑記/pyukiwikiメモ/2006-02-20
PG雑記/pyukiwikiメモ/2006-02-21
PG雑記/pyukiwikiメモ/2006-02-22
PG雑記/pyukiwikiメモ/2006-02-23
PG雑記/pyukiwikiメモ/2006-02-24
PG雑記/pyukiwikiメモ/2006-02-25
PG雑記/pyukiwikiメモ/2006-02-26
PG雑記/pyukiwikiメモ/2006-03-16
PG雑記/pyukiwikiメモ/2006-03-17
PG雑記/pyukiwikiメモ/2006-03-18
PG雑記/pyukiwikiメモ/2006-03-19
PG雑記/pyukiwikiメモ/2006-03-22
PG雑記/pyukiwikiメモ/SubMenu
SandBox
SubMenu
The "Artistic License"
The Artistic License 日本語訳 1.0
cl_test_src
lcc メモ
readme
ruig
test
サブルーチン
ヘルプ
倉庫
倉庫/C関係
倉庫/SubMenu
倉庫/hidemaru
倉庫/pyukiwiki
倉庫/グラフィック関係
倉庫/一般
倉庫/開発
掲示板
整形ルール
雑記
雑記/2005
雑記/2005-06-12
雑記/2005-06-19
雑記/2006
雑記/2006-02-08
雑記/2006-02-11
雑記/2006-02-12
雑記/2006-02-13
雑記/2006-02-26
雑記/2006-03-12
雑記/2006-03-13
雑記/2006-03-20
雑記/2006-03-21
雑記/2006-03-30
雑記/2006-11-05
雑記/2006-11-06
雑記/2006-11-11
雑記/2007
雑記/2007-04-28
雑記/2007-08-26
雑記/2008
雑記/2008-01-01
雑記/2008-05-24
雑記/2008-10-01
雑記/2008-10-02
雑記/2008-10-03
雑記/2008-10-04
雑記/2008-10-05
雑記/2008-10-11
雑記/2008-11-03
雑記/2008-11-04
雑記/2009
雑記/2009-11-02
雑記/2009-11-20
雑記/2009-11-23
雑記/2009-11-26
雑記/2009-12-02
雑記/2009-12-04
雑記/2009-12-09
雑記/2009-12-10
雑記/2009-12-12
雑記/2009-12-15
雑記/2009-12-17
雑記/2009-12-18
雑記/2009-12-20
雑記/2009-12-24
雑記/2010
雑記/2010-01-11
雑記/2010-01-26
雑記/2010-02-04
雑記/2010-02-05
雑記/2010-02-13
雑記/2010-03-09
雑記/2010-03-12
雑記/2010-03-13
雑記/2010-03-14
雑記/2010-03-15
雑記/2010-03-20
雑記/2010-03-21
雑記/2011
雑記/2011-01-01
雑記/2011-01-06
雑記/2011-11-11
雑記/2012
雑記/2012-02-29
雑記/2012-06-03
雑記/2012-10-27
雑記/2012-12-12
雑記/2012-12-14
雑記/2013
雑記/2013-05-19
雑記/2013-05-20
雑記/2013-05-21
雑記/2013-05-26
雑記/2013-06-16
雑記/2013-09-22
雑記/2014
雑記/2014-06-20
雑記/2014-10-12
雑記/2014-10-13
雑記/2014-11-02
雑記/2014-12-14
雑記/2014-12-21
雑記/2014-12-28
雑記/2015
雑記/2015-01-01
雑記/2015-02-07
雑記/2015-04-20
雑記/2015-04-29
雑記/2015-07-05
雑記/2015-07-18
雑記/2015-08-13
雑記/2015-08-14
雑記/2015-08-15
雑記/2016
雑記/2016-02-28
雑記/2016-03-27
雑記/2016-08-03
雑記/2016-09-03
雑記/2016-11-19
雑記/2017
雑記/SubMenu
#freeze * assert系デバッグ処理についてのメモ. assert(系)マクロは、主に外的要因でなくプログラム内での矛盾/バグを 検出するためのデバッグ機能。 このチェックで引っかかるバグは、プログラマに対する直接的なモノで、 プログラマ以外のスタッフは本来、頻繁に出会うはずのないエラーの類。 assertチェックは、よほどのことが無い限り、組込系の小さいものから 大規模なプロジェクトまでさまざまに使われる. ただ標準のモノだけだと不便もあるため、assertの仕様にあわせた デバッグ処理群を用意する場合もある. と、いうことで、そのへんに関して、 プリプロセッサマクロの使い方の例を兼ねて、書いてみる. (おさらい) ~ ** assert まずは基本的なこと. 開発時のデバッグ用の処理として、c/c++ 標準では、~ ''assert.h'' ヘッダ (c++では、 ''cassert'' もあり)~ が用意されている。 これを include することで、assert(x) マクロが使える。これは、たとえば、 void foo(int x, bool player) {~ ''assert''(x >= 0 && x < 10);~ ''assert''(player && "player以外でfoo()が呼ばれた");~ 略~ }~ のように、''成立すべき条件''式を assert で記述しておく. DEBUGコンパイル時はチェックルーチンが生成され、条件を満たさない場合、 エラーログを出力&終了し、RELEASEコンパイル時は、なんらチェックせず、 1命令もコードを生成しない. RELEASEコンパイル, DEBUGコンパイルの判定は、~ ''NDEBUG'' マクロ の''定義''/''未定儀''~ で行われる。 通常、VC等一般的なコンパイラのプロジェクトファイルのデフォルトでは~ ''DEBUG'' コンパイル用では ''NDEBUG 未定儀''、~ ''RELEASE'' コンパイル用では ''NDEBUG 定義''、~ のように設定されている。 もし、自分でコンパイル用の makefile やバッチorシェル・スクリプトを記述する場合は、状況にあわせて NDEBUG を定義、未定儀する必要がある.~ (わりと多くのコンパイラが、コマンドラインオプションでのマクロ指定として -D を採用しているようなので、RELEASEコンパイル時に -DNDEBUG を付加することになるだろう) //>※_DEBUG,_RELEASE 等似たようなマクロがコンパイラごとに用意されていたりするが、c/c++標準として決まっているは NDEBUG なので汎用性を考慮するならこの名を使うが吉. ~ なお assert(式) は、出力されるエラーメッセージを変更することはできないが、~ ''assert(式 && "メッセージ")'';~ のように式の一部として、&& でつなげて必ず非0アドレスとなるであろう文字列定数を記入しておくことで、エラー時のログにメッセージを埋め込むことは出来る. ~ *** assert の実装 assertマクロは、例えば、以下のような感じに定義できる. #ifdef NDEBUG // NDEBUG定義時(RELESEコンパイル時)は何もしない. #define assert(x) #else // NDEBUG未定儀時(DEBUGコンパイル時)は条件をチェック. #define assert(x) do { \ if (!(x)) { \ printf("Assertion failed: %s, file %s, line %d\n" \ , #x, __FILE__, __LINE__); \ exit(1); /*abort();*/ \ } \ } while (0) #endif プリプロセッサ・マクロの記述に慣れてないと?な代物かもしれない. このマクロ定義では、マクロ記述での基本的な事が結構含まれていて - #define マクロ定義は1行で記述しなければならず、複数行に別けたい場合は行連結指定の\ を使う必要がある - if条件式で !x でなく !(x) のように記述するのは、例えば a+1 が指定されると !a+1 のように式の意味が変わってしまって不味いので、それを回避するため。~ のでマクロ引数で''数値(式)''を受け取る場合は、''マクロ中では必ず()をつける''必要がある。 - #x は、マクロ引数の条件式を、出力するために文字列化する. - _&color(#0){_FILE_};_, _&color(#0){_LINE_};_ はコンパイル時に使われたそのファイル名(文字列)と行番号(整数)に置き換わる特殊なマクロ名. - {…} でなく、do { … } while (0) で全体を囲っているのは、~ if (…) assert(…); else …;~ のように使われた場合に {} のみだと~ if(…) {…}; else …;~ になって '';'' else がエラーになるのを回避するため.~ マクロ定義で複文を扱うときの常套句. //(コーディング規約で絶対にif,while等で{}を付ける、と決めていたとしても、ツールでチェックしているので無い限り、do {} while(0)にすべき) のようになっている。~ (あと、ここで出ていないマクロ機能関係としては##や _&color(#0){_func_};_ があるが、それらは後述) ※ 隣接文字列定数の連結機能を用いて&br(); "Assertion failed: " #x ", file " _&color(#0){_FILE__}; ", line %d\n"&br();のように書くこともできる.&br(); が、これだと実行時の引数は減るが、assert 毎にデバッグ文字列が作られstaticな文字列が膨れ上がりやすいので、止めておいたほうがいいだろう. ~ なお上記は、動作の雰囲気をみるため、あえて標準ライブラリのみでマクロ内だけで処理を終えているが、実際には #define assert(x) (!(x) && _assert_message(#x,__FILE__,__LINE__)) のような感じに、出力&プログラムストップ処理をまとめたサブルーチンを用意しているかもしれない。(このほうが、デバッグ処理として生成されるコード量が減るだろう) ~ ** static_assert c/c++の次の標準規格(c1x, c++0x)では、 static_assert(定数(条件)式, "エラー時のメッセージ") という感じで、コンパイル時に、定数式が真(非0)であるかをチェックし、 偽(0)だったら、エラーメッセージを表示する、という機能が追加される. 例えば (試してないのでたぶん) static_assert(sizeof(int) == 4, "intが4バイトでない"); static_assert はコンパイラ自身への追加機能で実現されるが、 エラーメッセージ出力無しならば、現行の c/c++でも似たような処理を用意できる. →定数式の結果が0のときに、コンパイルエラーになるような文を作ればいい.~ たとえば #define STATIC_ASSERT(x) typedef char static_assert_check[ (x) ? 1 : -1 ] のようにすれば、定数式 xの結果が真ならば static_assert_check という名のchar[1] の型がつくられokだが、偽だとchar[-1]でコンパイルエラーになる.~ typedef を記述できる箇所ならば、関数の内外で記述できる. >※ typedefでなく例えば structでメンバーに配列を持つのでもできるが、typedefのほうが単純で楽だろう.~ ※ コンパイラがどの規格に沿っているか……配列の添え字は、古いものだと0は NG だが gcc拡張やc99以降 0 は OK のため、NG として -1 を選んでいる. ~ ※ Cの場合でも配列添え字[0]がokなら、struct中での記述を可能にできたりする模様([[こちら>http://www.kijineko.co.jp/node/709]]を参照) さて c++ (および c でも同様な拡張をしているコンパイラ) なら typedef は同じ内容なら複数回定義できるが、c の場合は 名前(static_assert_check)が衝突してしまう。 なので名前の衝突回避としてマクロの連結機能## と 行番号 _&color(#0){_LINE_};_ を用いてその都度適当に新しい名前を生成することにする。 ''ダメ''な例を先に出すと #define STATIC_ASSERT(x) typedef char static_assert_##_&color(#0){_LINE_};_[ (x) ? 1 : -1 ] は static_assert__&color(#0){_LINE_};_ というふうに先に連結##が機能して固定の名になってしまい意味なし。 いつマクロ(名) が展開されるかが問題で、連結##より先に _&color(#0){_LINE_};_ が展開されるようにするには''マクロ引数''の展開を利用する。~ 以下のような連結マクロを用意。 #define M_CAT(a,b) M_CAT_2(a,b) #define M_CAT_2(a,b) M_CAT_3(a##b) #define M_CAT_3(x) x 実はこのへんコンパイラごとに微妙に非互換があり、M_CAT_3が不要なコンパイラも多いけれど vc だと必要。 // おそらくISO C99 準拠ならば M_CAT_3 は不要だけれど、(c99非準の) vc では必要。 (また、これではダメなコンパイラもあるようだけど、手持ちにないので未考慮。具体的には boost/preprocessor 等を参照のこと) これを用いて #define STATIC_ASSERT(x) typedef char M_CAT(static_assert_,__LINE__)[ (x) ? 1 : -1 ] のようになる。~ 行番号を用いているため、もちろん、直に同じ行に書くと衝突するし、別ヘッダでたまたま同じ行だったとしても衝突するが、 実使用上はそうそうぶつからないだろうでよしとしとく(ぶつかったら、適当に改行いれて行をづらす、と)。~ vc や gcc では _&color(#0){_COUNTER_};_ という使われる都度 値をカウントアップする特殊なマクロ名があるので、この手のものがあるコンパイラならば _&color(#0){_LINE_};_ のかわりに使うのも手。 C++の場合は (エラーメッセージを多少わかりやすくできたりするので) template を用いた実装のほうがよいかもしれない。 そのへんは BOOST_STATIC_ASSERT とか諸々あるので、それらを使うなり参考にするなり。 ** assert系マクロ作成 assertマクロは、エラーになったとき、変数の値等までは出力しない。 デバッガ上でとめられたならば、まだ値を見れる可能性もあるが、 ターゲット環境/ユーザー環境ではログは出せるけどデバッガは使えないことも多い。 ので、例えば assert と同じような仕組みで ASSERT_LIMIT(変数名,最小値,最大値) // 変数の範囲チェックマクロ ASSERT_PTR(ポインタ名) // ポインタが正常なメモリを指しているか のようなモノを用意して、エラーログとして変数とその内容、を出せると便利になる. ということで、そういうマクロを作ってみる. *** 下準備 1 abort(), puts() チェックマクロから使う基本ルーチンを用意する. 一番最初の assert マクロ例からすると~ printf(), exit(1)/*abort()*/ あたり。多少、複数の環境を考慮してマクロにしておく. とりあえず、コマンドラインツール向けならば #define ERR_ABORT() exit(1) // プログラム中断. 場合によってはabort() #define ERR_PUTS(s) fputs(s, stderr) // 一行を標準エラー出力 のような感じか. (マクロ名はとりあえず、ERR_ではじめることにする) ~ Winodows(のIDE)環境用ならば #define ERR_ABORT() DebugBreak() // ブレークポイント停止 #define ERR_PUTS(s) OutputDebugString(b) // デバッグ窓への出力 他のターゲット環境用の場合でも、win同様に ブレークポイントを設定できる場合はそれを、 なければ #define ERR_ABORT() (*(char*)0 = 0) のようにしておけばアドレスエラーでストップ、 デバッガ上でならば、運がよければ PC を変更して継続できるかもしれない. ターゲットでの1行出力は、 USB,LAN,232C等でログを出せるならばそれを、 無くて書き込みメディアが使えるならそこに、 あるいは、画面表示できるならば、それでよいだろう. (PG以外のスタッフがエラーをみることあるならば、できれば多少手間でも日本語表示も出来るようしとくほうが何かと楽) ~ ※ターゲットが貧弱な場合(やゲーム等で速度がクリティカルな箇所の場合)、 デバッガでメモリー覗けるのならば最悪2,3行分をメモリーに貯めるだけでも救われる場合もあり. ~ *** 下準備 2 : printf系 ERR_PUTS(s) 1行出力だけでは通常の利用で面倒なのでprintf形式も用意する.~ (c/c++共用のため. ただc++専用でも、なるべくデバッグ処理側でmalloc,new系を使われたくないので、stream系よりもprintfをまず使えるようにする) その準備として、まず、vsnprintfの辻褄あわせマクロを用意.(名前が微妙に違ったり、vsnprintfが無かったり) #define ERR_VSNPRINTF(b,l,f,a) vsnprintf(b,l,f,a) // 普通 #define ERR_VSNPRINTF(b,l,f,a) _vsnprintf(b,l,f,a) // win系 #define ERR_VSNPRINTF(b,l,f,a) vsprintf(b,f,a) // snprintfがない場合 とりあえずヘッダのみの利用を想定して、実処理を inline で記述(inline 自体が c だと__inline だったりするかもしれないが). static inline int err_printf(const char* fmt, ...) { char buf[1030]; va_list arg; va_start(arg,fmt); ERR_VSNPRINTF(buf, (sizeof buf), fmt, arg); va_end(arg); buf[(sizeof buf)-1] = '\0'; ERR_PUTS(buf); return 1; } 固定バッファだが、プログラム内部のデバッグメッセージ用なので、さして不都合はないとする. がバッファ溢れは怖いので、可能な限り vsnprintf系を使う.~ (追記: vcのvsnprintfはあふれた時最後に \0 を書き込んでくれない模様……ので、最後の位置に'\0'を置いてガード) これを使ったマクロを用意. やり方は古いC(プリプロセッサ)で出来る方法と、C99以降の可変マクロを用いる方法がありえる. 古い Cでも使える方法は #define ERR_PRINTF(x) err_printf x として、使うときは ERR_PRINTF(("error : %s\n", msg)); のように 二重括弧で記述する.~ C/C++の一般的な書き方でないので、初見は気持ち悪いかも知れず、 また、C/C++に慣れていない人には嫌がられるかもしれないが、 デバッグ専用処理と納得できれば、むしろ見た目で判別しやすいので メリットとも考えられる.(個人的には見目を優先してこちらが好み) 可変引数マクロを使った場合は、 #define ERR_PRINTF(...) err_printf(__VA_ARGS__) で、ERR_PRINTF("error : %s\n", msg); といったところ. ~ ※ 標準エラー出力やターゲット環境用に回りくどいことしているが、エラー出力が標準出力で問題なければ #define ERR_PRINTF(x) printf x や #define ERR_PRINTF printf とするのがてっとりばやいだろう。(ターゲット環境によっては開発用ログ出力関数が printf の場合もあるし) ~ ※ printf系は普通に使う分にはmalloc系が使われることは無いように思われるが、全くないかというとそうではなく、実装次第の話で、数$による順番変更や、やたら桁数が多い浮動小数点数の%f表示があると、使われるかもしれない. (ので表示の質的に問題なければ %f より %g のほうが無難かも) ~ *** 下準備 3: stream系 なるべく、デバッグ処理側でアロケータが使われたくないとはいえ、 ポインタのチェック以外では、少々のことなら大丈夫、という考え方もある(環境次第). (※malloc系でNULL返されてる状況では、デバッグ処理がアロケートしようとしてもハングだろうで) なので、c++ の場合 stream 系も利用するのも手. ~ 変数内容表示等で型別のマクロを使わなくてもすむのは、かなりメリットでもあり. 実装に関しては、sprintfの代用としてはベターなので strstream を用いてみる.(レガシーで将来破棄される予定だろうが、今はまだあるので). #define ERR_STREAM(msg) do { \ char _buf_[1030]; \ std::strstream _ss_(_buf_, sizeof _buf_); \ _ss_ << msg << std::ends; \ ERR_PUTS(_buf_); \ } while (0) 使うときは ERR_STREAM("~~~\n"); ERR_STREAM("pos=(" << x << "," << y << ")\n"); といった感じか。~ ※ fsteam や stringstream は ファイル処理や std::string のパフォーマンスの悪さが足を引っ張るが、 (当然実装次第だけど)原理からすれば streambuf は printf系と比べてそうひどくない、というか、 (実行時にフォーマット解析して分岐するよりコンパイル時に型確定する分) 場合によっては速くなるかもしれない。 *** 下準備 4: 実メモリへのポインタのチェック グローバル変数やローカル変数、ヒープ、プログラム領域等、 通常のアプリがメモリとして指すポインタのチェックとしては、 NULLチェックだけでなく、 可能ならば範囲チェックをしたほうがいい. (I/Oとか特殊なアドレスは別枠のチェックとして考える). アドレス空間へのメモリーの割り当ては、当然、ハードごとに違いはあるけれど、 32ビットCPU以上の環境の場合、0からの数十KB,数百KB, あるいは、ケツからの数十KB,数百KBなどは、少なくとも、通常のプログラムや作業メモリーが割り当てられることは無いだろう、と考えられる. とりあえず、そうだと考え、前後16Kを非RAMと仮定として #define ERR_IS_RAM_PTR(p) ((size_t)(p) > 0x3fff && (size_t)(p) < 0xffffC000) のようなチェックマクロを用意してみる. win32 ならば 前 1MB, 後 256MBは非RAMにできるかもしれない(?) #define ERR_IS_RAM_PTR(p) ((size_t)(p) > 0xfffff && (size_t)(p) < 0xf0000000) PC環境でなく、コンシューマ機やターゲット機器の場合は、 仮想記憶をフル活用はせず、 RAMは固定範囲になっていることも多いと思われるので、 さらに、具体的な範囲を判定できる可能性が高い。 そのようにして用意した ERR_IS_RAM_PTR(p) を用いて ERR_ASSERT_PTR(p) 等を作ることになる(後述). ~ こういうチェックは OS 環境下だけで作業していると、 つい汎用性を考えると手を出さずじまいになりやすいが、 他の環境の事情なんか後回しで、 今の環境でのデバッグを優先する、と考えればありだろうと思う. たとえ、アドレス空間の先頭 数キロバイトだったとしても、 NULLチェックだけではひっかからないエラー (たとえばNULLポインタでのメンバー変数のアドレス参照等)が ひっかかるようになれば、メリットだろう. (まして、範囲がかなり限定されるターゲット環境の場合は、やらないと損では?と) **** アライメント・チェック C++の場合、templateを用いて基本型のアライメントをチェックすることが可能. //基本 (アライメントはなんでもokにしとく) template<typename T> bool err_check_ptr_align(T*) { return 1; } // ポインタのポインタ. template<typename T> bool err_check_ptr_align(T** p) { return !( (std::size_t)p & (sizeof(T*)-1) ); } // intポインタの場合のアライメントチェック. template<> bool err_check_ptr_align<int>(int* p) { return !( (std::size_t)p & (sizeof(int)-1) ); } // unsigned intポインタの場合のアライメントチェック. template<> bool err_check_ptr_align<unsigned>(unsigned* p) { return !( (std::size_t)p & (sizeof(unsigned)-1) ); } といった感じに、他にもshort,long,long long の符号付無, float, double, long double も同様に実装する. ※long double は2の乗数とは限らないのでちょっと例外的.~ ※追記: 32ビットCPUだとdouble,int64_t結局 4バイトアライメントで配置されるため、下手に8バイトでチェックしないほうがよいかも.~ 追記: 書いておいてなんだが、手間や速度等思うと、ポインタについては範囲チェックだけでも十分、という気もする. *** 消えるマクロ、消えないマクロ 最終的な製品のコンパイルでは、すべてのデバッグマクロの実体は消してしまうが、 開発中は、RELEASEコンパイル時でも、特殊ルートや、 部分的にチェックしたい場合もあるため、 同じ動作だけど、設定によって消えるマクロ、消えないマクロを用意 しておくほうが便利。 とりあえず、ここでは、 NDEBUG時に消えてなくなるマクロは DBG_ ではじめ、 消さないマクロは ERR_ ではじめるとする. 例えば DBG_ASSERT(x) は NDEBUG定義で消えるが、 ERR_ASSERT(x) なら残る。もちろん、大半はDBG_ASSERT(x)で書くことになる. ※すべてを2重化するのは面倒なので、実際には部分的... *** チェック用のマクロ 以下のようなマクロを用意してみようと考える. DBG_ASSERT(x) x が成立するか. DBG_ASSERT_PTR(p) アドレスpがRAMとしておかしくないか(NULLはNG) DBG_ASSERT_PTR0(p) アドレスpがRAMとしておかしくないか(NULLはOK) DBG_LIMIT(a, mi, ma) aが[mi,ma]の範囲に収まるか(調度mi,maも含む) DBG_EQ(a,b) aとbが等しいか DBG_LIM_I(a,mi,ma) DBG_LIMITのint専用版 DBG_EQ_I(a,b) DBG_EQのint専用版 (同様にlong long,float,double等も) 多少凝ったチェック等も用意してたこともあるが、 (たとえばポインタチェックでアライメントも指定する等)、 使っていて面倒だと、使わなくなったりおざなりな指定になったりするため 結局、そこそこ単純なのだけでいい、というのが結論。 実装例としては(面倒なんで3つだけ) #define ERR_ASSERT(x) do { \ if (!(x)) { \ ERR_PRINTF(("%s (%s): %s, failed.\n", __FILE__, __LINE__, #x)); \ ERR_ABORT(); \ } \ } while (0) #define ERR_ASSERT_PTR(p) do { \ if (!(ERR_IS_RAM_PTR(p) && err_check_ptr_align(p))) { \ ERR_PRINTF(("%s (%s): bad pointer(%s=%#p).\n" \ , __FILE__, __LINE__, #p, p)); \ ERR_ABORT(); \ } \ } while (0) #define ERR_LIMIT(a,mi,ma) do { \ if (!((mi)<=(a)&&(a)<=(ma))) { \ ERR_STREAM(__FILE__ << " (" << __LINE__ << "): " \ << #a << "=" << (a) << ", out of range[" \ << (mi) << ',' << (ma) << "]\n"); \ ERR_ABORT(); \ } \ } while (0) #ifdef NDEBUG #define DBG_ASSERT(x) #define DBG_ASSERT_PTR(p) #define DBG_LIMIT(a,mi,ma) #else #define DBG_ASSERT(x) ERR_ASSERT(x) #define DBG_ASSERT_PTR(p) ERR_ASSERT_PTR(p) #define DBG_LIMIT(a,mi,ma) ERR_LIMIT(a,mi,ma) #endif 上記は、ERR_ABORT() がvoid関数であることを前提にしているが inline int ERR_ABORT2() { ERR_ABORT(); return 1; } のようにintを返すように定義でもしておけば、 #define ERR_ASSERT(x) (!(x) && (ERR_PRINTF(("%s (%s): %s, failed.\n" \ , __FILE__, __LINE__, #x)), ERR_ABORT2() ) ) のようにもかける. 生成サイズ的にはさして違いはないだろうけど、記述量的にはシンプルになる. が、if 文の形のほうがまだわかりやすい、と感じる人も多そうな気はする. ※ void exit(int); の場合 #define ERR_ABORT2() (((int (*)(int))exit)( 1 ), 1) のように無理やりキャストするのも手……だが実際の定義では _&color(#0){_cdecl}; やら &color(#0){_};_declspec _&color(#0){_attribute_};_ の指定もからんでコンパイラごとの違いが面倒かもでinline関数するのが楽かも、と。 **** タグジャンプ 先のマクロでのエラーメッセージは、すべて~ ファイル名 (行番号)~ で始まるようにしている。 dos/win系のテキストエディタでは、この形式でかかれたテキストを開いて~ そのファイルのその行へ移動~ するタグジャンプ機能(キー)が付いていることが多いため. また MS Visual Studio のログ出力窓においても、この形式でかかれた行をマウスでダブルクリックすることで、そのソースファイルのその行に移動できる。 なので、この形式でエラー吐くのがwin系ではベターだろう. ~ が、面倒なことにデフォルトの assert は、この形式になっていないことが多い。 assertチェックにひっかかったとき、多少面倒なので、可能ならば assertマクロ を再定義して乗っ取ってしまうのも手. 一定規模以上のプログラムの場合、共通ヘッダ(あるいはプリコンパイルヘッダ) を用意するだろうで、その中で、#include <assert.h>のあとで、 #undef assert #define assert(x) DBG_ASSERT(x) のように再定義する. 運がよければそれで ok... が、コンパイラによっては#include <assert.h> をするたびに assert マクロが再定義(undef&define)されるものもあるようで(vc)、その場合は、個別の assert.h のincludeを可能な限り #ifndef assert #include <assert.h> #endif のような感じに assert 定義の有無をチェックするようにすればマシになるかもしれない. ~ *** _&color(#0){_func_};_ C99以降対応の(あるいは一部を取り入れた)コンパイラでは _&color(#0){_func_};_ に、現在実行中の関数の名前が入っている. これを利用してデバッグ出力で、 DBG_PRINTF(("%s (%d): %s: ...\n", __FILE__, __LINE__, __func__ )); のように関数名も一緒に出力できる. ソース行数が変動することを思えば関数名がわかるのは有益なことが多いだろう. ただ vc等 C99対応を見送ったコンパイラでは _&color(#0){_func_};_ 対応していないものもあり、規格化以前からある各コンパイラの同様の機能を判別して使うことになる。 vc,gcc系では _&color(#0){_FUNCTION_};_ の名で使えるので、こちらのほうが無難かもしれない。 C++ の場合、クラス情報無しのメンバー関数名のみだったり、コンストラクタ等は簡易な名前だったり等、コンパイラによって名前がどうなるか、ばらつきが多く、紛らわしいだけで役たたずになる場合もある. なので、コンパイラを判別して使う手間や、出力文字数(レイアウト)、等鑑みて、やっぱり関数名文字列を使わない、という選択もあるだろう.~ ※関数名だけの _&color(#0){_func_};_,_&color(#0){_FUNCTION_};_ とは別に関数定義を文字列化した名前を別途用意しているコンパイラもある。g++ だと _&color(#0){_PRETTY_FUNCTION_};_、 vcだと _&color(#0){_FUNCSIG_};_ 。(ついでに vc では_&color(#0){_FUNCDNAME_};_ でマングル化された名前文字列になる模様) なお、_&color(#0){_func_};_ はプリプロセッサの文字列でなくコンパイラ側が用意する文字列なのでマクロで連結等はできない.~ (※ gcc の _&color(#0){_FUNCTION_};_ は c99確定以前の一時期(v2.9?台)は連結可能だったかもしれない) ---- (このネタ関係のソースとして [[dbg.h>http://www.6809.net/tenk/html/sbr/dbg.h.html]] [[(zip)>http://www.6809.net/tenk/html/sbr/dbg.zip]] ) ---- 2011-07 static_assert関係を再再修正… やっぱりCだと定義内容が同じでも同名のtypedefは何度でもできなかった(c++ およびvc拡張だった). 間抜けすぎでした。~ 2013-06 vcでは _&color(#0){_func_};_ に対応していなかったことを追記. 普段は使わないか辻褄合わせしていたようで失念していました. [追加修正] _&color(#0){_FUNCDNAME_};_ は _&color(#0){_func_};_ と同じでなくマングル化された名前だった.
凍結する
凍結しない
タイムスタンプを更新
テキスト整形のルールを表示する
[
凍結
|
差分
|
添付
|
リロード
] [
新規
|
ヘルプ
]
Last-modified: 2013-06-16(日)