tomyamaのブログ

日記・雑記。

C言語 <assert.h> デバッグ用の診断機能

C言語の「assert.h」を使ったサンプルプログラムです。

たまに見かけるコードなのですが、自分では今までに使った事が無くて、今回初めて使ってみました。

 

 

  • assert() 条件の検証
    • 準拠 POSIX.1-2001, POSIX.1-2008, C89, C99
    • #include <assert.h>
    • void assert( int expression );
    • expressionが偽の場合、プログラムを中止する
    • assert.hをインクルードする前にNDEBUGマクロを定義しておくと、コードを無効化できる。この仕組みを使うとデバッグ用のassert()の呼び出しコードを残したままのソースコードをリリースすることが可能。

 

CU_ASSERT()と同じような機能なのですが、偽の場合にプログラムをABORTしてしまうところが異なります。


目次


サンプルコード

[assert.c]

/****************************************************************//**
 * \file  assert.c
 *
 * \brief  assert()関数のサンプルアプリケーション
 *
 * - 文字コード・改行コード
 *     - 文字コードUTF-8(BOM無し)
 *     - 改行コードは LF
 *
 * \author   tomyama  Oct 2022
 * \version  $Revision$
 * \date     $Date$
 ********************************************************************/

#include <stdio.h>

//#define NDEBUG
//#ifdef MYDEBUG /* Not RELEASE */
// #undef NDEBUG
//#endif
#include <assert.h>         /* assert() */
#ifndef NDEBUG
 #include <errno.h>         /* errno */
 #include <string.h>        /* strerror() */
 #include <signal.h>        /* signal() */
#endif /* ! NDEBUG */


/*+++++++[ 定数定義 ]+++++++++++++++++++++++++++++++++++++*/

#ifdef NDEBUG
 #define preparingAssert()
#endif /* NDEBUG */


/*+++++++[ 型定義 ]+++++++++++++++++++++++++++++++++++++++*/


/*+++++++[ 関数プロトタイプ ]+++++++++++++++++++++++++++++*/

static void cleanup( void );
#ifndef NDEBUG
 static int  preparingAssert( void );
 static void sighdl_abort( int signum );
#endif /* ! NDEBUG */


/*+++++++[ 変数定義 ]+++++++++++++++++++++++++++++++++++++*/


/*+++++++[ 関数定義 ]+++++++++++++++++++++++++++++++++++++*/

int main( int argc, char *argv[] )
{
    preparingAssert();

    assert( argc == 1 );    /* 診断コード: 引数が指定されていない事 */

    printf( "%s(): 正常系の終了パス: assert()が効いた時にはここは通らない\n", __func__ );
    cleanup();
    return 0;
}


static void cleanup( void )
{
    /* 何かする */
    printf( "%s(): 何かする\n", __func__ );
}


#ifndef NDEBUG
static int preparingAssert( void )
{
    /* SIGABRTシグナルを受信した時の動作を変更する */
    if( signal( SIGABRT, sighdl_abort ) == SIG_ERR ){
        fprintf( stderr, "%s(): error: signal(): <%d>%s\n",
            __func__, errno, strerror( errno ) );
        return errno;
    }
    return 0;
}


/*************************************************
 * シグナルハンドラ << SIGABRT >>
 *************************************************/

static void sighdl_abort( int signum )
{
    printf( "%s(): abnormal end: abort !!!\n", __func__ );
    cleanup();
    return;
}
#endif /* ! NDEBUG */

 

実行例

Windows 10, Visual Studio 2022, x86 Native Tools Command Prompt

デバッグ用にコンパイル

>set CFLAGS=/nologo /source-charset:utf-8 /execution-charset:shift_jis
>cl %CFLAGS% /Fe:assert_msvc assert.c
assert.c
>
>assert_msvc.exe     & echo exit_status: %ERRORLEVEL%
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
>
>assert_msvc.exe 123 & echo exit_status: %ERRORLEVEL%
Assertion failed: argc == 1, file assert.c, line 54
sighdl_abort(): abnormal end: abort !!!
cleanup(): 何かする
exit_status: 0
>

  • WindowsのVisual CではABORTしてもステータスは0のまま。他のプラットフォームとは異なり異常終了扱いにはならない。

 

リリース用にコンパイル
  • NDEBUGマクロを定義してassert()関連のコードを除去

>cl %CFLAGS% /Fe:assert_msvc assert.c /DNDEBUG
assert.c
>
>assert_msvc.exe     & echo exit_status: %ERRORLEVEL%
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
>
>assert_msvc.exe 123 & echo exit_status: %ERRORLEVEL%
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
>

  • リリース用のプログラムはどちらも同じ結果になる。( ≒ assert()文が除去できている。)

 

CentOS Stream 9, gcc (GCC) 11.3.1

デバッグ用にコンパイル

$ gcc -Wall -O2 -o assert_centos9 assert.c
$
$ ./assert_centos9     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_centos9 123 ; echo "exit_status: $?"
assert_centos9: assert.c:54: main: Assertion `argc == 1' failed.
sighdl_abort(): abnormal end: abort !!!
cleanup(): 何かする
中止 (コアダンプ)
exit_status: 134
$

 

リリース用にコンパイル
  • NDEBUGマクロを定義してassert()関連のコードを除去

$ gcc -Wall -O2 -o assert_centos9 assert.c -DNDEBUG
$
$ ./assert_centos9     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_centos9 123 ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$

  • リリース用のプログラムはどちらも同じ結果になる。( ≒ assert()文が除去できている。)

 

Cygwin 3.3.6-1, gcc (GCC) 11.3.0

デバッグ用にコンパイル

$ gcc -Wall -O2 -o assert_cygwin assert.c
$
$ ./assert_cygwin.exe     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_cygwin.exe 123 ; echo "exit_status: $?"
assertion "argc == 1" failed: file "assert.c", line 25, function: main
                  sighdl_abort(): abnormal end: abort !!!
cleanup(): 何かする
Aborted
exit_status: 134
$

 

リリース用にコンパイル
  • NDEBUGマクロを定義してassert()関連のコードを除去

$ gcc -Wall -O2 -o assert_cygwin assert.c -DNDEBUG
$
$ ./assert_cygwin.exe     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_cygwin.exe 123 ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$

  • リリース用のプログラムはどちらも同じ結果になる。( ≒ assert()文が除去できている。)

 

Android, Termux 0.118.0, clang 15.0.2

デバッグ用にコンパイル

$ gcc -Wall -O2 -o assert_termux assert.c
$
$ ./assert_termux     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_termux 123 ; echo "exit_status: $?"
assert.c:25: int main(int, char **): assertion "argc == 1" failed
sighdl_abort(): abnormal end: abort !!!
cleanup(): 何かする
Aborted
exit_status: 134
$

 

リリース用にコンパイル
  • NDEBUGマクロを定義してassert()関連のコードを除去

$ gcc -Wall -O2 -o assert_termux assert.c -DNDEBUG
$
$ ./assert_termux     ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$
$ ./assert_termux 123 ; echo "exit_status: $?"
main(): 正常系の終了パス: assert()が効いた時にはここは通らない
cleanup(): 何かする
exit_status: 0
$

  • リリース用のプログラムはどちらも同じ結果になる。( ≒ assert()文が除去できている。)