🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1010閲覧

VS2022におけるC++のint、long intの全ビット反転の(個人的に)想定外の挙動について

L-pos

総合スコア1

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

0クリップ

投稿2024/03/30 12:34

編集2024/03/30 15:38

実現したいこと

つい数日前C言語を始めた初心者です。C言語の入門書を読みながら、0の「1の補数」を求め、bit単位でその1の数を確認することによってunsigned intないしunsignd long intのビット数を確認しようと試みました。
具体的なコードは後述しています。

追記:
タイトルには全ビット反転の想定外の挙動とありますが、そもそも認識に齟齬があり、「C言語において論理シフトと算術シフトが環境によって変化することに対する無知」、加えて「その他無理解」が原因でした。結果として全ビット反転(1の補数演算子)に関する問題ではなかったので、その旨をここに書いておきます。
似たような知識の欠落・思い込みを持つ初学者のためにはなるかもしれないので、タイトルは修正せずに残しておきます。

発生している問題・分からないこと

永遠に数字の1が出力され続けます。
inteがintないしlong intの場合

変数inteの型をunsignd short intにすると想定通りの結果が出力されます。
inteがshotの場合

該当のソースコード

VS

1#include <stdio.h> 2int count(int num) { 3 int i = 0; 4 for (; !(num == 0); num >>= 1) { 5 (num & 1U) ? printf("%d", 1) : printf("%d", 0); 6 i++; 7 } 8 return i; 9} 10 11int main(void) { 12 unsigned int inte = ~0; 13 printf("\nbit ; %d",count(inte)); 14}

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

公式ドキュメントによって(limits.hやsizeofでも)unsigned intは32bitだということが確認できましたが、上記の結果は明らかにその範囲を超えています。
公式ドキュメントの「1の補数演算子」のページも確認しましたが、原因はまだつかめていません。

追記:
コードの12行目の宣言を、
unsigned int inte = ~0;
から
unsigned int inte = ~0U;
にすることも試してみましたが、問題は解決しませんでした。

補足

VS2022のバージョンは17.9.5です。
できる限り調べてからここに投稿していますが、Pythonを独学で学んだ程度のプログラミング歴しか持っていないため、極めて初歩的なミスをしている可能性もあります。

ikedas👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2024/03/30 14:23

unsigned int inte; とちゃんと符号無しで宣言しているにも関わらず、countの仮引数でint numと符号付きで宣言してしまった原因を掘り下げておいた方が良いと思います。 ・ 仮引数もunsignedとすべきことは分かっていたが、タイプミスレベルのうっかりミス ⇒ 見直してもミスを発見できないというのは注意力不足。もしくは自分はうっかりミスなどしないという思いこみで見直しすらしていない ⇒ どちらにしても、自己認識を改める ・ 元の変数がunsignedなら仮引数がsingedでも良いと思っていた ⇒ なんらかの理解不足。何の理解不足なのか突き止めてその部分を復習。例えば、変数のスコープ、引数の受け渡し方、キャストあるいは暗黙の型変換、などなど (このケースは単に学習途中と言うだけなので理解不足があってもその認識があれば問題なし。学習を継続すれば理解できます) ・inte のほうは雰囲気でunsignedと書いたが、実は違いが分かっていない ⇒ ということはないと思うので略
L-pos

2024/03/30 15:46 編集

掘り下げについてのアドバイスまで、何から何まで本当にありがとうございます。 とても真摯に向き合ってくださっているので、こちらも本気で分析して、メモ書きのようなイメージでここに載せておこうと思います(めちゃくちゃ長文です。勝手に反省しているだけなので、読む必要はありません)。 お恥ずかしい限りですが、自分のミスはそのすべての間みたいな立ち位置で、より具体的には、 ・仮引数の宣言は、見た目が似ているpythonでいう「仮引数:型」という注釈書き程度の役割しかない類のものに、宣言した型と違うものが代入されればエラーを発生させる機能を追加したものだという先入観がうっすらとあった(数日前に一度仮引数の宣言に関する話を読んでいたので、一度は言葉で理解していたが、記憶が曖昧になっていた→だからベストアンサーの方に指摘された時に修正することだけはできた) ・上に加えて、intとunsigned short intの間の違いを、言葉では理解していても勝手に曖昧なもの?だとどこかで考えていて、「違う型の実引数を受け渡したのにもかかわらず、受け渡しの時にエラーが発生していない」+「1つ目の理由」⇒「int型の仮引数の代入の際に勝手にunsignedやshortが適用される」という謎の無意識的な認識が発生していた(「今まで使っていた他の型」を間違えて渡したときにはエラーが発生していたため。また、代入とほぼ同義であるとも思っていなかった)。 という本当に変な先入観(理解の未定着)がまず根本にあったうえで、 ・bit単位の演算子は処理が軽いものだと思っていたが、「1の補数演算子」の処理だと思われるタイミングで、なぜか毎回時間がかかっていて、次のcount関数の処理が始まるまでが遅かったので、タイトルからも分かる通り、この遅延の原因は「1の補数演算子」の用法への不理解にあるものだと先入観が働いていて、さらに「予想外の挙動をしている」→「バグの原因もこの部分にある可能性が高い」という認識に発展していたため、そもそも他の場所に目が向きづらかった(デバックの段階で特定できればよかったが、今にして思えば変数の変化の過程を追うデバックの内容が浅かった) ・そのうえで、">>"は絶対的に論理シフトの記号だと考えていたため、そもそもsignedとunsignedのどちらでもcount関数はバグを起こさないと思い込んでいた(この線は現ベストアンサーの回答を頂いた段階でなくなっていたので、ベストアンサーを頂いた後も理解できなかった原因は上記のものですべてです) という感じだと思っています。 仮引数の宣言の型以外の「謎の先入観」については、はっきりと問われれば言葉上で正しく答えることができる内容ではあったものの、チェックするときに「1の補数演算子」にばかり目がいっていたため、この最初の思い込みの強さが根本の原因だという気もします。 次からは質問する前に、原因がないと思ったものにも丁寧に目を向けてみようと思います。 本当に、重ね重ね、ありがとうございました!
otn

2024/03/30 16:13

「整数型にもいろいろある」というのはC/C++にあって、Pythonにない特徴ですね。 > ">>"は絶対的に論理シフトの記号だと考えていたため、 と思っていたのであれば、全部 int で良いはずなのに、一部にだけ unsigned を付けたということは「雰囲気で付けた」と言うことなのですかね?それとも最初は全部 int だったけど、うまく行かないので試行錯誤の途中のコードだったのか。 > 受け渡しの時にエラーが発生していない gcc だと -Wall (警告を沢山出す)や -pedantic(細かいことまで警告する)のオプションでは警告は出ませんが、-Wsign-conversion オプションを付けると警告が出ます。 num & 1U と inte = ~0; と count(inte) の3箇所。 ただ、このレベルの警告を出すと、ライブラリ関数呼び出しとかでも沢山出そうなので、-Wall の all に含まれてないのでしょう( -Wall って警告全部出すのかと最初思ってましたけどそうではない)。 あと、全く本質ではないですが、普通は、 printf("%d", (num & 1U) ? 1 : 0); とか putchar((num & 1U) ? '1' : '0'); ですね。 num & 1U の値は 0 か 1 かなので、 printf("%d", num & 1U ); でも同じですが、まあ、? : を使う方が良い気がします。
L-pos

2024/03/31 05:24 編集

補足など、ありがとうとざいます! >一部にだけ unsigned を付けたということは「雰囲気で付けた」と言うことなのですかね? signedよりもunsignedのほうが、僅かですがなんとなくbit単位の構造に関して見通しが良かったというのが理由です。流石に必要最低限(入門書に書いてある程度)のことは理解できているとは思うのですが、初めてのことだから少し不安で、よりシンプルな方を選ぼうという考えでした。 >gcc だと -Wall (警告を沢山出す)や(後略) 補足めちゃくちゃ助かります! この手の情報って、自分からピンポイントで検索しに行ったりしない限り広範囲を探索しないと見つからないので、本当にありがたいです。 >あと、全く本質ではないですが(後略) 確かに無駄に長い記述をしてますね……。初学者だとかじゃ言い訳できないレベルのひどいスクリプトでした。言われてみれば条件分岐すら必要ないですね。 個人的にはシンプルなprintf("%d", num & 1U ); のほうが綺麗に見えてしまいます(笑)。エラーが起きそうな怖さはあっても、記述を長くするタイプの条件分岐ってあるだけでちょっと嫌な気分なってしまって……。処理速度との兼ね合いだとは思うのですが、まだそのあたりの知識やバランス感覚がないので、色々自分で調べてみようと思います。 本当に色々ありがとうございます! こんなに親切に教えてくださるとは思っていませんでした。できる限り試したつもりでも、すごく初歩的なところで間違えていたので、以後は自己解決できるように精進します。
guest

回答2

0

ベストアンサー

シフト演算には、算術シフト論理シフトがあります。

算術シフトというのは、符号付き整数については符号を保ったままシフトする演算です。8ビット符号付き整数で例示するとこんな感じですね。

2 (00000010) >> 1 → 1 (00000001) -2 (11111110) >> 1 → -1 (11111111)

論理シフトというのは、符号ビットも含めてシフトする演算です。8ビット整数で例示するとこうなります。

2 (00000010) >> 1 → 1 (00000001) -2 (11111110) >> 1 → 127 (01111111)

C言語の標準規格では、符号付き整数型に対するシフト演算子 (>>など) が算術シフトなのか論理シフトなのかは規定されていません。ですから処理系によって算術シフトか論理シフトかが違う可能性があります。実際は符号付き整数型に対するシフト演算は算術シフトとされる場合が圧倒的に多いです。VC++もそうです。

ですからご質問のコードでは、int型 (正確にはsigned int型) に対して算術シフトが実行されるためいくらシフトしても符号ビットが保存されることになります。

一方、unsigned int型では常に論理シフトが使われるため、符号ビットは保存されません。

投稿2024/03/30 13:01

編集2024/03/30 13:04
ikedas

総合スコア4443

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

L-pos

2024/03/30 14:10 編集

ありがとうございます! 算術シフトだったのは想定外でした。signedとして処理されたのなら納得できます。count関数の仮引数の型指定をunsigned intにすることによって解決しました! ただ気になる点として、short intの方はなぜ上手くいったのでしょう? 再現性もあったので、少し不思議です。 別の質問にすべきかとも悩みましたが、この問題とは地続きなので、一応ここに書いておきます(回答していただかなくても大丈夫です)。
ikedas

2024/03/30 13:28

「unsignd short int」ということなので、符号付きでないため論理シフトになりますね。
L-pos

2024/03/30 13:45 編集

すみません、書き方が悪かったです。 short intの方のコードは載せていないのですが、ここに載せている間違っているコードと同じく、count関数の仮引数の宣言の型はintのままでした。 count関数の実引数である変数inteの宣言にはどちらもunsignedをつけていたので、なぜshortのほうだけがunsignedのままの受け渡しに成功したのか不思議だな、と思いまして。
ikedas

2024/03/30 13:45

ちょっと状況が飲み込めてないんですが、count()という関数では引数をint (signed int) と定義しているんで、main関数側でcount()を呼び出すときの引数がunsignedであるかどうかは関係ないと思います。そういう話ではないのかな……
otn

2024/03/30 13:56 編集

実引数から仮引数に代入が発生すると考えて下さい。 unsigned int から int への代入は同じサイズなので同じビットパターンのままです。 unsigned short int の ~0 は、サイズが2バイトだとすると、値は 65535 (= 0xFFFF)です。これはint (4バイトとします)の正の範囲で表現できる値で、intに代入されると 0x0000FFFF です。MSBが0なので算術シフトでもどんどん1が消えてきます。
L-pos

2024/03/30 14:12 編集

ありがとうございます! おっしゃっていることがわかりました! shortとそれ以外のサイズの違いがbit単位に上手く落とし込めていなかったようです。 理解力がなくて申し訳ないです。 Pythonではぬるま湯に浸っていたため、bit云々に触るのは初めてなので……(言い訳ですごめんなさい)。 ikedas様、otn様、それぞれ本当に助かりました!
guest

0

プログラムを確認したところ、永遠に1が出力され続ける原因は、unsigned intの範囲が大きいためです。

unsigned intは通常32ビットの範囲を持ち、その全ビットが1の場合、値は4,294,967,295となります。この値を右シフトしていく際に、最上位ビットが0になるまでに非常に多くのループが必要になります。

一方、unsigned short intは通常16ビットの範囲を持ち、全ビットが1の場合の値は65,535です。この値であれば、右シフトを繰り返すことで比較的短いループで最上位ビットが0になります。

問題を解決するためには、以下の方法が考えられます:

1.unsigned intの代わりにunsigned short intを使用する。
2.ループ条件を修正し、シフト回数がsizeof(unsigned int) * 8に達したら終了するようにする。

以下は2番の案のコードです。

C

1#include <stdio.h> 2 3int count(unsigned int num) { 4 int i = 0; 5 for (; i < sizeof(unsigned int) * 8; num >>= 1, i++) { 6 (num & 1U) ? printf("%d", 1) : printf("%d", 0); 7 } 8 return i; 9} 10 11int main(void) { 12 unsigned int inte = ~0; 13 printf("\nbit ; %d", count(inte)); 14}

投稿2024/03/30 12:59

quiz

総合スコア269

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2024/03/30 13:14

この回答は間違っていますので、削除依頼を出してほしいです。
otn

2024/03/30 13:27

とりあえず「質問内容に関係ない回答」とバッドを付けておきましたが、 ChatGPTの回答って、前は冒頭にその旨書いてなかったでしたっけ?
ikedas

2024/03/30 13:39 編集

https://teratail.com/help#about-ai-terms ・1行目にAIの出力である旨を書け。 ・内容が正しいか精査しろ。 ・そもそもAI回答の投稿は非推奨。 某ユーザのようにネタ回答として投稿してるのならともかく、回答の正確性を精査してないことが明らかに見て取れるようなものを投稿するのはあかんと思う。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問