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に設定する際に使用されます。
a | b | a|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
と記述する必要があります。
演算子の優先順位を把握することで、式を簡潔かつ正確に書くことが可能になります。優先順位と結合性の表を記憶しておくと便利ですが、必要に応じて調べることで、正確なプログラムを作成することが大切です。
コメント