hiro99ma blog

何か技術的なこと

clang: 私のC言語歴と_Noreturn

はじめに

最近のコンピュータ言語は、新しいだけにアップデートもしばしば行われる。
インターネットが普及する前から存在する言語は、なんとなくだがアップデートはあまりないイメージがある。 調べてはいないが。
C言語も結構古い言語になると思うが、アップデートが行われているようだ。

昔話

私が知っているところでは、ANSI-C、C89、C99 くらいである。
C89 と ANSI-C のどちらが古いのかは知らないが、その辺の時代は混沌としていて gcc のようなオープンソースが主体になれるような状況でもないので コンパイラのメーカーが提供するのが普通だったと思う。
私も学生のときは UNIX で cc だったり CC だったりでコンパイルしていたし、 就職してからはどこかのメーカーが発売していた Cコンパイラを使っていた記憶がある。
まだ void* がなかったり struct を戻り値で返せなかったりだったし(C89かな?)、最適化するとおかしくなるバグがあったりと面倒だった。

その後で組み込みLinux というものに出会った。仕事でだが。
まだ Hardhat とかがない時代(すぐ出てきたので私が知らなかっただけだろう)で、展示会でも「組み込み環境でDBを使うことがあると思いますか?」みたいなアンケートがある時代だ。

25年くらい前の話だ。四半世紀というやつか。

C99

もう 2000年を過ぎていたのだが C99 がデフォルトになっていないことが多かったように思う。
使いたいのは、// での 1行コメントとか、for() で局所変数を使うところだと思う。
そういえば stdint.huint8_t などや stdbool.hbool も C99 だったか。

私が C言語で書くときはだいたい C99 以降になっていると思う。
ただ、C99 の正式な仕様書を読んだことはない。 見たことがあったとしても gcc での仕様だと思う。
最近はオライリーさんの本を参照しているな。

C11

C言語のバージョンが上がっても C99 くらいでもういいだろうと思っている。
たぶんだが、劇的に変化することはないという勝手な予想である。
どちらかといえばコンパイラの機能を新たに知る(新機能が追加されるということもあるだろうが、たぶん知らない機能の方が多い)ことで C言語の仕様を知るということが多いんじゃなかろうか。

オライリーさんの本を読んでいて知ったのが、C99 の次に C11 があるということだった。

本で C11 が目に付いたのも _Noreturn という組み込みくらいでしか使わないような識別子に気付いたからだった。

_Noreturn

abort() などが _Noreturn になっていることを知った。

C11 から追加された識別子(keyword)だが gcc では __attribute__ ((noreturn)) で扱えるようになっている。
検索で探したら gcc 4.7.2 には載っていた。

Zephyr では FUNC_NORETURN というマクロを使うことで C11 でなくても _Noreturn 相当のことができるようになっている(gcc の noreturn)。
vscode で FUNC_NORETURN にマウスカーソルを当てるとこれがホバーされた。

#define FUNC_NORETURN __attribute__((__noreturn__))
Expands to:

__attribute__((__noreturn__))

__attribute__ は関数の中身を書き始める { の手前に置くものかと思っていたけど、頭に置いても良いのかな?
あるいは単にホバーだから簡易表現されただけかもしれない。
どのヘッダファイルなのかまでは見てないのだ。

自分で _Noreturn 相当の関数を書く場合、明らかにその関数が終わらないように書かないと warning が出る。
zephyr で k_sleep(K_FOREVER) と書いておけばいいだろうと思ったのだが、それではダメだった。
while (1) {} でも出なくなったが、本当にそこに陥ると嫌なので abort() 系の _Noreturn な関数で終わるか、zephyr なら k_fatal_halt() という手もある。

ここの修正では while でぐるぐる回すことにした。

abort()exit() にするか悩んだのだが、プラットフォームに依存した関数の挙動はよくわからんでね。
説明文もないし。

k_fatal_halt は誰が使ってよいのか

使うなら k_fatal_halt の方がまだ挙動が分かるのだが、これの引数が unsigned int となっている。
おそらく enum k_fatal_error_reason が取り得る値になると思う。

そして K_ERR_ARCH_START 以降がプラットフォームだか自分だかで好きに使ってよいエラー番号の始まりなのだろう(_STARTなので)。
Arm の場合はここのようだ。

MPU ごとに定義するようであれば、k_fatal_halt() はユーザが使うものではないな。

致命的なエラーの対応

Nordic AI によるとk_sys_fatal_error_handler() がデフォルトでは呼び出されてログ出力した後リセットするらしい。
オーバーライドも可能とのこと。

以前の記事 では assert_post_action() をオーバーライドすることでそういうことをやろうとしたのだが、あれは _ASSERT() できるときにしか効果が無い。
この方式は k_panic() を呼び出せば良いようなので扱いやすそうにみえる。

デフォルト実装ではどう動くか分からないので、オーバーライドした方が安心できそうだ。
LED をチカチカさせて無限ループさせておけば、WDT が有効ならリセットしてくれるだろう。
ただ、それが起動してすぐ起きてしまうような状態に陥っていると激しく電力を消費することになってしまうだろう。
WDT は昔は仕事で使った CPU だと外付けでやらないとえらく短い時間でしか耐えられなかった。
そういえば nRF シリーズで WDT を使ったことが無いな。。。

話が飛んだが、とにかく致命的なエラーへの対応は難しい、と改めて認識した次第である。

< Top page