unsigned long型とshort型の論理和

C言語基礎を教えてください。(;_;)

17bitの整数データを送ってくるデバイスがあります。
特殊(へんな仕様?)で先頭の符号bitをportで確認し、
残り16bitを整数の通信で送ってきます。
17bitなのでマイコンのlong型の変数に入れる必要があります。

元のソフトではportで取ったunsigned long型とshort型の論理和をしているようです。
これって大丈夫でしょうか?

試しにRL78でやってみたら、short型の先頭bitが1の時は良さそうですが、0だと負の整数にならないようです。

(1)先頭bitが"<>"の時うまく負の数になる理由がわかりません。

一応、unsigned long型とlong型を共用体にする変数を作って対応しようと思ってますが

(2)その中でshort型の変数をunsigned short型にキャストしようと思いますが、これっていいのでしょうか?

  • NAKAさん、こんにちは。NoMaYです。

    もし元のプログラムがそれでも動作していたのであれば、もう一度そもそもの仕様を確認した方法が良いと思うのです。よくあることですけれども、通信仕様書の記載が間違いで実際の動作が正しい(というのも何ですが)、かも知れません。あるいは、データ受信処理を抜けた後の次の過程で加工されている、かも知れません。

    通信仕様書を見ないまでも、以下の場合には、どういう仕様なのだろう?、という気掛かりが思い浮かびます。

    ・ 符号ビット=1、残り16ビット=0xffff --> -1 or -0xffff ?

    自分の感覚的としては、このあたりのことだけであれば共用体は使わないと思います。こんな感じにすると思うのです。(キャストは不要かも知れないけれども、自分は毎日Cコードを書いているわけでは無くて詳細を暗記していなくて、、、)

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = (int32_t)(0xffff0000 | (uint32_t)uint16_t型変数)
        とか
        int32_t型変数 = - (int32_t)uint16_t型変数
    とか
    }

     
    [追記] 2023/03/03 20:00

    この後のエビスクラウンさんのリプライで、さすがに上のコードはコメント不足ですよね、と思いましたので、以下にコメントだけ足しました。

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = (int32_t)(0xffff0000 | (uint32_t)uint16_t型変数) ← 符号ビット=1、残り16ビット=0xffff --> -1 なら
        とか
        int32_t型変数 = - (int32_t)uint16_t型変数 ← 符号ビット=1、残り16ビット=0xffff --> -0xffff なら
    }

     

  • NAKAさん、NoMaYさん、エビスクラウンです。

    横やりを入れるようで済みませんが、「良いか」の意味は、C言語仕様的に良いか、それともマイコンの仕様的に良いかのどちらでしょうか?

    もし、C言語仕様的であれば、駄目です。理由はC言語は負の内部表現を決めておらず、符号付き型に対して、表現できない値を代入したり、キャストしたら、不定となるからです。つまり、16ビット符号付き変数に0x8000を代入した時点で言語仕様上は不定、0xffff0000より大きな値を符号付き32ビットの整数型にキャストしたときも言語仕様上は不定となります。

    もっとも、マイコンはどれも2の補数で符号付き整数を扱いますから、「負の表現は2の補数に限定する」と言った条件下で考えるのであれば、「良い」と言うか、思った通りの結果となります。

    short型をunsigned short型にキャストするのも同じ考えです。また、short型やunsigned short型は演算時、整数の格上げによって、int型またはunsigned int型になることも考慮すると、本件はかなり難しいです。やはり、元の仕様における通信で得られた下位側16ビットをshort型で扱っていることに無理がありそうです。

  • エビスクラウンさん、こんにちは。NoMaYです。

    ここのところが分からなかったです。なぜ、この値が境目になるのだったけかなぁ、というところなのですけれども。

    > 0xffff0000より大きな値を符号付き32ビットの整数型にキャストしたときも言語仕様上は不定

  • NoMaYさん、エビスクラウンです。

    境目は0x7fffffffまではOK。0x80000000からNGです。0xffff0000を使ったのは、NoMaYさんが記載されていた

    int32_t型変数 = (int32_t)(0xffff0000 | (uint32_t)uint16_t型変数)

    から取りました。上記の式だと、右辺のint32_tへのキャスト前は、必ず0xffff0000より大きくなりますから!

  • エビスクラウンさん、こんにちは。NoMaYです。

    私の投稿から取った、あくまで一例、ということだったのですね。確かに、負数を作るのに拙かったかなと思い直しましたので、こうしてみました。あとそういえば、C言語規格が制定される前から存在していたコンパイラの中には、挙動が少し違うものもあったのかも知れません。バグによっては仕様と言い切れば通ってしまったこともあったかも知れません。そう意味では、NAKAさんの提示されていた移植前のコードも(もしかしたらバグのせいで)うまく動いていた可能性もあるかも知れないと思いました。

    修正案:

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = - (int32_t)(0x10000 - uint16_t型変数)
        とか
        int32_t型変数 = - (int32_t)uint16_t型変数
    }

     
    [追記] 2023/03/03 20:00

    この後のエビスクラウンさんのリプライで、さすがに上のコードはコメント不足ですよね、と思いましたので、以下にコメントだけ足しました。

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = - (int32_t)(0x10000 - uint16_t型変数) ← 符号ビット=1、残り16ビット=0xffff --> -1 なら
        とか
        int32_t型変数 = - (int32_t)uint16_t型変数 ← 符号ビット=1、残り16ビット=0xffff --> -0xffff なら
    }

     

  • こんにちは。NoMaYです。

    ところで、ちょっと脱線しますけれども、Arduino言語仕様に unsigned ってあるのでしたっけ。大昔、少し触ってみた時には無かったような気もしているのですけれども。(コンパイラはGCCなので書けば使えたのですけれども。)

  • NoMaYさん、エビスクラウンです。

    私もほぼ同じ結論に達しました。もともと符号付き整数型の負の内部表現を決めていない言語仕様で、17ビットの2の補数表現を扱うプログラムですから、かなり厄介です。ちなみにNoMaYさんの2つ目の計算式は値がおかしくなりませんか? 例えば、全ビット"1"の場合、uint16_t型の0xffffは65535ですから、それをint32_t型にキャストしても同じ65535、それの符号反転ですから、-1にはならず、-65535になりませんか?

    なので、

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = - (0x10000L - uint16_t型変数)
    }

    になるかと思います。定数値はキャストではなく、接尾子にしました。

    NAKAさん

    上記の例は、あくまでも通信で得られる下位側16ビットをuint16_t型の変数に格納することが前提となっており、最初に記載されていた16ビット符号付き型の変数ではないことをご注意ください。

  • NoMaYさん、エビスクラウンです。

    私もほぼ同じ結論に達しました。もともと負の内部表現を決めていない言語仕様で、17ビットの2の補数表現を扱うプログラムですから、ちょっと厄介です。ちなみにNoMaYさんの2つ目の式は正しい結果が得られますでしょうか(済みません、確認していません)。例えば、全ビットオール"1"の場合、下位側16ビットのuint16_t型の変数は0xFFFFの65535、それをint32_t型にキャストしても、同じ65535、それの符号反転ですから、-1にはならず、-65535では?

    なので、

    if(符号ビット==0)
    {
        int32_t型変数 = uint16_t型変数
    {
    else
    {
        int32_t型変数 = - (0x10000L - uint16_t型変数)
    }

    になるかと思います。なお、定数値はキャストではなく、接尾子にしました。

    NAKAさん

    私とNoMaYさんの例は、あくまでも通信で得られた下位側16ビットを符号なし16ビットの変数に格納することが前提です。最初に記載されていた符号付き整数ではちょっと難しいかと思います。

  • NAKAさん、エビスクラウンです。追記します。

    最初の投稿で負の数になる理由が分かりませんとの事でしたが、その理由は以下の通りです。

    まず、演算時、整数型は整数の格上げにより、short型はint型に自動的に変換されます。もっとも、RL78はshort型とint型は同じサイズのなので、そのまま0x8000、2の補数で考えれば -32768 です。

    次に演算を行う際は共通の型に揃えてから演算を行うと言う算術変換規則があり、これによりRL78の場合(int型が16ビットの場合)、unsigned long型との論理演算では、int型はunsigned long型に変換されます。ここで問題となるのは、負の値が格納されている値を符号なしの値に変換しなければならないことです。このルールはちゃんと規定されていて、その型の最大値+1から目的の値を減算した結果となります。つまり、4294967295+1-32768=4294934528、これを16進数にすると0xffff8000です。

    だから、負の値の時は上手くいくのです。

  • エビスクラウンさん、こんにちは。NoMaYです。

    指摘ありがとうございます。2つあります。まず、RL78なのでintが16ビット幅である、ことを失念していました。ありがとうございます。他方、「とか」となっているのは、幾つか前のリプライの以下と対になっているのでした。

    > 通信仕様書を見ないまでも、以下の場合には、どういう仕様なのだろう?、という気掛かりが思い浮かびます。

    > ・ 符号ビット=1、残り16ビット=0xffff --> -1 or -0xffff ?

    [追記]

    > もし元のプログラムがそれでも動作していたのであれば、もう一度そもそもの仕様を確認した方法が良いと思うのです。よくあることですけれども、通信仕様書の記載が間違いで実際の動作が正しい(というのも何ですが)、かも知れません。あるいは、データ受信処理を抜けた後の次の過程で加工されている、かも知れません。