🧠

メモリ操作時、値をunsigned char型に格納する理由

2023/10/15に公開

C言語において、以下の3つの特性を持つデータ型はunsigned char型のみであるため、unsigned char型がバイナリデータの格納に使用される
メモリ操作系の関数(memsetmemcpymemmovememchrmemcmpなど)の内部では、このunsigned char型が重宝される。

1️⃣ パディングビットがなく、すべてのビットがデータの値に使用される

パディングビット(padding bits)とは?

あるデータ型がメモリ上で占めるビットの中で、そのデータの実際の値を表現するのに使用されないビットを指します。char型と、その派生型であるsigned char型とunsigned char型には、パディングビットが存在しません
👇 詳しくは以下を参照
https://zenn.dev/hyppyboy/articles/7a129182f56e1e

2️⃣ 符号がないため、未定義の挙動を生じさせない

前提

  • unsigned char型は符号を持たない。
  • signed char型は符号を持つ。
  • char型が符号を持つかは、使用しているコンパイラやプラットフォームに依存する。

符号なしの整数型の特性1:オーバーフローの挙動が安定している

符号なしの整数型のオーバーフローの挙動は、C言語の標準により定義されており、モジュロ算術に基づきます。最大値を超えて加算すると、最小値(通常は0)から再度数値が始まります。同様に、最小値より減算すると、最大値に戻ります。

例えば、unsigned char型で考えると、この型は通常8ビットであり、値域は0から255までです。この場合、値が255である unsigned char型の変数に1を加えると、結果は0になります。逆に、0から1を減算すると、結果は255になります。

これは、(x + 1) % 256または(x - 1 + 256) % 256のようなモジュロ256の算術と考えることができます。

一方、符号付き整数のオーバーフローは、コアのダンプやバッファのオーバーランなど、プログラムが何でもできる未定義の動作を引き起こすとC言語の標準で規定されています。不正な動作がオーバーフローに先立って発生することもあります。このようなオーバーフローは、加算、減算、乗算、除算、および左シフト中に発生する可能性があります。

符号なしの整数型の特性2:ビット単位の操作が安全である

符号無しのデータ型の場合、すべてのビットが値に寄与するため、ビット操作が非常に安全です。
符号ありのデータ型の場合、つまり符号ビットが存在する場合、ビット単位での操作には危険があります。 特に算術右シフトでは、意図しない結果や未定義の動作を引き起こすことがあります。

例えば、signed char型の変数が-2(ビット表現で11111110)である場合、これを1ビット右シフトすると、通常は-1(ビット表現で11111111)になります。この操作では、符号を維持するために、最上位ビット(符号ビット)が拡張されています。

しかし、この動作は必ずしも全てのプラットフォームで保証されているわけではありません。一部のプラットフォームでは、算術右シフトが論理右シフトとして実行される可能性があり、その場合、最上位ビットに0が入ります。これは通常、プログラマが期待する動作ではありません。

このように、符号ありのデータ型でビット操作を行う場合、特に右シフト操作では、プラットフォーム依存の動作や未定義の動作に注意が必要です。符号無しのデータ型では、このような問題が発生しません。それは、符号ビットが存在しないからです。

3️⃣ エイリアシングルールを違反せずに、他のデータ型をエイリアスとして使用できる

エイリアシングとは?

エイリアシングとは、複数の左辺値(lvalue)が同じメモリ位置を参照することです。左辺値とは、代入の左側にくる変数や、変更可能なオブジェクトを指します。

Copy code
int i;
int *p = &i;

この場合、*pの値を変更すると、iの値も変わります。これは、*piの別名、すなわちエイリアスであるためです。

エイリアシングルールとは?

C言語の標準では、異なる型のオブジェクトにアクセスする際に異なる型のポインタを使用することが制限されています。この制限をエイリアシングルールと呼びます。このルールの目的は、コンパイラによる最適化を効率的に行い、プログラムの予期しない動作を防ぐことにあります。

具体的には、特定の型のオブジェクトにアクセスするために別の型のポインタを使用することは、基本的には許可されていません。ただし、例外も存在します。

unsigned charとエイリアシングルール

unsigned char型のポインタは、エイリアシングルールの例外です。C言語の標準によれば、unsigned char型のポインタを使用して、任意の型のデータにアクセスすることが認められています。これは、unsigned charポインタがメモリのバイト単位のアクセスを表すためと解釈されます

記事を読む上での注意点

この記事は、書籍やネット上の記事、ChatGPT、Perplexityなどから作成した私の学習ノートです。
私はまだ経験の浅い学生であり、記述に誤りがあるかもしれません。
もし誤りを見つけた場合は、コメント欄でご指摘いただけると幸いです。
よろしくお願いします🙇‍♂️

参照

https://stackoverflow.com/questions/13642381/c-c-why-to-use-unsigned-char-for-binary-data

https://www.gnu.org/software/autoconf/manual/autoconf-2.63/html_node/Integer-Overflow-Basics.html
https://en.wikipedia.org/wiki/Arithmetic_shift#Handling_the_issue_in_programming_languages

http://dbp-consulting.com/tutorials/StrictAliasing.html
https://qiita.com/yumetodo/items/8eae5714a6cfe1c0407d
https://www.jpcert.or.jp/sc-rules/c-exp39-c.html
https://yohhoy.hatenadiary.jp/entry/20120220/p1

Discussion