
C言語基礎|論理シフトと算術シフト
同じ右シフトでも結果が変わる!?論理シフトと算術シフトで“負数のビットの動き”をスッキリ整理しよう。
論理シフトと算術シフトは「負数の右シフト」を理解する鍵
シフト演算(<< や >>)は、ビット列を左や右にずらす操作でしたね。
でも、負の整数を右にずらすときは話が少しややこしくなります。
理由はカンタンで、負数は最上位ビット(符号ビット)が 1 になっていることが多く、右にずらしたときに
- 左側の空いたビットを 0 で埋めるのか
- それとも 1(符号ビット)で埋めるのか
で、結果がまったく変わってしまうからです。
この「埋め方」の代表が、論理シフトと算術シフトです。
C言語では、符号付き負数の右シフトは処理系依存になりやすいので、ここをちゃんと理解しておくと安全なコードが書けるようになります。

まずは用語の整理:論理シフトと算術シフト
提示文の内容を、学習用に表でまとめます。
論理シフトと算術シフトの違い(右シフトのイメージ)
| 名前 | 何を基準にシフトする? | 空いた上位ビットの埋め方 | 負数を右シフトした結果 |
|---|---|---|---|
| 論理シフト(logical shift) | 符号ビットも含めて全部をそのまま | 0 で埋める | 0 または正の値になりやすい |
| 算術シフト(arithmetic shift) | 符号を保つことを優先 | 元の符号ビット(0 or 1)で埋める | 符号が変わりにくい(負のまま) |
この表の説明
- 論理シフトは「ビット列としての右シフト」です。符号なんて知らないよ、という感じ。
- 算術シフトは「符号付き整数としての右シフト」です。符号を維持しやすい動きになります。
- どちらが使われるかは処理系や型(signed/unsigned)で変わるので、C言語では注意が必要です。
負の値を右に1ビットずらしたときの違い
図:負の整数の右シフト(論理シフト vs 算術シフト)
元(負の値の例): 1 xxx xxxx xxxx xxxx (先頭の1が符号ビット)
a) 論理シフト(右へ1)
0 1xx xxxx xxxx xxxx ← 左端は0で埋める
→ 符号ビットが0になるので、0または正になりやすい
b) 算術シフト(右へ1)
1 1xx xxxx xxxx xxxx ← 左端は元の符号ビット(1)で埋める
→ 負のままになりやすい
この図の説明
- “右シフト”の共通部分は「全体が右へずれる」ことです。
- 違いは、左端(上位側)の空きを何で埋めるかだけ。
- ところがその 1ビットが、符号(正負)を左右するので結果が激変します。
算術シフトは「だいたい 1/2」っぽく見える理由
算術シフトは、符号ビットを維持する方向に動くので、負数でも「割り算っぽい」動きに見えます。
表:算術右シフトが割り算っぽい理由(イメージ)
| 操作 | 直感 | 注意 |
|---|---|---|
| x >> 1(算術シフト) | 値がおおむね 1/2 になる | 端数の丸めが絡む(負数は特に) |
| x << 1 | 値がおおむね 2倍になる | オーバーフローに注意 |
この表の説明
2進数は 1ビット動くと重みが変わるので、シフトが倍率変化に見えます。
ただし負数のときは、丸め方向(切り捨て/切り上げ相当)の差で、単純に割り算と一致しない場面が出ます。
C言語での超重要ポイント:unsigned なら論理シフトの動きになりやすい
C言語では、一般的に
- unsigned の右シフトは、上位が 0 で埋まる(論理シフトのイメージ)
- signed の右シフト(特に負数)は処理系依存になりやすい
と考えると安全です。
安全な方針
| やりたいこと | おすすめ | 理由 |
|---|---|---|
| ビット列として扱いたい | unsigned を使う | 符号のややこしさを避けられる |
| 負数を右にずらしたい | できるだけ避ける | 結果が処理系依存になりやすい |
| どうしても signed の挙動確認が必要 | 実験して確認する | 環境差が出るから |
この表の説明
教材としては、まず unsigned 中心でビット操作に慣れるのが一番スムーズです。
「負数のシフトは罠がある」ことだけ先に知っておけばOKです。
登場する命令(演算子)の書式と「何をする命令か」
ここで扱う命令(演算子)はシンプルです。
今回の主役(シフト演算子)
| 演算子 | 書式 | 何をする命令? |
|---|---|---|
| >> | a >> b | a のビット列を b ビット右にずらした値を作る |
| << | a << b | a のビット列を b ビット左にずらした値を作る |
代入を伴う形(よく使う)
| 演算子 | 書式 | 意味 |
|---|---|---|
| >>= | a >>= b | a を右に b ビットずらして a に代入する |
| <<= | a <<= b | a を左に b ビットずらして a に代入する |
この表の説明
- や << は「新しい値を生成」
- = や <<= は「ずらした結果で自分を書き換える」
という違いがあります。
サンプルプログラム
同じビット列を signed と unsigned で表示して、右シフト結果の違いが出る可能性を観察するプログラムです。
ポイント
- 同じ 0xF0(上位が1になりやすい値)を signed char と unsigned char として解釈させる
- 右シフトしたときに、上位が 0 埋めになりやすいか、1 埋めになりやすいかを見比べる
プロジェクト名:chap7-18-1 ソースファイル名:chap7-18-1.c
// 論理シフトと算術シフトの「雰囲気」を観察する例
#include <stdio.h>
void print_bits8(unsigned x)
{
for (int i = 7; i >= 0; i--) {
putchar(((x >> i) & 1U) ? '1' : '0');
}
}
int main(void)
{
unsigned char u = 0xF0; // 11110000
signed char s = (signed char)u;
printf("観察:同じ8ビットでも、型で右シフトの結果が変わることがあります。\n\n");
printf("unsigned char u = 0xF0\n");
printf("u = "); print_bits8(u); putchar('\n');
printf("u >> 1 = "); print_bits8((unsigned)(u >> 1)); putchar('\n');
putchar('\n');
printf("signed char s = (signed char)0xF0\n");
printf("s(ビット) = "); print_bits8((unsigned char)s); putchar('\n');
printf("s >> 1(ビット)= "); print_bits8((unsigned char)(s >> 1)); putchar('\n');
printf("\nメモ:signed の負数右シフトは、環境によって埋め方が変わる可能性があります。\n");
printf("ビット操作は unsigned を基本にすると安全です。\n");
return 0;
}このプログラムの説明
- u は unsigned char なので、右シフト時に 上位が 0 で埋まる動き(論理シフトっぽさ)を期待できます。
- s は signed char で、0xF0 は多くの環境で負数扱いになりやすいので、右シフト時に 上位が 1 で埋まる動き(算術シフトっぽさ)が出ることがあります。
- ただしここが重要で、signed の負数右シフトは処理系依存なので、結果は環境で変わり得ます。
「変わり得ることを観察する」ための教材プログラムです。
まとめ:迷ったらこう覚える
- 論理シフト:上位を 0 で埋める(ビット列として自然)
- 算術シフト:上位を符号ビットで埋める(符号を保ちやすい)
- C言語で安全にビット操作したいなら、まず unsigned を使う
