プロが教える店舗&オフィスのセキュリティ対策術

C言語 型変換のタイミングについて教えてください。
「型変換は代入のとき行われる」と理解していたのですが、代入の前の計算時も行われるのでしょうか?

char c1=120,c2=10;
int i;
のとき、
i = c1 + c2;
は i= -126 を期待したのですが i = 130 となっていました。

質問1)これは、C言語の仕様でしょうか? それともコンパイラに任されている仕様でしょうか?

関連して、
質問2)期待した計算結果をiに代入するには、
char c3;
c3 = c1 + c2; i=c3;
とする以外に方法ありますか?

質問3)計算途中でオーバーフローする可能性のある計算を確実の行うにはどのように記述したらよいでしょう?
int x0,x1,x2,x3;
x0 = (x1 + x2 + x3) / 3;
x0 = x1 * x2 / x3; (ただしx2 < x3)

よろしくお願いします。

A 回答 (10件)

1 については C の仕様です. 計算を行うときに, int 以下の型はすべて int (または unsigned int) に変換したうえで計算されます. したがって


i = c1 + c2;
はあたかも
i = (int)c1 + (int)c2;
と書いたのと同じ計算をします.

2 は
i = (char)(c1+c2);
のように演算結果を強引にキャストすればいい, んじゃないかなと思う.

3 については.... ポータブルにやるなら努力と根性で多倍長演算を実装するしかないんじゃないかなぁ.

あ, あと余談だけど C の仕様では
char は*少なくとも*8ビット
です.
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
>1 については C の仕様です. 計算を行うときに, int 以下の型はすべて int (または unsigned int) に変換したうえで計算されます.
なるほど!!
納得です。

>char は*少なくとも*8ビット
ありがとうございます。今までずーっと勘違いして、覚えていました。

質問2,3の回答も納得です。

お礼日時:2017/04/13 17:20

「長い整数型」から「短い整数型」にキャストしたときに, 元の値が新しい型で表現できないときには


・新しい型が符号なしの型であれば範囲内に入るように適当に変形
・新しい型が符号付きの型であれば処理系定義 (処理系定義の方法で処理される, あるいは処理系が定義したシグナルが発生する)
です. なので char が 8ビット符号付きの場合
char c1=120,c2=10;
int i;
i = (char)(c1 + c2);
とやっても i が -126 になるという保証はありません (c1+c2 の結果である 130 が char に入らないため: ただし処理系が決まればどうなるかも決まる). 未定義動作であるオーバーフローよりはましですが, ポータブルではないというのも確か.

あと符号付き整数における負数の表現は
・2の補数
・1の補数
・符号+絶対値
のいずれかです.
    • good
    • 0
この回答へのお礼

・符号+絶対値
なるほど、忘れていました。
ご指摘ありがとうございます。

今質問を読み直して、読み手に誤解を与えてしまう記述と反省しているところです。

>質問2)期待した計算結果をiに代入するには、
これは、
char c1=120,c2=10;
int i;
のとき、
i = c1 + c2;
で i= -126 を期待してプログラミングをしたい、というわけではありません。
デバッグしていて、私の予想と違う挙動をした部分を抜き出して質問しただけです。

私も、処理系に依存しないプログラミングを心がけているので、 i= -126 を期待したプログラムにならないように気を付けているところです。

でも、皆さまからいろいろと深堀した回答がいただけて、感謝しています。
皆さまをベストアンサーにしたいところですが、悲しいかな1名しか選べないこと、お許しください。

お礼日時:2017/04/14 18:08

intより精度の低い整数はintに型変換される、というのはこのあたりから。


https://ja.wikipedia.org/wiki/%E6%B1%8E%E6%95%B4 …
https://msdn.microsoft.com/ja-jp/library/aetzh11 …

(2)
> char c3;
> c3 = c1 + c2; i=c3;
は、
c3 = (int)c1 + (int)c2;
となり、 = の代入時に暗黙の型変換(int→char)が適用されています。
ですから、これを明示的に型変換すれば、 中間変数c3 を使う必要はありません
i = (char)(c1 + c2) ;

(3)
> 計算途中でオーバーフローする可能性のある計算を確実の行うにはどのように記述したらよいでしょう?

には「どんな時でも使える万能な方法はありません」
速度、メモリ、許容される誤差、難易度等々を考慮して、その場その場で対応するしかありません。

主なやり方は
○精度の高い型を使う
・intが32bit,longが64bitなら、longに型変換して計算する
・doubleがIEEE754の倍精度だったら、仮数部が52(+1)ビットあるので、int32ビット入れても誤差は無い。

○多倍長整数演算用ライブラリを利用する、または自作する
https://ja.wikipedia.org/wiki/%E4%BB%BB%E6%84%8F …

○数学の性質を利用する
例えば
x1=m1*3+r1; /* r1 は x1 を3で割った余り(0,1,2) */
x2=m2*3+r2;
x3=m3*3+r3;
とすると、
x0 = (x1 + x2 + x3) / 3;
を展開すると
x0=(m1+m2+m3)+(r1+r2+r3)/2 ;
になります。
m1=x1/3, r1 = x1 % 3
を使って、置き変えれば、x1,x2,x3の式になります。
また、m1+m2+m3 は intの精度を越えません。

○何もしない
 状況によっては、そのままオーバーフローさせてしまうことも考える。
    • good
    • 0
この回答へのお礼

kmee様、前回https://oshiete.goo.ne.jp/qa/9704420.htmlに引き続き、回答ありがとうございます。

教えていただいたページの
「予期せぬ汎整数拡張」
の記事、参考になります。

>i = (char)(c1 + c2) ;
これでOKですか? i に代入される時点で(char) が無視される気がしていたので、意外です。intで計算され、計算結果をcharに変換してから、intに再変換されて i に代入されるのですね。
 計算途中ではintですが、すべてcharで計算した場合と同じ結果になる、
訳ですね。じっくり考えてみます。

>○数学の性質を利用する
これと類似した手法
 予測される平均値をあらかじめ引いておいて、残差の累計から求めた平均誤差値を最後に足す
は依然使ったことがあります。

>○何もしない
> 状況によっては、そのままオーバーフローさせてしまうことも考える。
これまた、斬新なアイデアありがとうございます。
状況によっては意外とシンプルな解決策の気がしてきました。

お礼日時:2017/04/14 09:47

余談その2.



本気で「アーキテクチャに依存しないコーディング」を突き詰めると非常にめんどくさかったりします.

例えば, char が 8ビットだとして
unsigned char uc = 130;
signed char sc = *(signed char *)&uc;
とすると sc の値がどうなるかって考えると難しい.
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
8ビットならば sc = -126 , 9ビット以上であれば sc = 130 ということですね。
いや、8ビットであっても最上位ビットが符号ビットとは限らない、
負の数の表現方法として、1の補数、2の補数表現かによってscの値はかわりますね。

「アーキテクチャに依存しないコーディング」、本当に面倒ですね。

了解しました。

お礼日時:2017/04/14 09:58

char 型の符号の解釈は、Tacosan さんの見解が正しいかもしれませんね。



私は、ポーティングエンジニアの仕事をしていた時、環境に依存しない書き方を徹底的に叩き込まれたので、曖昧な表現は避けるべきという考えを今でも持っています。タダそれだけです。

失礼しました。
    • good
    • 0
この回答へのお礼

何度も回答ありがとうございます。
感謝です。

お礼日時:2017/04/13 18:24

確かに



i=(signed char)c3;

でも Microsoft のコンパイラなら大丈夫ですね。

ビットの並びを変更せずに解釈だけ変えるときはアドレスに対してポインタをキャストするのがアーキテクチャに依存しないコーディングの基本です。それを知らずに書いていると、いざ他の環境に移植しようと思った時に誤動作の原因が掴めなくなります。(例えばバイトオーダーが違う環境など) 私はかつてポーティングの仕事をしていたので結構、こういうところ神経質になる癖があります。

他の環境を触る可能性がないのでしたら気にしないで結構です。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
>ビットの並びを変更せずに解釈だけ変えるときはアドレスに対してポインタをキャストするのがアーキテクチャに依存しないコーディングの基本です。

なるほど、勉強になります。
奥が深いですね。

お礼日時:2017/04/13 18:18

ちょっと今手元の規格を調べてみたけど, char が符号を持つかどうかは処理系定義, と読める文章しか見つからなかった. だから #4 の「unsigned char を既定としている処理系に於いて 120+10=130 ですから合ってます」は正しいんだけど, だからといってそれが任意の処理系で成り立つと判断しちゃいけないよなと思ったりする.



「ANSI の規格では 'char' は文字型を前提としているので、既定値は 'unsigned char' と解釈され、符号付きで処理したい場合には明示的に 'signed char' と表記しなければいけません。」ってのはどこにあるんでしょうか>#4.

あ, 念のため補足しておくと, #1 に書いてあるように「計算時に型が違えば、精度が高いほうの型に拡張されます」は正しい. ただ, その「精度が高いほうの型に拡張されます」の前に「int より小さい型の値は自動的に int (か unsigned int) に変換する」というステップが入ります.
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

No7さんの回答も合わせて、
 char は 最低8ビット
 unsigned char、signed char かは処理系次第
ということですね。

かなりすっきりしてきました。

>「int より小さい型の値は自動的に int (か unsigned int) に変換する」
これ、今回の質問での最大の収穫です。
ありがとうございました。

お礼日時:2017/04/13 18:23

処理系に依存する曖昧な表現は極力避けて下さい。



ANSI の規格では 'char' は文字型を前提としているので、既定値は 'unsigned char' と解釈され、符号付きで処理したい場合には明示的に 'signed char' と表記しなければいけません。

unsigned char を既定としている処理系に於いて 120+10=130 ですから合ってます。明示的にオーバーフローを期待した処理ならば、代入時に

i=*(signed char*)&c3;

と記述するべきでしょう。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

>ANSI の規格では 'char' は文字型を前提としているので、既定値は 'unsigned char' と解釈され、符号付きで処理したい場合には明示的に 'signed char' と表記しなければいけません。
そういえばそうでした!!

ということは、Visual Studio は
  ANSIの規格に従っていない
のですね。

https://msdn.microsoft.com/ja-jp/library/s3f49kt …
には、
char 1バイト 既定では -128 ~ 127
/J を使用してコンパイルするときは 0 〜 255
とあります。

>明示的にオーバーフローを期待した処理ならば、代入時に

今回の質問はデバグ中に遭遇した疑問なので、「明示的にオーバーフローを期待した処理」をしたいわけではないですが、そのような場合には、参考になります。

ところで
> i=*(signed char*)&c3;
素直に、
 i= (signed char) c3;
と書かない訳は何かあるのでしょうか?

お礼日時:2017/04/13 17:40

>計算時c1 + c2にはchar 型同士の足し算なので、型変換されずにchar型で計算され、 -126 の計算結果になると思います。


うん、ごめんなさい
そうですね、なんか私が変な勘違いしてたみたいです

そう考えると、計算の精度拡張より代入の変換のほうが優先度が高いということでしょうね
ここは色々実験して確かめたほうがいいかもしれません

なので質問3については私の知識ではちょっと分かりません
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
>そうですね、なんか私が変な勘違いしてたみたいです
ご理解いただけたようで、うれしいです。

回答、お待ちしています。

補足
C言語の仕様として定められているのは、
 char 8bit
 shortとintは少なくとも16ビット、
 longは少なくとも32ビット、
 shortはint以下で、intはlong以下、
ということだけであることは承知しています。

質問3に関しては、intが16ビット、longが32ビットの処理系であれば、
x0 = ((long)x1 + (long)x2 + (long)x3) / (long)3;
でOKなことはわかっていますが、もっとスマートな記載方法が知りたいと思っています。
int、longが共に32ビットの処理系では、平易な「解なし」かとも思っています。

お礼日時:2017/04/13 16:29

> i= -126 を期待したのですが i = 130 となっていました。


なぜその結果を期待したのかが全然分かりませんね

型変換のタイミングは代入時と計算時だったはずです
計算時に型が違えば、精度が高いほうの型に拡張されます
そして代入時に型が違えば、左辺の型に変換されたはずです
多分C言語の使用かな?ちゃんと調べてないので確かではないです

質問2)
あなたの期待結果がよく分からないので
ちょっと何も言えないです
私の知識が乏しいせいです。すみません

質問3)
型を変えてください
オーバーフローする可能性のある計算を
そのままの型で行うこと自体がおかしなことです
そのような状況に陥らないように普通はコーディングします。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
>なぜその結果を期待したのかが全然分かりませんね
そうですか、詳細を述べます。

>型変換のタイミングは代入時と計算時だったはずです
>計算時に型が違えば、精度が高いほうの型に拡張されます
計算時c1 + c2にはchar 型同士の足し算なので、型変換されずにchar型で計算され、 -126 の計算結果になると思います。

>そして代入時に型が違えば、左辺の型に変換されたはずです
char型の値 -126 が int型 に変換されて int型の値 -126 が i に代入されると思います。

質問3)
>そのような状況に陥らないように普通はコーディングします。
質問文に書いてあるように、そのような状況に陥らないようなコーディングが必要なことは理解しています。
ですので、具体的な、そのようなコーディングを教えてくださいという質問です。
よろしくお願いします。

お礼日時:2017/04/13 15:15

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!