C言語における演算子の分類と内部表現の理解

演算子の種類と概要

C言語の演算子は、処理を行う対象や機能によって多岐にわたります。主なカテゴリーは以下の通りです。

  • 算術演算子:加減乗除(+、-、*、/)および剰余演算(%)など。
  • 代入演算子:基本的な代入(=)および複合代入演算子(+=、-= など)。ビットシフトや論理演算を組み合わせる簡易形式も含まれます。
  • 単項演算子:インクリメント・デクリメント(++、--)、否定(!)、アドレス取得(&)、間접参照(*)、サイズ判定(sizeof)、型変換((type))など。
  • 関係演算子:大小比較(>、>=、<、<=)および等価判定(==、!=)。
  • 論理演算子:論理積(&&)、論理和(||)。
  • 条件演算子:三項演算子(?:)。
  • ビット操作系:シフト演算子(<<、>>)および按位演算子(&, |, ^, ~)。
  • その他の演算子:配列アクセス([])、関数呼び出し(())、メンバーアクセス(.、->)、コンマ(,)。

整数の内部表現:符号と補数

コンピュータ上での整数データは、メモリ内でどのように格納されるかを理解する必要があります。主に以下の 3 つの表現方法が知られていますが、現代のシステムでは特定の方式が標準として採用されています。

  1. 符号付き絶対値表現(原碼):最高位を符号位とし、それ以外のビットを値として扱います。
  2. 1 の補数表現(反碼):負数において、符号位を除外してすべてのビットを反転させた形式です。
  3. 2 の補数表現(補碼):1 の補数表現に対して 1 を加えた形式です。

正数の場合、これら 3 つの表現方法はすべて同じバイナリ値になりますが、負数においては異なります。特に 2 の補数表現では、符号位を含めた全てのビットを反転し、最後に 1 を加えることで求められます。逆に、2 の補数を元の値に戻す際にも、同様の反転と加算操作を行います。

なぜ 2 の補数が採用されるのか

ハードウェア設計上の観点から、計算機内部では数值を一様に 2 の補数で管理しています。主な理由は以下の通りです。

  • 符号位と数値域を区別せず、統一的に処理可能であること。
  • 加算器のみを用いて減算を実現できるため、CPU の回路構成が単純化されます(例:A - B は A + (-B) として処理)。

例えば、「1 - 1」のような計算において、もし 2 の補数を使わずに通常の二進法で計算すると結果が不正確になる可能性があります。しかし、2 の補数を使用すれば、1 とその負の補数(-1)を加算することで適切にゼロが得られ、符号の扱いもエラーなく処理できます。

シフト演算子の実行メカニズム

ビットパターンを左右に移動させる演算子には「左シフト(<<)」と「右シフト(>>)」があります。ただし、これらの操作対象は整数型に限定されます。

左シフト(<<)

指定された桁数だけデータを左方向へ移動させます。空いた下位ビットには必ず 0 が埋められます。この特性を利用すると、シフト 1 回につき元の値が 2 倍になり、シフト 2 回なら 4 倍という数学的な効果を得られます。

#include <stdio.h>
int main(void) {
    unsigned int base_value = 16;
    // 左に 2 ビットシフトすると、16 * 2 * 2 = 64 となる
    unsigned int result = base_value << 2; 
    
    printf("元値:%u\n", base_value);
    printf("シフト後:%u\n", result);
    
    return 0;
}

右シフト(>>)

右方向への移動を行います。上位ビットの埋め方には 2 つの方式が存在します。

  • 論理シフト:空いた上位ビットに 0 を埋めます。
  • 算術シフト:符号ビット(最高位)をコピーして上位ビットに埋めます。負数の場合、この方が正しい数値計算となります。

多くのコンパイラ環境では、符号付き整数に対する右シフトは「算術シフト」として実装されています。

注意:シフト量として負数を指定した場合、動作は未定義となり避けるべきです。

按位演算子の動作原理

ビット単位で論理判断を行う演算子群です。これらは整数型のデータのみに適用可能です。

演算子 名称 動作説明
& 按位 AND 両方のビットが 1 の場合のみ 1 になります。
| 按位 OR 少なくとも一方のビットが 1 であれば 1 になります。
^ 按位 XOR ビットが異なれば 1、同じであれば 0 になります。
~ 按位 NOT すべてのビットを反転させます(0→1, 1→0)。

応用課題:変数交換アルゴリズム

按位 XOR 演算(^)の性質を利用すると、一時変数を使用せずに 2 つの値を交換することができます。これは XOR の以下の特性に基づいています。

  1. 任意の値 $A$ に対して、$A \oplus A = 0$ である。
  2. 任意の値 $A$ に対して、$A \oplus 0 = A$ である。
  3. XOR 演算は交換法則と結合法則が成り立つ。

具体的な実装例は以下のようになります。

#include <stdio.h>
int main(void) {
    int x = 15;
    int y = 25;
    
    printf("交換前 : x=%d, y=%d\n", x, y);
    
    // 1. x に x と y の XOR 結果を保存
    x = x ^ y;
    // 2. y に新しい x (旧 x^y) と旧 y の XOR を取ることで、旧 x が復元される
    y = x ^ y; 
    // 3. x に新しい y (旧 x) と新しい x (旧 x^y) の XOR を取ることで、旧 y が復元される
    x = x ^ y;
    
    printf("交換後 : x=%d, y=%d\n", x, y);
    
    return 0;
}

タグ: C言語,ビット演算,2 の補数,CPU アーキテクチャ,メモリアクセス

5月20日 09:23 投稿