C言語 応用編 ~7日目~

C言語に関する説明を一通り行いました。この章は応用編の最後のセクションです。
この章では、以前に説明していなかった「ビット演算」について説明します。

ビット演算子

ビットは、コンピュータが処理を行う際のデータの最小単位です。1バイトは8ビットで構成され、基本的には0と1の2つの数値を使用する2進数で考えます。
C言語には、ビット操作を行うためのビット演算子が備わっています。これらの演算子は、ハードウェア制御や通信などの分野で頻繁に使用されます。さらに、大量のデータを扱う際にビット単位で情報を管理することもあります。

ビット演算子の一覧を以下に示します。

演算子 読み方 意味 使用例
& アンパサンド 論理積(AND) a & b
| パーティカルバー 論理和(OR) a | b
^ ハット 排他的論理和(XOR) a ^ b
~ チルダ 否定(NOT) ~a
<< 小なり 左シフト a << 2
aを左に2ビットシフト
>> 大なり 右シフト a >> 2
aを右に2ビットシフト

※ビット演算子は整数にのみ適用可能ですが、浮動小数点数には使用できません。

以下にそれぞれの演算子についての説明を示します。

&(論理積:AND)

論理積は、aとbの両方のビットが1である場合に1となる演算です。これは特定のビットを0に設定する際に使用されます。

a b a&b
0 0 0
0 1 0
1 0 0
1 1 1


以下は、論理積を使用して2進数01010101の上位4ビットを保持し、下位4ビットを0にするサンプルプログラムです。

#include <stdio.h>
// 2進数の表示関数
void printBit(char num){
	int len = 8;
	int bit[8];
	int x;
	for(int i=0;i<8;i++){
		x = (num  >> i ) & 1; //i番目のビットを取り出す
		bit[i] = (x ) ?  1 : 0; //取り出したビットから0または1を配列にセットする
	}
	//8番目のビットから表示する
	for(int i=7;i>0;i--){
		printf("%d",bit[i]);
	}
}

int main(void){
	unsigned char dt = 0b01010101; 
	unsigned char mask = 0b11110000; 
	unsigned char n;
	n = dt & mask;
	printBit(dt);
	printf(" & ");
	printBit(mask);
	printf(" = ");
	printBit(n);
}
実行結果
01010101 & 11110000 = 01010000

C言語には2進数を表示する方法がないため、printBit()という関数を作成しました。
以降のサンプルにもこの関数を使用します。

|(論理和:OR)

論理和は、aとbの両方のビットが0の場合に0となり、それ以外の場合は1になります。特定のビットを1に設定する際に使用されます。

aba|b
0 0 0
0 1 1
1 0 1
1 1 1

以下のサンプルプログラムは、2進数01010101で上位4ビットを1にし、下位4ビットをそのままにしています。

int main(void){
	unsigned char dt = 0b01010101; 
	unsigned char mask = 0b11110000; 
	unsigned char n;
	n = dt | mask;
	printBit(dt);
	printf(" & ");
	printBit(mask);
	printf(" = ");
	printBit(n);
}
実行結果
01010101 | 11110000 = 11110101

^(排他的論理和:XOR)

排他的論理和は、二つのビットが異なる場合に1を、それ以外の場合は0を返します。これは特定のビットを反転させたい場合に利用されます。

a b a^b
0 0 0
0 1 1
1 0 1
1 1 0

以下は、2進数00000101の上位4ビットを評価し、下位4ビットをそのままに保つサンプルプログラムです。

int main(void){
	unsigned char dt = 0b00000101; 
	unsigned char mask = 0b11110000; 
	unsigned char n;
	n = dt ^ mask;
	printBit(dt);
	printf(" & ");
	printBit(mask);
	printf(" = ");
	printBit(n);
}
実行結果
00000101 ^ 11110000 = 11110101

~(否定:NOT)

すべてのビット値を反転します。

以下は、2進数00001111を反転するサンプルプログラムです。

int main(void){
	unsigned char dt = 0b00001111; 
	unsigned char n;
	
	n = ~dt;
	printf("NOT ");
	printBit(dt);
	printf("=");
	printBit(n);
	printf("\n");
}
実行結果
NOT 00001111=11110000

<<(左シフト)

ビットを左へシフトします。
x << nとは、xをnビット左にシフトすることを意味します。正の数であれば、x << 1はxを2倍にすることに相当します。
以下は、10を4ビット左にシフトするサンプルプログラムです。

int main(void){
	unsigned char p = 0b00001010;
	printBit(p);
	printf(" (10進数:%d)\n",p);
	p = p << 4;
	printBit(p);
	printf(" (10進数:%d)\n",p);
}
実行結果
00001010 (10進数:10)
10100000 (10進数:160)

右側の空いたビットには0が埋められ、左側のビットは切り捨てられます。

>>(右シフト)

ビットを右へシフトします。
x >> n は、x を n ビット右にシフトすることを意味します。x が正の数である場合、x >> 1 は x を 2 で割ったものに等しくなります。

以下は、符号付きのchar型整数と符号なしのunsigned char型整数をそれぞれ2ビット右にシフトするサンプルプログラムです。

int main(void){
	char p = 0b11010000;
	printBit(p);
	printf(" (10進数:%d)\n",p);
	p = p >> 2;
	printBit(p);
	printf(" (10進数:%d)\n",p);

	unsigned char up = 0b11010000;
	printBit(up);
	printf(" (10進数:%d)\n",up);
	up = up >> 2;
	printBit(up);
	printf(" (10進数:%d)\n",up);
}
実行結果
11010000 (10進数:-48)
11110100 (10進数:-12)
11010000 (10進数:208)
00110100 (10進数:52)

char(符号付き)とunsigned char(符号なし)では、最上位ビットの扱いが異なる点に留意してください。
符号付き数値では、最上位ビットが符号ビットとして機能します。符号付き数値の場合、算術シフトが適用され、右シフト時には符号ビットの値で空位が埋められます。一方で、符号なし数値では、論理シフトが適用され、右シフト時には空位が0で埋められます。

ビット演算の使用例

たとえば、次のような使用方法があります。

(1)nビット目の取得(nは0からの値)
printBit()関数にも記述されている通り、対象の数値をnビット右シフトした後、1とAND(論理積)を取る操作を行います。
x = (num >> n ) & 1
(2)特定のビットを1にする
特定のビットに1を立てた整数を用意し、対象となる数値とOR(論理和)を取る操作を行います。
たとえば、変数numの下位3ビットをすべて1にしたい場合
x = num | 0b00000111
(3)nビット目を0にする
特定のビットに1を立てた整数を用意し、反転後、対象となる数値とAND(論理積)を取る操作を行います。
たとえば、変数numの下位3ビットをすべて0にしたい場合
x = num & ~0b00000111
(4)nビット目を反転する
特定のビットに1を立てた整数を用意し、対象となる数値とXOR(排他的論理和)を取る操作を行います。
たとえば、変数numの下位3ビットをすべて反転したい場合
x = num ^ 0b00000111

C言語の演算子の優先順位について

C言語では、演算子ごとに計算の優先順位が定められています。
四則演算では、算数の規則と同様に、加算(+)や減算(ー)よりも乗算(*)や除算(/)を優先して計算します。
また、同じ優先順位の演算子が複数ある場合、どちらの演算子から計算を始めるかを決める「結合性」というルールがあります。結合性には「左結合」と「右結合」の二種類があり、「左結合」は左側の演算子から先に計算し、「右結合」は右側の演算子から先に計算します。
以下のサイトにCの演算子の優先順位が記載されています。

https://ja.cppreference.com/w/c/language/operator_precedence

演算の優先順位を意識し、必要に応じて括弧を使用して順序を明確にすることが重要です。
例えば、以下のコードは、x の最下位ビットをテストすることを目的としています。

x & 1 == 0

しかしながら、演算子の優先順位の規則により、この式は以下のように実行されます。

x & (1 == 0)

つまり(x & 0) と評価され、結果は 0 になってしまいます。

これを避けるため、括弧を用いて、

(x & 1) == 0

と記述する必要があります。

演算子の優先順位を把握することで、式を簡潔かつ正確に書くことが可能になります。優先順位と結合性の表を記憶しておくと便利ですが、必要に応じて調べることで、正確なプログラムを作成することが大切です。

コメント

この記事へのコメントはありません。

関連記事

プログラミング 独学方法 まとめ

Python 基本編 第7回

プログラミングしなくてもアプリを作れるツール

PAGE TOP