ch32fun(旧 ch32v003fun)での開発において assert
マクロを使う場合、__assert_func
が未定義だというエラーが出ることがあります。筆者が遭遇したエラーメッセージは次のようなものでした。
make build
《中略》
/usr/lib/gcc/riscv64-unknown-elf/13.2.0/../../../riscv64-unknown-elf/bin/ld: /tmp/ccdNAgAX.ltrans0.ltrans.o: in function `TIM1_SetPulseWidth':
/home/uchan/workspace/github.com/uchan-nos/eleclab/ch32v003fun_projects/ledtester2/periph.c:81:(.text.TIM1_SetPulseWidth+0x38): undefined reference to `__assert_func'
関数 TIM1_SetPulseWidth
の中で assert
マクロを使っていました。関数の実装を次に示します。
void TIM1_SetPulseWidth(uint8_t channel, uint16_t width) {
switch (channel) {
case 0: TIM1->CH1CVR = width; break;
case 1: TIM1->CH2CVR = width; break;
case 2: TIM1->CH3CVR = width; break;
case 3: TIM1->CH4CVR = width; break;
default: assert(0);
}
}
引数 channel
の値が想定外のときに assert
で検出します。呼び出し元はこんな感じです。
TIM1_SetPulseWidth(led_current_ch, pw);
led_current_ch
は uint8_t
型のグローバル変数です。
__assert_func
の未定義エラーを回避するためには、この関数の定義をいずれかのソースコードに含める必要があります。筆者は次のような実装としました。
void __assert_func(const char *file, int line, const char *func, const char *expr) {
printf("assertion failed inside %s (%s:%d): %s\n", func, file, line, expr);
while (1) {
__WFE();
}
}
__assert_func
を定義せずに assert
が使える場合assert
に与えた式が真であると評価できる場合に __assert_func
を呼び出すコードが省略されることがあります。この場合、そもそも __assert_func
は呼び出されないので、定義する必要がありません。
この挙動は大きなメリットがあります。あえて __assert_func
を未定義にしておくことで、assert
の引数が偽になったことをリンクエラーとして検出できるのです。もし __assert_func
を定義してしまっていると、リンクエラーにはならず、実行時にのみエラーが検出されます。実行してみないとプログラミングのミスに気付けないより、リンクエラーとして気付ける方がずっと嬉しいです。
これはコンパイラの最適化に頼った動作のため、必ずそうなるというわけではありません。試しに先述の関数呼び出しを次のように変えたところ、__assert_func
の実装が不要になりました。
switch (led_current_ch) {
case 0: TIM1_SetPulseWidth(0, pw); break;
case 1: TIM1_SetPulseWidth(1, pw); break;
case 2: TIM1_SetPulseWidth(2, pw); break;
case 3: TIM1_SetPulseWidth(3, pw); break;
}
コンパイラは TIM1_SetPulseWidth
の引数 channel
が 0~3 のみだと分かると assert(0)
が呼ばれることはないと判断できるわけです。assert
の失敗をリンク時に検出するためだけに上記の様な冗長な書き方にしたいかというと、それは嫌ですけどね。