42
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C言語のunsigned char型が想像以上に沼だった話

Last updated at Posted at 2020-06-28

#はじめに
C言語学習者にとっては誰もが一度は疑問に思う、**charとunsigned charとsigned charの使い分けがよくわからないよ!**という悩み。

ことの発端は、memcpyやmemcmp, memsetなどの関数のなかでは、汎用ポインタ(void*)型として渡された引数をunsigned char*型にコピーして操作しているらしい、ということに気づいたところから始まる。

memset.c
void *memset(void *dst, int val, size_t len)
{
    unsigned char *ptr = dst; //unsigned char*型を使用している!
    while (len-- > 0)
        *ptr++ = val;
    return dst;
}

※memset : dstに対してlenバイト分だけvalで埋めるための関数。

このときに、「え?なんでunsigned char型なの?char型じゃダメなの?」と疑問に思ったので、いろいろ調べてみた。

#char型には3種類あり、すべて別物
まず、そもそもcharunsigned charsigned charはすべて別物だ。
これこそが初学者が最も陥りやすい第一ポイントではないかと思う。

処理系は、char を、signed char または unsigned char のいずれかと同じ値の範囲、同じ表現形式、同じ動作をするものとして定義しなければならない。char はどちらに定義されたとしても、signed char とも unsigned char とも異なる型であり、これらの型と互換性はない。
(引用:JPCERT GC)

つまり、charをsinged- かunsigned- とするかは標準として未規定であり、これは処理系(コンパイラ)が定義するように任されている。そのどちらに定義されたとしても、この3種類の型に互換性はないということらしい。

だから、ネットでたまに「char型の範囲は-128〜127だ」と断言しているのを見かけるがそれは誤りである。
この辺はintと仕様が異なるところなので注意したい。

データ型名 バイト その他の名前 値の範囲
int 2または4 signed -2,147,483,648 ~ 2,147,483,64
unsigned int 2または4 unsigned 0 ~ 4,294,967,295
char 1 - 0 〜 255 / -128 〜 127
signed char 1 - -128 〜 127
unsigned char 1 - 0 〜 255
参考:データ型の範囲 - Microsoft Docs

#3種類のcharの基本的な使い分け
結論からいえば、文字集合としての単純な文字データを扱う場合にはcharを、数値として扱う場合にはsigned charかunsigned charを用いるのが基本的な使い分けである。

特に、mem-系の関数のように、操作対象が汎用型(void *など)で、その対象すべてのビットにアクセスしたいような場合にはunsigned charが積極的に使われている(後述)。

1. 文字集合を扱う場合

文字列処理系の標準ライブラリの仕様を考慮し、文字データを単純に表す際にはcharを使用するのが好ましい。

2. charを数値として扱う場合

この場合はsingned charかunsigned charを使う。
いずれにせよ値の範囲に収まるように注意せねばならず、特にsigned charの範囲は-128 ~ 127と狭いのでいまいち使いどきがピンとこない。

こういうときこそsigned charの出番だよ!という良い事例ないかな...

#unsigned charの特徴
冒頭で、mem-系の関数のなかでは汎用ポインタ(void*)型として渡された引数をunsigned char*型にコピーして操作しているのなんで?という疑問を紹介した。
詳しく調べてみると、どうやらunsigned charの標準仕様となっている表記法に関係があるようだ。

C標準[ISO/IEC 9899:2011]6.2.6.

unsigned char型の[...]オブジェクトに格納される値は純2進表記法で表現される。
...
(純2進表記法とは) バイナリの桁 0 および 1 を使用する整数の位置表現で、連続するビットで表現される値は加算方式をとり、1 から始まり、最上位ビット以外は連続する 2 の整数乗で乗算される。

だから、汎用ポインタ(void *)型などで渡されたオブジェクトの非ビットフィールドに対して、その表現を1バイトずつ検査することが可能になる。

...文系には頭がなかなか追いつかないが、unsigned char型のオブジェクトはパディングビットを持たないことがあるので、mem-系関数のようにメモリ領域そのものを扱うような関数で、操作対象の非ビットフィールドも含めたすべてのビットにアクセスしたいときにはunsigned char型の表記法が最も適する、ということのようだ。

memcpyやmemcmpなどの関数のなかで、void *型で渡される引数をunsigned char型にコピーして操作するのはそのためである。

 
 
unsigned charを完璧に理解するにはまだ道のりが長そう。。

42
23
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?