42に入学してから最初の課題であるLibftというやつの予習をしていた際に、unsigned charを理解する必要があったため備忘録も兼ねて整理。
3種類のchar
・char (-128~127 / 0~255)(←範囲はコンパイラによって異なる)
・signed char (-128~127)
・unsigned char (0~255)
(参考:https://qiita.com/mizcii/items/a5adb7a56b1c4a31951a)
memchrを実装する際にマニュアルを確認したところ、どうも与えられた文字をunsigned char型にキャストしてから行うらしい。なぜなのか?
発端はたったそれだけのことだったのだけれどこれが予想以上に深かった。
void* memchr(const void* s, int c, size_t n);
Q. memchrやmemcmpはどうしてunsigned charなのか
A. unsigned charの方が処理しやすいから。
charに移る前に、まずint型の話から。
int型の変数1つあたりの大きさは4バイト、つまり32ビット。(1バイト=8ビット)
ここでint型の最大値、最小値を思い出す。
-2147483648 ~ 2147483647 (piscine中に覚えてしまった)
実はこれは2の31乗と関係がある。
2の31乗は2147483648。
31乗、32ビット。
これを2進数で表すと10000000 00000000 00000000 00000000
ちょうど32桁。
(ビットについてはまだどこか別の記事でまとめます。もし読んでいる人の中でビット列について曖昧な方がいらっしゃったら、とりあえず今は「ビット列はint型に代入されている10進数の値を2進数に変換して格納しているもの⦅最終的に機械側が1と0だけを区別して処理していくため2進数⦆」という理解で大丈夫だと思います)
ここで疑問。
intの最大値は2147483647
つまり01111111 11111111 11111111 11111111
一番左側のビット(最上位ビット)が空く。ここは空いていていいのか?
答えはシンプルで、最上位ビットは値の符号を判定しているところ。
つまり、最上位ビットが0のときは正の値で、1のときは負の値、ということになる。
例:
01111111 11111111 11111111 11111111 // 2147483647
00000000 00000000 00000000 00000001 // 1
00000000 00000000 00000000 00000000 // 0
11111111 11111111 11111111 11111111 // -1
10000000 00000000 00000000 00000000 // -2147483648
負の数の時は、最上位ビット以外のビット列を反転(0を1にして1を0にする)したのもに1を足したあと、それに-1を掛ける、という処理で値を取得できる。
例: -1だったら
11111111 11111111 11111111 11111111
→最上位ビット以外を反転: 10000000 00000000 00000000 00000000
→最上位ビット以降は全部0なので0、そこに1を足して1
→マイナスを掛けて-1
というような処理を機械が行っている。
signed char, unsigned charの場合も同様。
どちらも一つ辺り1バイトの大きさなので、例えば’a’は97という値に変換されるので
01100001
というようになる。
そして異なるのが、最上位ビットの扱い。
Asciiコードに対応している文字は0~127、つまり00000000 ~ 011111111なので最上位ビットは特に関係ないのだが、signed charは最上位ビットを符号判定用のものとして処理する(だから範囲が-128~127)のに対してunsigned charは最上位ビットを値変換用のものとして扱う(だから範囲が0~255)。
例えば、10011110だったらsigned charだと-98に変換されてunsigned charだと158に変換される。
そして、機械的にはunsigned charの方が正の数と負の数で異なる処理をする~というようなややこしいことをしなくていいのでありがたいからmemchrやmemcmpはunsigned charにキャストしてから行っている、、、、
という理解。
(※あくまでc言語初学者が自分が調べた範囲内の知識で書いているだけですので、修正点・事実と異なる点などございましたら教えて頂けますと幸いです)
以上!