C言語でビット演算子とシフト演算子の使い方を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
ビット演算子
ビット演算子とは,各々のビットに対する論理演算を一度に行う演算子のことです.
C言語は本来はOSのようなシステム記述用の言語として開発されたので,ビット演算等の低レベルのプログラミングが可能です.
なので,C言語は高級アセンブリ言語とみなすことができます.
C言語で用意されているビット演算子は下表になります.
記号 | 意味 | 例 |
---|---|---|
& | ビット毎のAND(論理積) | a = b & 0x7fff |
| | ビット毎のOR(論理和) | a = b | 0x8000 |
^ | ビット毎のXOR(排他的論理和) | a = b ^ 0x000f |
~ | ビット毎のNOR(ビット反転) | a = ~b |
<< | 左シフト | a = b << 2 |
>> | 右シフト | a = b >> 2 |
これらのビット操作は,charやint等の整数型にしか利用できません.
floatやdouble等の浮動小数点型には利用できないことに注意して下さい.
&:ビット毎のAND(論理積)
&はビット毎のAND(論理積)をとり,下表のようになります.
つまり,両方のビットが1の時のみ1となります.
a | b | a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
例えば,「0xaaaa & 0x00ff」というビット毎のANDは「0x00aa」になります(下表).
2進数 | 16進数 |
---|---|
1010 1010 1010 1010 | 0xaaaa |
AND | AND |
0000 0000 1111 1111 | 0x00ff |
0000 0000 1010 1010 | 0x00aa |
このように,AND(&)演算子はビットマスクをして特定のビットの内容を取り出す時によく使われます.
上の例では上位8ビットはマスクして下位8ビットのみを取り出しています.
例えば,ハードウェアのステータスの特定のビットを取り出すためにAND演算子が用いられます.
|:ビット毎のOR(論理和)
|はビット毎のOR(論理和)をとり,下表のようになります.
つまり,どちらかのビットが1の時に1になります.
a | b | a | b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
例えば,「0xaaaa | 0x00ff」というビット毎のORは「0xaaff」になります(下表).
2進数 | 16進数 |
---|---|
1010 1010 1010 1010 | 0xaaaa |
OR | OR |
0000 0000 1111 1111 | 0x00ff |
1010 1010 1111 1111 | 0xaaff |
このようにOR(|)演算子は他のビットに影響を与えることなく特定のビットを1にする時によく利用されます.
上記の例では,上位8ビットはそのままで,下位8ビットを全て1にしています.
ORは,特定ビットのフラグを立てたい時等に利用されます.
^:ビット毎のXOR(排他的論理和)
^はビット毎のXOR(排他的論理和)をとり,下表のようになります.
つまり,両方のビットが互いに異なる時に1になります.
a | b | a ^ b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
例えば,「0xaaaa ^ 0x00ff」というビット毎のXORは「0xaa55」になります(下表).
2進数 | 16進数 |
---|---|
1010 1010 1010 1010 | 0xaaaa |
XOR | XOR |
0000 0000 1111 1111 | 0x00ff |
1010 1010 0101 0101 | 0xaa55 |
このようにXOR(^)演算子はビットマスクをして特定のビットの内容を反転させる時によく使われます.
上の例では,上位8ビットはそのままで下位8ビットを反転しています.
XORは,ハードウェアの現在のステータスと違う値を出力する時等に利用されます.
~:ビット毎のNOT(ビット反転)
~(チルダ)はビット毎のNOT(ビット反転)を行い,下表のようになります.
a | ~a |
---|---|
0 | 1 |
1 | 0 |
例えば,「~0xaaaa」というビット毎のNORは「0x5555」になります(下表).
2進数 | 16進数 |
---|---|
NOT | NOT |
1010 1010 1010 1010 | 0xaaaa |
0101 0101 0101 0101 | 0x5555 |
XOR演算子が特定のビットの反転に利用するのに対して,ビット毎のNOT(~)は全ビットの内容を各々反転させることができます.
ビット毎のAND,OR,XOR,NOTのコード
これまでのビット操作の例を以下のコードに示します.
ここで,unsigned int型は32ビット(4バイト)になることに注意して下さい.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> int main(void) { unsigned int data, mask; data = 0xaaaa; mask = 0x00ff; printf("0x%04x & 0x%04x = 0x%04x\n", data, mask, (data & mask)); printf("0x%04x | 0x%04x = 0x%04x\n", data, mask, (data | mask)); printf("0x%04x ^ 0x%04x = 0x%04x\n", data, mask, (data ^ mask)); printf("~0x%04x = 0x%04x\n", data, ~data); return 0; } |
実行結果は以下になります.
AND,OR,XORは紹介した例と同じ結果です(3~5行目).
これに対して,NOTは「0x5555」ではなく「0xffff5555」と異なる結果になりました(6行目).
この理由は,unsigned int型は32ビットで上位16ビットが「0x0000」だったのが,ビット反転により「0xffff」になったからです.
1 2 3 4 5 6 |
$ gcc and_or_xor_not.c $ a.out 0xaaaa & 0x00ff = 0x00aa 0xaaaa | 0x00ff = 0xaaff 0xaaaa ^ 0x00ff = 0xaa55 ~0xaaaa = 0xffff5555 |
<<,>>:左シフト・右シフト演算子
シフト演算子は,int型やchar型等の整数のビットの並びを必要なだけ左(<<),または右(>>)にシフトする演算子です.
左シフト
4byteの整数「0xa5a55a5a」を4ビット左にシフトする場合を考えてみましょう.
4ビット左にシフトするということは,値を16倍するということです(結果は「0x5a55a5a0」).
シフトの様子は以下になります.
左シフトは,正のオーバーフローをしない限り,2のべき乗倍することができます.
1 2 3 4 |
/* left shift */ 0xa5a55a5a << 4 10100101101001010101101001011010: 0xa5a55a5a 01011010010101011010010110100000: 0x5a55a5a0 /* insert 0 in the least significant 4 bits. */ |
右シフト(論理シフトと算術シフト)
右シフトはunsigned(符号なし)かsigned(符号あり)かどうかで結果が異なる場合があります.
ここで,符号なしのシフトを論理シフト,符号ありのシフトを算術シフトと呼びます.
unsignedの整数「0xa5a55a5a」を4ビット右に論理シフトする場合を考えてみましょう.
論理シフトの様子は以下になります.
4ビット右に論理シフトするということは,値を1/16するということです.
このように,unsignedで右に論理シフトする場合は,論理シフト量分の上位ビットは0になり,結果は「0x0a5a55a5」になります.
1 2 3 4 |
/* right shift (unsigned) */ 0xa5a55a5a >> 4 10100101101001010101101001011010: 0xa5a55a5a 00001010010110100101010110100101: 0x0a5a55a5 /* insert 0 in the most significant 4 bits. */ |
同様にsignedの整数0xa0afa5a5を4ビット右に算術シフトする場合を考えてみましょう.
算術シフトの様子を以下に示します.
負のオーバーフローが発生しない限り,元の値の1/(2のべき乗分の1)することができます.
signedで右に算術シフトする場合,算術シフト量分の上位ビットは符号ビットであるMost Significant Bit(MSB)で埋められます.
signedの整数は2の補数で表現されるため,MSBが1の場合は負の数になります.
つまり,算術シフト量分の上位ビットは,正の整数の場合は0で埋められ,負の整数の場合は1で埋められます.
この操作を符号拡張と呼びます.
1 2 3 4 |
/* right shift (signed) */ 0xa5a55a5a >> 4 10100101101001010101101001011010: 0xa5a55a5a 11111010010110100101010110100101: 0xfa5a55a5 /* insert 1 in the most significant 4 bits. */ |
左シフトと右シフト(論理シフトと算術シフト)のコード
左シフトと右シフト(論理シフトと算術シフト)のコードは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #define DATA 0xa5a55a5a #define SHIFT 4 int main(void) { unsigned int data; int data2; unsigned int shift; data = data2 = DATA; shift = SHIFT; printf("left shift: 0x%08x << %u = 0x%08x\n", data, shift, data << shift); printf("right shift (unsigned): 0x%08x >> %u = 0x%08x\n", data, shift, data >> shift); printf("right shift (signed): 0x%08x >> %u = 0x%08x\n", data2, shift, data2 >> shift); return 0; } |
実行結果は以下になります.
同じ結果です.
1 2 3 4 5 |
$ gcc left_and_right_shifts.c $ a.out left shift: 0xa5a55a5a << 4 = 0x5a55a5a0 right shift (unsigned): 0xa5a55a5a >> 4 = 0x0a5a55a5 right shift (signed): 0xa5a55a5a >> 4 = 0xfa5a55a5 |
まとめ
C言語でビット演算子とシフト演算子の使い方を紹介しました.
ビット演算子はAND,OR,XOR,NOT,シフト演算子は左シフトと右シフト(論理シフトと算術シフト)があることがわかりました.
ビット演算子やシフト演算子は,OSがハードウェアの状態を制御する時によく利用されるので,正しく理解しましょう.
ビット演算子シフト演算の応用やバイトオーダーの交換(バイトスワップ)を学びたいあなたは,以下の記事を読むことをおすすめします.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!