NDEBUG と DEBUG
デバッグビルドしているのに、デバッグコードが動作しない、
でもassertは機能している。
何かおかしい・・・
NDEBUG と DEBUG とは?
NDEBUG と DEBUG は、C言語において デバッグビルドやリリースビルドの動作を制御するためのマクロ。特定のコード(主にDebug関連のコード)を有効または無効を切り替えるために使用されている。
- NDEBUG マクロ
NDEBUG は「No Debug」の略。
ANSI C 標準化の過程で assert マクロを制御するための仕組みとしてC89で導入された。主にリリースビルドにおけるデバッグ機能を無効化するために使用されている。
標準Cでは、NDEBUG が定義されているとassert 呼び出しは実行されなくなる。リリースビルドではNDEBUGを定義して、デバッグ用のエラーチェックを無効化することで、パフォーマンスの向上を図るためである。デバッグ段階では assert を利用してプログラムの健全性を確認するが、リリースビルドではそのようなチェックが不要となり、性能低下やエラーリスクを避けるために assert を無効化する。
標準Cにおける NDEBUG の役割は assert の無効化に限定されているが、この特性からプロジェクト独自のデバッグコードや機能を有効/無効にする目的で NDEBUG が利用されていることもある。
- DEBUG マクロ (_DEBUG マクロ)
DEBUG は、デバッグビルド時に使用されることが多いが、標準Cで規定されるものではなく、開発者が独自に定義するマクロ。
DEBUG を定義した場合、デバッグ用のコードやログ出力を有効化する。たとえば、変数の状態表示や詳細なエラーメッセージの出力など、開発中にのみ必要な情報を出力するコードを DEBUG を利用して管理する。
また _DEBUGは、VisualStudio,SDK,DDKなど 主にMicorosoftの開発環境で使われている。使い方はDEBUGと同様。
NDEBUG と DEBUG の矛盾
NDEBUG と DEBUG の両方が定義されている場合、あるいはどちらも定義されていない場合は、デバッグ用コードの一部が有効で、一部が無効になり、リリースビルドとデバッグビルドが混ざったような状態になる。プログラムの動作に一貫性がなくなり、予期せぬ挙動を引き起こす危険があり、一般に避けるべき状態である。
矛盾した状態を避けるため、どちらか一方のみを定義するのが望ましい。
NDEBUG と DEBUG が存在する理由
矛盾を招く可能性がある NDEBUG と DEBUG が存在するのには、C言語やソフトウェア開発の歴史的背景や設計意図が関係している。
なぜ分かれている?
NDEBUG と DEBUG が一つにまとめられず、分かれている理由は、それぞれ異なる目的を持っているため。
-
NDEBUG の役割
NDEBUG は主にリリースビルドに焦点を当てて作られている。リリースビルドではプログラムがデバッグ済みであると想定され、デバッグ用のコード(特に assert マクロ)は不要とされる。NDEBUG が定義されると、assert マクロが無効化され、リリースビルド時に不必要なエラーチェックを省き、パフォーマンスが最適化されるよう設計されている。
このように、NDEBUG は主にリリースビルドにおいて、デバッグ用のコードを無効化するための手段として提供されている。 -
DEBUG の役割
DEBUG は公式な標準ではなく、独自に使用されるマクロとして広く使用されている。開発者がデバッグ情報を手軽に有効/無効化するために DEBUG を定義し、特定のデバッグコード(ログ出力や変数の追跡など)を条件付きで有効化する手段として使われている。これにより、柔軟かつ簡単にデバッグ機能を組み込むことが可能になるが、標準のマクロではないため一貫した使い方が定まっていない。結果として開発者によって異なる扱われ方をされており、NDEBUG と組み合わせた場合に矛盾が生じやすい。
NDEBUG の目的は、主に assert マクロを無効化し、リリースビルドでパフォーマンスを最適化することにある。
一方、DEBUG の目的は、デバッグ用の追加コード(ログやチェック)を開発者が自由に組み込むためのマクロであり、デバッグ機能の制御を容易にすることにある。
NDEBUG は標準の assert の制御、DEBUG は開発者が独自に使用するデバッグコードの制御を目的として使用されている。
NDEBUG と DEBUG は、それぞれ独立した用途、異なる目的を持っているため、1つに統一されずに分かれている。
歴史的背景
C言語は1970年代から使われており、特に標準化以前の時代にはデバッグ支援機能がほとんどなく、デバッグは開発者自身の工夫に依存していた。開発者はデバッグ情報を簡単に切り替えるため、DEBUG マクロのような仕組みを用いて、デバッグコードを手軽に有効化/無効化できるようにした。
一方、NDEBUG は ANSI C の標準化の過程で、assert マクロを制御する仕組みとして導入された。assert は開発中には有効にしたいが、リリース時には無効にしたいというニーズに応じて生まれたものである。
この結果、異なる目的を持ったマクロが共存するようになり、両者を同時に使用すると矛盾が生じやすい状況になっている。
ビルドシステムでの定義
ビルドシステムによっては、デバッグビルドとリリースビルドで NDEBUG や DEBUG マクロが自動的に定義されるようプリセットされていることがある。
以下に、主要なビルドシステムでの扱いについて説明する。
CMake
CMakeでは、デフォルトでビルドのタイプを指定するオプションがあり、以下のような設定でそれぞれのマクロが定義される。
-
Debug ビルド
-D CMAKE_BUILD_TYPE=Debug と指定すると、最適化が無効化され、デバッグシンボルが生成されるように設定される。CMakeの標準設定では、CMAKE_BUILD_TYPEがDebugに設定された場合でも、DEBUGマクロは定義されない。 -
Release ビルド
-D CMAKE_BUILD_TYPE=Release と指定すると、自動的に NDEBUG マクロが定義され、デバッグシンボルは生成されず、最適化が有効化される。
CMakeでは、他にも RelWithDebInfo(最適化付きデバッグ情報)や MinSizeRel(最小サイズのリリース)というビルドタイプがあり、これらも NDEBUG マクロが定義される。
標準設定でDEBUGマクロは定義されないため、DEBUGマクロを使用したい場合は、CMakelist.txなどにマクロを定義する必要がある。
Visual Studio (MSBuild)
Visual Studio のプロジェクトでは、ビルドの設定(Debug と Release)によって、Defaultでこれらのマクロが適用されるよう設定されている。
-
Debug ビルド
Debug 設定を選択すると、_DEBUG マクロが定義される。また、デバッグシンボルが生成され、最適化は無効化される。includeするheaderによっては、_DEBUGが定義されていると自動的にDEBUGマクロを定義される場合もある。 -
Release ビルド
Release 設定では、NDEBUG マクロが定義され、assert などのデバッグ関連のコードが無効化される。さらに、最適化が有効化され、実行時のパフォーマンスが向上する。
GNU Make
GNU Make にはDebugやReleaseというビルドタイプの概念はない。ビルドタイプを分ける場合、開発者自身でビルドスクリプト(Makefile)に CFLAGS や CPPFLAGS マクロを定義する必要がある。
ビルドシステムや開発フレームワークによって、デバッグビルドとリリースビルドで、NDEBUG/DEBUG/_DEBUGがどのように定義されるかは異なっている。
これらのマクロを使用する場合、ビルドシステムの振る舞いを十分に理解しておく必要がある。
プラクティス
-
NDEBUG と DEBUG を同時に定義しない
リリース:NDEBUG だけ定義、デバッグ:DEBUG だけ定義
ビルドシステム(CMake や Makefile など)を使い、リリースビルドでは NDEBUG を定義(must)し、デバッグビルドでは DEBUG を定義(reccomend)するという方法で一貫性を保つ。
Microsoft系のビルドシステムでは、通常 DEBUG や_DEBUG が定義されているので特に何もする必要はない。 -
コードの先頭で、DEBUGを定義
多くのビルドシステムでは、リリースビルドでNDEBUGが定義されているため、非標準のDEBUGだけをNDEBUGと整合するようローカルに定義する。
ただし、ビルドシステムでDEBUGが定義されていると2重定義の警告になるので注意。#ifdef NDEBUG #undef DEBUG #else #define DEBUG #ENDIF
-
C標準のNDEBUGだけを使う
デバッグコードは NDEBUGの否定を使う。NDEBUGはC標準で規定されているマクロであるため、標準を重視する開発者に支持されている方法。
ただし、2重否定になることや'n'の存在を見落としやすいことから、可読性に劣る。#ifndef NDEBUG #if !defined(NDEBUG)
NDEBUG と DEBUG の使用については、様々な流派がある。
使用するプラットフォームや開発ターゲットのやり方を十分理解すること。