マイコンのわり算事情

とある事情により、あるマイコンで実際に使用されている命令を集計していた。

そしたら乗算・除算命令が使われていなくて、そうなの?

と思ったら、乗算・除算はアセンブラで書かれたコードを呼び出していた。


乗算については32bit×32bitの演算は最大で64bit幅になる可能性があるので、

そのようなケースに対応した処理をアセンブラで書いているようだった。

一方の除算なのだが、1bitずつ除算を進めていく命令を32個並べるというコードになっていた。

除算の途中状況をステータスレジスタに残しながら進めていくらしい。


確かにコンピュータにとってわり算は面倒である。

コンピュータの演算でAND・OR・NOTのような論理演算は論理回路そのままで実現できて、

足し算は全加算器という論理回路の比較的簡単な組み合わせで作ることができる。

引き算は2の補数にして足すという処理に置き換えられるので、全加算器に追加の回路を足せば良い。

これに比べるとかけ算は複雑だが、かけ算を行う乗算器という論理回路は一般的に使われていて、

なので小規模なマイコンを除けば乗算器は積んでいるのが通常である。


一方のわり算だが、筆算のように何ステップかに分けて計算していくのが一般的である。

かけ算に比べるとはるかに複雑な演算なので一発で計算する回路は現実的に積めないと。

高速化する手法はいろいろあるが、回路規模とのトレードオフという側面もある。

それでこのマイコンは直接的にわり算を行う命令はなくて、

そのかわりビットシフトと足し引きをセットにしたような命令を用意していて、

これをビット数分繰り返すことでわり算ができる仕組みになっているよう。

具体的なアセンブラのサンプルコードがいくつか書かれていて、それを実装しているようだった。


こういう処理をコンパイラで自動生成できないのかはよくわからないけど。

ただ、わり算については何命令も積み重ねないといけないので、

そういう関数を1つ作って呼び出すという方がいいのかも。

そこまでコンパイラが自動でやってもよさそうなものだけど。


というわけで、わり算はかなり大変だというのを改めて認識したのだった。

前にRISC-Vの命令セットを見ていたときに、乗算・除算が拡張命令セットに位置づけられていて、

乗算器や除算器を積まないという選択肢を用意しているのかと思うも、

わざわざそういうものを作るのかねとは思う。世の中にはあるかもしれないけど。

うるう秒をやめる理由

以前、バイクで小金井市内を走っていると、建物の上部にデカデカと時刻が表示されている建物があって、

その前に表札で気づいたのだけど、ここが情報通信研究機構(NICT)である。

NICTは日本標準時を作っていることで有名な研究所で、そのアピールで標準時を表示していると。

だからどうしたという話なのだが、時々ここに大勢の人が集まることがある。

2017年1月1日 うるう秒の挿入 (YouTube)

うるう秒が挿入されると 8:59:60 という通常は存在しない時刻表示が発生する。

厳密な標準時を提供する研究所では、これが表示されるのである。


そんな小金井の名物? なのだが、2017年が最後になりそうである。

「うるう秒」27年に事実上廃止へ 自転とのずれ「1時間」まで容認 10月に国際会議で採決 (ITmedia)

そもそもうるう秒というのは地球の自転と1日の長さのずれを調整するものである。

1日=86400秒は原子時計でズレないが、地球の自転はわずかながら変動がある。

うるう秒を行わないと、日の出・日の入りの時刻がだんだんズレていく。

調整しないまま長い年月が経つと、日の出が午後になり、日の入りが午前になるかもしれないと脅される。


ただ、実は最近は地球の自転と1日の長さはとても近い。

現在の世界協定時(UTC)の運用が始まった1972年は1日より自転が遅く、

毎年のようにうるう秒を挿入して自転に合わせていた。

近年は自転が早くなり、2017年からうるう秒がいらない状態が10年近く続いている。

ところがこの自転が早いというところが心配事のようである。

中でも懸念されているのが、UTCから1秒を差し引く「負のうるう秒」だ。これまで実施例はなく、BIPMの決議案によると、地球の自転に関する専門家らは、35年までに必要となる確率を約30%と推定しているという。BIPMは、実施すれば対応が不十分な重要インフラで障害や混乱を招く恐れがあるとしている。

そもそも、うるう秒はコンピュータシステムにとって問題が多い。

それでも うるう秒の挿入であればこれまでの実績からできるだろうと。

でも、うるう秒の削除(UTCの23:59:59を飛ばして0:00:00にする)はこれまで経験がなく、

果たしてそれを無事に出来るのかという点に心配があると。

今後の地球の自転がどうなるかというのは予測は出来ないため、

地球の自転が早い状態が続き、0.9秒以上の差が蓄積されるかはわからない。

でも、うるう秒の削除もありうるとなって議論が活発化したようである。


60秒差まで許容するという案も言われていたが、最終的には3600秒差まで許容するという結論になったようである。

神戸では春分・秋分付近では朝6時頃に日の出・午後6時頃に日の入りになるけど(厳密には一致しないが)、

札幌では25分ほど早いし、那覇なら30分ほど遅い。

一般的にはタイムゾーンは1時間間隔で切られて、最も近いものを選ぶことが通常である。

さらに言えば一部地域ではサマータイムで1時間ずれたタイムゾーンを使うこともある。

なので1時間差までなら許容できると考えたそうだ。


もっとも1時間差が積みなさなるまでには数千年とかかる。

1時間差を許容するということは、今後はUTCを地球の自転に合わせないということにほぼ等しい。

数千年先にUTCが運用されているかどうかのほうが疑わしいわけだし。

1時間ずれを調整するために通常存在しない1時間挿入したり削除するのは現実的ではなく、

実際に1時間調整するとすればUTCとの時差を調整することになるのだろう。

現在、日本はUTC+9のタイムゾーンだが、それをUTC+8とかUTC+10にするということである。

コンピュータ上ではUTCで時刻を管理していることが多いため、その点では混乱は引き起こしにくい。


実際のところ、うるう秒に対して愚直に対応すると問題が多いので、

UTCの23:59:60を挿入する代わりに、直前2時間の1秒の長さを(7201/7200)倍に伸ばすことで対応するとか、

1秒ずれは後で時刻合わせで吸収するとかいう対応も多いよう。

元々、コンピュータの時間合わせはわずかな時間差であれば、

1秒の長さをわずかに伸び縮みさせて少しずつ吸収する方法が広く使われている。

この方法を事前にやるか、事後にやるかの違いである。

ただ、いずれの方法でも1秒単位の厳密な時刻からの乖離がしばらく生じる。

世界中で揃っているはずのUTCがシステムごとにずれると問題を引き起こしかねないと。


正直、コンピュータの都合だけで決めるのもどうかと思うんだけどね。

ただ、うるう秒を真面目に実施するのは困難なのもその通りで、

そのための代替手段が様々あるのが問題の1つでもある。

統一的な代替手段があれば、あまり不安はなかったのかもしれない。

でも、それを考えるよりは うるう秒 をやらない方が簡単と考えたわけですよね。

少なく見積もっても100年ぐらいは問題は顕在化しないだろうからね。

I2C通信とFIFOの組み合わせは難しい

以前こんな話を書いた。

I2C通信を割り込みでハンドリングする

このマイコンではI2C通信を割り込みでハンドリングする場合、

1バイト単位で割り込みが入り操作するのが必須だった。

本来、通信ペリフェラルではFIFOがあるが、I2Cでは使わせないと。

それとは別のマイコンでI2C通信を触っていた。

といっても既存のコードのデバッグだったのだけど。


このマイコンはI2CでもFIFOを使ってハンドリングできるのだが、

レジスタの説明を見てもちんぷんかんぷんで困ってしまった。

前に取り上げたときに使っていたマイコンは1バイト単位で現在の状態が渡され、次の状態を選択するというような使い方で、

こういう形でしか使わせてくれないが、その分シンプルではある。

このマイコンでは送信側で相手がACKを返し続ける限り、

あるいは受信側で自分がACKを返し続ける限りはFIFOを使って自動で処理してくれる。

そういうことをやって欲しいんだよということを実現している点ではよいのですが。


ただ、FIFOとの組み合わせはいろいろ複雑なようで、

相手側がACKを返さない場合の割り込みが来ないので変だなと調べていると、

レジスタ設定の組み合わせがよくなかったようである。

というわけで謎は解けてきたのだが、まだちょっと謎は残っている。

なんかいろいろ間違えて使ってそうな気がしましたね。

I2Cを1byteずつ送受信するだけでも複雑なのに、FIFOと組み合わせるとレジスタの組み合わせがさらに増えて複雑化していると。


どうしてこういう話を書いたのかという話なのだけど、

I2Cでは通信相手が応答しないことを検知できるということですね。

基本的にはアドレスを送ってACKの応答がないということで検出できる。

I2Cの仕様としては当たり前なのだけど、SPIだとそうじゃないんですよね。

SPI接続のデバイスにチップセレクト信号を入れて、

コマンドを送って、クロックを送ると結果が返ってくると信じているが、

デバイスが応答しているかしていないか知る手段がない。

実際には受信するデータが FF FF…のようにとなり、不当なデータであると検出できる場合もあるが、

それが正当なデータの可能性もあればそうもいかない。


というわけで相手が応答しているか確かめながら通信できるI2Cは優れているように思った。

ただ、全体的に通信制御の方式が複雑なんですよね。

今回デバッグしていて改めて複雑だなぁと思いましたね。


で、なんで既存のコードをデバッグしているのか?

という話だが、これは何らかの改善ということで1つ。まぁバグですよね。

ただ、実際には問題を引き起こす可能性は低いと思いますけどね。

strcpy_sとかmemcpy_s

職場で勤務時間のいくらかは学習時間に充てろという話があって、

社内の研修プログラムの受講とかで時間を費やすなら話は簡単だが、

そういうわけでもないなら何かネタをみつけてやらないといけない。

それで最近はセキュアコーディングというところでC言語の勉強をしている。

ソースコードの解析でこういうのは脆弱性の温床だという指摘はいろいろ見てきたが、

背景や打開策などわかっていない部分もあったので真面目に勉強するかと。


よく知られた話なのだがC標準ライブラリのstrcpy関数はけっこうこわい関数である。

C言語では文字列は文字の配列をNULL文字で終端することで表すのが通常で、

strcpyはNULL文字を検出するまで文字を右から左にコピーする関数である。

この関数が怖いのはNULL文字が出てくるまで際限なく転送してしまうことで、

転送先の配列のサイズなんて考えないわけである。

この結果、想定外の入力が与えられるとメモリ上の他のデータを破壊してしまうことがある。

それが脆弱性の原因となることがしばしばあるのである。


とはいえ、こういう文字列関係の関数は仕事ではほぼ使わないですね。

なのであまり興味はなかったのだが、この問題を緩和するための関数が導入されていたらしい。

C11(ISO/IEC 9899:2011)のAnnex Kで規定された”Bounds-checking interfaces”である。

strcpyに代表される危険な関数に境界チェック機能を追加したものを規定していて、

strcpyに対して strcpy_s のように “_s” を付けた関数名を使う。

NULL文字での終端を前提とした文字列関係の関数は基本的にあるわけだが、

memcpy_s というものも存在するらしい。

memcpyは指定されたサイズのデータを右から左にコピーする関数である。

strcpyと違って指定されたサイズを転送するので、あまりリスクはないと思っていた。

でも境界チェック機能付きのものがあるんですね。


そもそもこの境界チェック付きの関数とはどういうものか、strcpy_sの使用例を見てみる。

char buf[128];
errno_t err;

strcpy(buf, msg); err = strcpy_s(buf, sizeof(buf), msg); if(err != 0){ /* エラー処理 */ }

strcpyとstrcpy_sの使用上の差は2つある。

1つは転送先(上記では”buf”)とともに転送先のサイズ(上記では”sizeof(buf)”)を引数加えること。

もう1つは結果としてエラーコードが返ってくること。

従来のstrcpyは失敗することは基本的に想定しなくてよかったのだが、

境界チェックでひっかかると転送失敗ということになる。

その成否を表す値が返り値として渡されるわけである。

それをエラー処理につなげる必要があるかは設計によるが。


memcpy_sも同じような考えで転送先のサイズを渡すわけである。

memcpy(buf, rcv, len);
err = memcpy_s(buf, sizeof(buf), rcv, len);
if(err != 0){ /* エラー処理 */ }

単純に転送先のサイズより転送サイズが大きければエラーにするだけである。

この転送サイズを示すlenというのは、例えばデータ構造のヘッダに格納されているデータだと、

もしかすると想定外の値が入っていて転送先を破壊する危険がある。

本来はそういうことを意識してチェックする必要がある。

通常のmemcpyでもこういう書き方をすれば、上記のmemcpy_sはほぼ同じであろう。

if(len > sizeof(buf)){
   //エラー処理 }else{
   memcpy(buf, rcv, len); }

ただ、こういうことを個別に考えずとも徹底的に点検できるようにmemcpy_s というのがあるのだろう。


ところでこの”Bounds-checking interfaces”だが、C11ではオプション機能と位置づけられている。

必ずしも対応しているわけではなく、対応しているコンパイラでも

__STDC_WANT_LIB_EXT1__という定数を1にセットしなければならないという。

どうもこれはMicrosoftの提案でC11の規格書に掲載された経緯があるらしい。

STR07-C. 境界チェックインタフェースを使用し、文字列操作を行う既存のコードの脅威を緩和する (JPCERT)

そういえばVisual Studioで_s付きの関数を使えという警告が出てきたことがあったっけ。

元々はMicrosoft独自拡張だったが、規格書に掲載されて一応は標準規格にはなったが、

この拡張について懐疑的に思っているベンダーもあるらしい。


とはいえ、この関数は単なる境界チェックだけでもないんですね。

strcpy_s で転送先のサイズを超過する場合、転送先にはNULL文字を1文字だけ書き込む。

すなわち、転送に失敗した場合は転送先を空文字にするわけですね。

strcpyがNULL文字が出現するまで際限なく転送するという問題に対して、

昔からある標準ライブラリ関数のstrncpyを使うことで最大の転送数が決められるのだが、

これは最大の転送数に達するとNULL終端せずに終わってしまうので、後の処理で問題を引き起こす危険がある。

そういう問題を引き起こしにくいので使いやすいのではないか。


正直、NULL文字で終端されたchar型の配列というのは、

文字列を格納する手段としてはあまり使いやすいものでもないような気はする。

そういう環境で文字列を扱うことの是非は当然ある。

ただ、それが必要な場合にstrcpy_sのような関数を使うのは効果的に思う。

一方でmemcpyは外側でしっかり対策する方が効くのではないかなと思う。

でも徹底的にチェックするという意味でmemcpy_sを推奨したいという考えを持っている人もいるということだろう。


じゃあ、どうやると強固なプログラムを作れるのか?

それはプログラムの性質によるところもあるでしょう。

こうするのが安全と単純に言える話でもないのかなと思った。

バイトオーダーを選んでunpack

簡単なデータ変換を行うスクリプトをRubyで書いていた。

なんでRubyかというと、他の目的でRubyを使っている環境だったから。

処理を書いている中で、そういえばエンディアンを指定して整数とバイト列を往来するにはどうすればよいのだろうと。


Rubyではバイナリデータも文字列として扱うようですね。

Rubyでは文字列にエンコーディング情報が付加されているのだが、

ASCII-8BITってのがバイナリデータを扱うエンコーディングだと。

文字列を数字の配列に変換するためにはunpackメソッドを使うそう。

それでサンプルコードを見るとこんなことが書かれていた。

int_le = bytedata.byteslice(idx,4).unpack("L<")[0]
int_be = bytedata.byteslice(idx,4).unpack("L>")[0]

そもそもpack, unpackというのが慣れないところはあるが、

“<“という記号は一体何を表しているのかと気になった。


pack, unpackってのはPerlに由来しているのかな?

$text = pack("C*",@byte);
@byte = unpack("C*",$text);

のような感じで文字列$textと1byte単位の配列@byteを変換するのが典型的な使い方。

それぞれ引数の”C”がunsigned charに対応するというわけである。

ここには2byte以上の型もあり、32bit符号無し整数に対応するのが”L”である。

ただ、この”L”はバイトオーダーが環境依存である。(多くの場合はリトルエンディアンと考えられているが)

そこで “<“を付加するとリトルエンディアン、”>”を付加するとビックエンディアンと指定できる仕組みがあると。


こういうのってPerlにもあるのかと調べたらあるらしい。

もっともPerlでもRubyでもそうなのだが、ビックエンディアンの32bit符号無し整数は “N” でも表現できるそう。

なぜ “N”なのかというとネットワークバイトオーダーという意味らしい。

こちらの方がPerlでは一般的な書き方のようである。

逆にリトルエンディアンの32bit整数は “V” と書くそう。


このあたりは環境によりいろいろあるという話だが。

ただ、ちょっと前に.NET Frameworkのこの手の処理の実装例を見て驚いたんだけど……

BitConverter Class (Microsoft Learn)

この中でビッグエンディアンに対応させるためにはどうするかという話で、

IsLittleEndianがtrueならば、Array.Reverseでバイト列の順番を反転する処理を入れるとなっている。

果たして.NET Frameworkが動く環境でビックエンディアンがあるのかは疑問なのだが、

これを見て、実行環境のバイトオーダーと不一致ならばバイト順を反転させることで対応してくれということらしい。

もうちょっとなんとかならんのかと思うが。

充電しながら音楽が聴けるケーブルの謎

最近はコンセントの付いた列車も多いけど、

USB Type-Cでイヤホンを接続していると、同時には充電できない。

ちょっと不便だなと思っていたので、メルカリでこんなのを買った。

ハイレゾ対応 給電付き USB Type-C変換ケーブル(高耐久モデル) MPA-C35CSDPDBK (ELECOM)

新品で買うとけっこうするのだが、買って不要になった人が安くで出品していた。

これと一般的なアナログ接続のイヤホンも中古で安く買って組み合わせれば完成。


これとUSB Power Delivery対応のACアダプタと組み合わせると、

急速充電しながら、音声を出すことができていた。

というわけでとりあえず目的は達成出来そうである。

ただ、ふと気になったのである。どうやってUSB Type-Cからの音声出力とPDのネゴシエーションを両立しているのだろうと。


最近のスマートフォン・タブレットPCでは3.5mmミニジャックが付かないことが増え、

有線での音声出力手段がUSB Type-Cコネクタのみということも増えた。

このような機器の場合Type-Cのコネクタからアナログ音声出力ができる「Audio Adapter Accessory Mode」に対応しているのが普通で、

この場合はコネクタ形状の変換だけで3.5mmミニジャックを出すことができる。

規格上はこの方式はオーディオジャックへの変換のみが認められ、

ヘッドホンから直接Type-Cのコネクタを出す場合は適用できないそう。

すなわちDAC内蔵でなければType-C接続のヘッドホンは作れないということである。

本当にこのルールが守られているのかはよくわからないけど。


で、これとUSB PDは共存できるのかという話だが、できなさそうな気もするけど、できるという文献もあってよくわからない。

ただ、今回購入したケーブルはAudio Adapter Accessory Modeではなかった。

商品説明をよく読んでみると「DAC搭載」という記載がある。

ノートPCに接続してみるとUSB Audioとして認識して音を出せた。

というわけでUSB 2.0のオーディオデバイスと、USB PDの共存ということのようである。


ここでふと気になったのだが、USB PD対応のACアダプタとの組み合わせであれば、

給電能力を通信でACアダプタからスマートフォンに伝達できそうだが、

昔ながらのUSB BC(5V×1.5A)の場合は給電能力の伝達ができないのでは? と思った。

USB BCというのはUSB 1.1/2.0の通信ラインD+,D-をショートすることで1.5Aまで供給できることを示す仕組みである。

本来USB 1.1/2.0では500mAまでしか電流を引くことができない。

これでは充電に遅すぎるので1.5Aに拡張したのがUSB BCで長い歴史を持つ。

(Appleはこれを2.4Aに拡張したり、いろいろバリエーションはあるようだけど)

でも、このケーブルではD+/D-はUSBオーディオに接続されているはず。


というわけで、Type-A~Type-Cのケーブルを使って、充電器と変換器を繋ぐと、

なんとスマートフォンの表示は「急速充電中」になった。

これは予想外だった。5V×500mAの範囲で充電する「低速充電中」になると思ったから。

果たしてこの急速充電がどのようなプロファイルでの充電を指しているのかはよくわからないけど、

5V×1.5Aより大電力じゃないと急速充電を名乗るには値しないと思うが?

でも、そのためには接続されている充電器の能力をUSB PD以外の方法で把握しないといけないと思うが。


どうにもよくわからないので、調べていると、このケーブルにはUSB PDのコントローラも入っているのではと。

tomorrow56 (ThousanDIY)  / 【100均ガジェット分解】(69)オーディオDACを内蔵して「充電しながら使えるUSB Type-C変換ケーブル」 (note)

USB Type-Cではデバイス→ホストという向きに電力を送ることもできる。

この双方向給電の管理はUSB PDにより実現されているそう。

この変換ケーブルは双方向給電が必須である。

DACを駆動するには電力が必要で、充電器がないときはスマートフォンから供給する必要があるが、

充電器があるときは充電器からスマートフォンとDACに電力供給が行われるためである。

世の中には2つの機器と通信ができるUSB PDコントローラというのがあるようで、

このコントローラが充電器・スマートフォン双方とUSB PDのネゴシエーションを行っているのではないかということである。


そう言われると合点が行く部分もあって、それがこの仕様である。

USB Power Delivery対応 ○(最大27W対応)

27Wというのは9V×3Aのことだと思う。

USB Type-Cのケーブルで最大60W対応というのをよく見るが、20V×3Aまで対応という意味である。

3A超の電流を流す場合、安全面からケーブルをeMarkerで識別しなければならない。

裏返せば20V×3AまではType-Cのケーブルは通る可能性があるということである。

でも、このケーブルは単に電力を右から左に送るだけではなく、DACも搭載されている。

おそらくその都合、電圧も上限を決める必要があったのではないか。

単なるケーブルであれば制御は出来ないが、USB PDのコントローラがあれば、

充電器側が9V以上に対応していても、これを切り捨てるという対応ができる。


USB PD非対応の充電器を正しく認識できているのかはよくわからないが、

正しく認識した上でスマートフォンに見せていれば一応問題ないことになる。

でも、実際そういう挙動になっているかはよくわからない。

充電器の給電能力以上に電流を引いてしまうとトラブルの元なので、

ここは正しくコントロールできてないと危ないのだけど……


というわけで思ったよりは複雑な変換ケーブルだった。

ここまで賢いなら何でも出来ますよねという感じでしょうかね。

continue文に振り回されてた

これが終わったら、これをやる……というのがずっと続いてきた仕事、

やっと終わりが見えてきた感がある。

1年ちょっと前からずっとこんな状況だった覚えがある。

まだプロジェクト全体としてはやるべきこともいろいろあるけど。


このプロジェクトの計画段階の甘さにはいろいろ思うところがある。

その中でメンテナンス用のソフトウェアを作る必要があった。

社内で作れる人を新規に割り当てるのはおおよそ困難な状況、

自分はマイコンをどうにかするのが優先である。

そんな中で外注に出して、一定の部分は業者のアイデアも借りながら作り上げた。

ただ、正直なところ、担当者の理解度が悪くて、こういうパターンではうまく動かないのだがと、

そんなことが開発期間中ずっと繰り返されていたのが実情である。


それでも普通に使う分にはあまり問題ないものはできた。

ちょっと気になる動きはあるけど、メンテナンス用のツールだし……

と、いろいろ使う中で、こういう使い方をするとエラーが出る仕様だよなと確認したら、

なぜかエラーが出ずに動いてしまうということが何度かあった。

とはいえ、とりあえず使う分には困らないので後回しだとやっていたが、

他の作業も一段落したのでバグを直していくかと作業をしていた。

とっくに検収は終わっているし、小修正だし自分でやりますかと。


ソースコード見ると、イマイチな作り方だなと思うところはけっこうあったのだが、

そんな中で考慮が足らんと思ったのが continue文のこんな使い方だった。

for(....){
   if(....){
     ....; continue; //A
   }
   if(....){
     ....; continue; //B
   }
   if(....){
     .... //C
   }else{
     .... //D
   }
   if(cnt >= CNT_MAX){
     .... //E
   } }

continue文というのはループの終わりまでジャンプする文である。

このfor文はデータを順次処理していくのだが、カウント数が一定に達すると出力処理(上記の処理E)を行う。

処理Aをしたら、以後のif文の判定もなく終わりまでジャンプ、

処理Bをしたら、以後のif文の判定もなく終わりまでジャンプという形である。

このように後ろの処理をスキップすることで効率的な処理にしたつもりだったのだが、

条件によっては処理Aの時点で処理Eを行うべき条件を満たすことがあり、

さらに他の条件が重なると、エラーも無く処理が終わってしまうが、生成されるデータは壊れていると。


なんでこれにcontinue文を使ったのかは謎で、

if(....){
   .... //A
}else if(....){
   .... //B
}else if(....){
   .... //C
}else{
   .... //D
}
if(cnt >= CNT_MAX){
   .... //E
}

とelse ifを連ねていけば無駄なく記述できるためである。

他にも考慮が漏れてるパターンがあり、正しく処理Eに分岐するように修正したら直った。


というわけであまりこれはcontinue文を使う意味はわからなかったのだが、

こういう処理を書いているとcontinue文を使った方がよいかと悩むことも。

for(....){
  ...
  if(!error){
    ....
  }else{
    ....
    if(!error){
      ....
    }else{
      ....
    }
  } }

でもこんな風にif-elseのネストを深くする方を選ぶかなぁ。

これはこれで読みにくいことも事実だが、さっきの処理Eみたいに後ろに必須の処理がある場合も多いので。


break文はないとうまく記述できないことも多いが、

continue文はなくてもあまり困らないんじゃないかなと思う。

コーディングルールで使用を禁止しているところも多いんじゃないか。

できるだけ手を加える範囲を小さくしながら問題を解決していくのに苦心したが、

全くダメなフローでもなかったので、ちょっと手を加えれば使えた。

これでとりあえず発見した問題は解決したはず。

intの幅が32bitじゃないと怖いから?

定時内の移動中は仕事にちょっと関係する勉強でもするか、

ということでセキュアコーディングの勉強をしていた。

その中で、前々からなんで静的解析で指摘されるのだろうと思っていた部分の真の意味がわかった。

が、それは本当に心配するべきことなのだろうか? と思った。


(1u<<31)のような1をビットシフトしてデータを作る記述は、

特にマイコンのGPIO操作などではあまりによく見る記述である。

これは結果としては 0x80000000u と同じことを指すものの、

31bit目を1にするという明確な意図が伝わりやすいし、

(1u<<FOO_CH)のようにビット番号を他の定数を使って表したりもする。

あまりに当たり前すぎる表現だが、これがいちいちケチが付くのである。


これが意図した結果が得られることは机上検討で明らかなので、それでよいことにしているが、

果たしてこの記述にどのようなリスクがあるのだろうか?

答えはこの記述はint型のビット幅に依存するということのようだった。

1u<<31 が 0x80000000u と同じにならない場合があると。


1uというのはunsigned int型の1を指す。

さらに言えばC言語で整数演算するときはint型あるいはunsigned int型に揃える汎整数拡張というのがある。

この汎整数拡張というのもくせ者で、特にビット反転では思いもしない結果を生じさせかねないもので、

そういう問題を未然に防ぐためのコーディングというのもあるのだけど。

ただ、1u<<31 を32bit幅のレジスタなどに代入する分には何も直感に反する動きはないように思う。


しかしここには前提条件が合ってint型が32bit幅であることである。

いろいろデータ型モデルはあるが、今どきはほぼintは32bit幅である。

しかし16bitのマイコンなどではintが16bitというものも存在する。

この場合 1u<<31 は16bit幅のunsigned int型から押し出されて0になってしまう。

これにより 1u<<31 の結果は直感に反するものになりますよということである。


なるほど。確かに16bitのマイコンってのもあるから移植性の問題はあるかもしれない。

でも、そのときにこのコードをそのまま使うのは現実的ではない気もするが。

1uという整数リテラルは16bit幅、32bit幅、めったにないが64bit幅のintいずれでも表現できる。

一方で0x80000000uという整数リテラルは32bit以上の幅でなければunsigned intで表現できない。

このような場合はlongやlong longを適用することになる。

なのでintが16bitでlongが32bitの場合は 0x80000000u はunsigned long型になるはず。

この観点では 1u<<31 より 0x80000000u の方が移植性がよさそうである。


しかし、コンパイラによってintの幅が違うって不便じゃない。

というのは当然あって、それで stdint.h にuint32_t などビット幅固定の型が用意されている。

そして実はここには整数リテラル用のマクロってのもある。

UINT32_C(1) のように書くと最低32bitの幅のある符号無し型の整数リテラルを生成できる。

intが32bit幅の場合は UINT32_C(1)は 1u、intが16bit幅・longが32bit幅の場合は 1UL となる。

これを使って UINT32_C(1)<<31 って書けば文句ないだろ?

と静的解析にかけたのだがマクロ展開後の 1u<<31 で解析されたのか何も変わらなかった。


静的解析ツールが問題ないと判断できる書き方は ((uint32_t)1)<<31

のような表記になる。

でもこんなのをビットシフトで数値を作る度に書いていては括弧だらけで読みにくくなってしまう。

で、なんでこれが問題なのかというところにたちもどって考えて見ると、

int型が32bitより狭い環境に移植する場合に問題となると。

じゃあint型は32bitであると規定できれば問題にならないのでは? と。


16bitのマイコンのことはあるが、32bitも64bitもintは32bitであると考えてよい。

long型のサイズの方がアテにならない

32bit環境ではほぼ ILP32 で、int, long, ポインタ型がいずれも32bit幅である。

64bit環境では LP64 と LLP64 の2パターンがほとんどである。

LP64は intが32bit、longとポインタ型が64bitである。

LLP64は int, longは32bit、ポインタ型とlong long intが64bit型である。

UNIX系ではポインタ型をlongに代入する使い方が多くみられ、

その用途ではLP64が移植性がよいという理由で主流になっているよう。

とはいえ、いずれもint型は32bit幅である。

汎整数拡張の都合、int型の幅が変わる影響が大きいので、そこは回避したということなんだろう。


ちなみに16bit→32bitの移植ではintの幅は変わるが、longの幅はいずれも32bitなので、

この部分だけ見るとlongの方が移植性がよいように思えた。

この経緯から32bitを表すためにlongを使っているコードも多いんですよね。

この観点ではLP64は余計なお世話なのだが……

こういう歴史も考えれば、将来どうかというのを予測するのは難しいが、

int型が32bitより狭くなるということは考えないという決断はあってもいいように思う。


他にも想定しているリスクに見合わないような指摘もありますけどね。

ただ、想定しているリスクが何なのかは正しく勉強しておかないといけないと思ったのだった。

未定義の挙動と処理系定義の挙動

先日、このBlogを書くための調べごとの中で気づいたことがあって、

それはC言語で符号付き整数の演算でオーバーフロー時の挙動は未定義ということである。

えっ、そうだったの? それだと仕事で作ってるシステムで問題じゃないかと。


未定義というのは、コンパイラやプロセッサの都合のいいように決めてよいということである。

int16_tの場合、通常は最大値は32767、最小値は-32768である。

int16_t型の変数xが32767のとき、x++;がどうなるかは不明である。

この処理単独ならば、ほぼ間違えなく-32768になるのですが。

ただ、他の処理と合わせた最適化の中でどうなるかはわからない。


一方で符号無し整数型のオーバーフロー時の挙動は明確である。

uint16_tの場合、通常は0~65535の値を表すことができるが、

この範囲を超える場合は65536で割った余りとなると規定されている。

すなわちuint16_t型の変数yが65535のとき、y++;をすると0になる。

こういう動作をラップアラウンドって言ってますね。

ちなみにこの考え方は負の数を代入するときにも適用され、

-64をuint16_t型にキャストすると -64+65536=65472 となる。

これは負の数は2の補数で表現されると言っているのと同じである。


逆に符号なし整数を符号付き整数に変換する場合の挙動だが、

符号付き整数型の範囲外の値をどうするかは、処理系定義となっている。

処理系定義ってのはコンパイラやプロセッサの都合のいいように決めて良いということだが、

さっきも未定義の挙動と違って、マニュアルで定義されているということである。

とはいえ、ほとんどの場合はビットパターンをそのまま転記する。

そして最上位ビットが1の場合は2の補数で表された負の数として扱われる。

符号無しの 65472u をint16_t型に変換すると -64 となるわけである。


なので冒頭に「int16_t型の変数xが32767のとき、x++;がどうなるかは不明」と書いたが、

x = (int16_t)( ((uint16_t)x) + 1u); だと x=32767のとき-32768、x=-1のとき0となるとほぼ断言できる。

符号無しで 32767+1=32768 と演算して、これをint16_t型に変換すると、

典型的な処理系定義の動作ではこれを2の補数で表された負の数と解釈し、-32768 となる。

負の数をuint16_tに変換するというのは65536を加えた値にすることで、

int16_t型の-1はuint16_tにキャストすると 65565となる。これに1加えるとラップアラウンドして0となる。

0は符号付き型に変換しても当然0なので、これは処理系定義の動作も関係ない。


負の数がオーバーフローしたときの動作はわからないのに、

符号付き・符号無しとも範囲外の値を扱う場合のルールは決まるんですね。

ちょっと変な気もするのだが、ここにはいろいろ事情もあるよう。

int32_t型の変数x,y について (x-y)>10 という条件判定を行うことを考える。

ここで x=-2147483630, y=2147483640 の場合、

単純な数学的な計算では x-y=-4294967270 となり 10より小さい。

しかし、-4294967270 はint32_t型の範囲を超えてしまう。

2の補数形式で表して32bitに入らない部分は切り捨てると 26 となるので、

もしそのようにして計算した結果で大小比較を行えば10より大きいということになる。


果たしてどちらをお望みかというのは一概には言えない話だが、

符号付き整数型のように最大値に1加えると最小値に戻る、

というような考え方を適用すると後者の挙動となる。

32bitのプロセッサだと、結局は32bitにして大小比較しないといけない。

なので、ほぼ後者の挙動しか考えられないところである。

ただ、64bitのプロセッサの場合、64bitで演算して大小比較するという方法もとれる。

この場合は前者のような挙動になることも考えられる。コンパイラ次第である。

(int32_t)( ((uint32_t)x) – ((uint32_t)y) )>10 と書けば、確実に後者の挙動となる。

x=-2147483630は uint32_t型では 2147483666 となり、

2147483666-2147483640=26 なので 10より大となる。


なので、まさにこういう書換をしたんですよね。

そしたら、実行コードとしては何も変わらないものが生成された。

最適化レベルにも依存するのかもしれないが、32bitのプロセッサではほぼこういう動きしかしないのだろう。

というわけで思った挙動をするコードは元々生成できていたことが確認出来たと。

今後、コンパイラの設定変更や移植があっても同等の処理になることは保証できると。

(処理系定義の動作があるので、そこに差分がないのが前提だが)


手元にあった64bitプロセッサのコンパイラでも同じことをやってみたが、

こちらは一例だけではあるが、そちらも同じコードが生成できていた。

これこそ最適化レベルや前後のコード次第の面もあるとは思うが。

なのでこれが問題になるケースって本当にあるのかな? と思ってしまうが、

forループのループ変数で発生した場合とか、実際に問題になるケースはいろいろあるらしい。

今回のケースでは特に問題はないということが確認出来たということである。

休暇の間にタイマの評価

明日から徳島に向けてお出かけ。帰ってくるのは月曜日である。

金曜・月曜と休暇を取るわけだが、ここで気づいたのである。

それは長時間かかる評価をやるのに絶好の機会じゃないかと。


長時間かかる評価というのはタイマである。

最初、タイマの最大時間は3600秒、すなわち1時間にしようと考えていたのだが、

それでは足りないという話になった。そんな気はしてたが。

そこで別のシステムを参考にして 86400秒、すなわち24時間を上限とすることになった。

ということは少なくとも24時間は動かさないと検証できないわけである。


24時間ってなら普通の週末に動かしてても達しますが。

なので、それでもいいかなと思ったんだけど。

より長い期間をかけたほうがいい評価であることは確かである。

というわけで今日からやるかと思ったのだが、電源をつけっぱなしで去るには配線がとっちらかっていたので、

配線をきれいに片付けて、安全点検をしてと、けっこう手間はかかった。


あとはフリーランタイマーのオーバーフローというのもありますが。

これはスタートの値を変更することで、すぐにオーバーフローが起こるようにして影響がないか確認している。

ただ、フリーランタイマーを使用する動作は様々あって、

しかも、そのときに動作がおかしくなってもバレないものもあるよなと。

フリーランタイマーを使う処理は基本的にどれも同じ書き方をしているから、

代表パターンで影響がないのを見れば大丈夫とは言えそうだけど。


ただ、この休暇中に動かしてもオーバーフローしないものがあるなと。

最初はオーバーフローすると思っていたのだが、ちょっと考え方が間違えていた。

というか書いているときにちょっとマズいことに気づいてしまった。

その内容次第ではそもそも86400秒のタイマーの試験もやり直しかもしれない。


少なくとも途中で停止せずに完走してほしいものだが。

試験対象の機器は多分大丈夫で、心配なのは測定用のPCですね。