unsigned / signed / const / volatile
| 対応: | C89(1989) |
|---|
型修飾子は基本型に付加して、変数の性質を変えるキーワードです。符号の有無を指定する『unsigned/signed』、値の変更を禁止する『const』、最適化を制御する『volatile』があります。
構文
// 符号なし整数型(0以上の値のみ)を宣言します。 unsigned int 変数名; unsigned char 変数名; // 符号あり整数型(負の値も扱える)を宣言します(デフォルト)。 signed int 変数名; // 値を変更できない定数を宣言します。 const 型 変数名 = 値; // コンパイラの最適化を抑制します。 volatile 型 変数名; // 複数の修飾子を組み合わせることもできます。 const unsigned int 変数名 = 値;
型修飾子一覧
| 修飾子 | 概要 |
|---|---|
| unsigned | 符号なし整数型にします。負の値を扱わない代わりに、正の値の上限が約2倍になります。例えば『unsigned int』は0〜4294967295を扱えます。 |
| signed | 符号あり整数型にします。デフォルトの挙動のため通常は省略しますが、『char』は処理系によって符号ありかなしかが異なるため明示することがあります。 |
| const | 変数を定数として宣言します。宣言時に初期化が必須で、以降は代入や変更ができません。 |
| volatile | コンパイラの最適化を抑制します。割り込みやハードウェアレジスタのように、プログラムの外部から値が変わる可能性がある変数に使用します。 |
サンプルコード
sample_unsigned_const.c
#include <stdio.h>
int main(void) {
/* u サフィックスで符号なし定数を明示します。 */
unsigned int population = 1280000000u;
printf("人口: %u\n", population);
unsigned char byte = 255;
printf("バイト値: %u\n", byte);
/* 符号なし型は 0 - 1 が最大値になります(ラップアラウンド)。 */
unsigned int u = 0;
u = u - 1;
printf("unsigned のラップアラウンド: %u\n", u);
const double PI = 3.14159265358979;
const int MAX_SIZE = 100;
printf("円周率: %f\n", PI);
printf("最大サイズ: %d\n", MAX_SIZE);
/* PI = 3.0; */ /* コンパイルエラーになります。 */
/* volatile はコンパイラの最適化を抑制します。 */
volatile int sensor_value = 0;
printf("センサー値: %d\n", sensor_value);
/* const volatile は読み取り専用ハードウェアレジスタに使います。 */
/* const volatile int *reg = (int *)0x40000000; */
return 0;
}
コンパイルして実行すると次のようになります。
gcc unsigned_const.c -o unsigned_const ./unsigned_const 人口: 1280000000 バイト値: 255 unsigned のラップアラウンド: 4294967295 円周率: 3.141593 最大サイズ: 100 センサー値: 0
const ポインタと const を使ったバッファ保護
const をポインタと組み合わせると、「ポインタが指す先を変更しない」ことをコンパイラに伝えられます。関数引数での read-only 保証によく使われます。
const_pointer.c
#include <stdio.h>
/* const char *: ポインタの指す先(文字列)を変更しません。 */
int count_char(const char *s, char c) {
int n = 0;
for (; *s; s++) {
if (*s == c) n++;
}
return n;
}
int main(void) {
const char *msg = "Son Goku is the strongest.";
printf("'o' の数: %d\n", count_char(msg, 'o'));
printf("'s' の数: %d\n", count_char(msg, 's'));
/* const 配列要素には代入できません。 */
const int scores[] = {95, 88, 72, 100};
/* scores[0] = 0; */ /* コンパイルエラー */
int total = 0;
for (int i = 0; i < 4; i++) {
total += scores[i];
}
printf("合計: %d\n", total);
return 0;
}
コンパイルして実行すると次のようになります。
gcc const_pointer.c -o const_pointer ./const_pointer 'o' の数: 3 's' の数: 2 合計: 355
よくあるミス
よくあるミス: 符号なし型と符号あり型を混在させる
符号なし型と符号あり型を比較・演算すると、符号あり側が符号なしに変換されて意図しない結果になります。
unsigned_signed_ng.c
#include <stdio.h>
int main(void) {
unsigned int u = 1;
int s = -1;
/* NG: s が unsigned に変換されて巨大な値になる */
if (s < (int)u) {
printf("OK: キャストして比較\n");
}
/* NG を確認 */
if ((unsigned int)s > u) {
/* (unsigned int)(-1) = 4294967295 なので u=1 より大きい */
printf("符号あり -1 を unsigned にすると: %u\n", (unsigned int)s);
}
/* OK: 同じ型で比較する */
int ui = (int)u;
if (s < ui) {
printf("OK: 同じ型で比較 (%d < %d)\n", s, ui);
}
return 0;
}
修正後は次の通りです。
gcc unsigned_signed_ng.c -o unsigned_signed_ng ./unsigned_signed_ng OK: キャストして比較 符号あり -1 を unsigned にすると: 4294967295 OK: 同じ型で比較 (-1 < 1)
概要
『unsigned』型は負の値を扱わないことで正の値の範囲を広げます。符号なし型と符号あり型を混在させた演算は意図しない結果を招くことがあります。比較演算でも符号なし型が優先されるため、注意が必要です。
『const』はコンパイラに「この値を変更しない」ことを伝えることで、不正な代入をコンパイル時に検出できます。マクロ(『#define』)による定数定義より型チェックが効くため、C99以降では『const』を使う方が推奨されます。マクロについては『#include / #define(定数)』を参照してください。
『volatile』はハードウェア制御や組み込みプログラミングで重要です。コンパイラはレジスタキャッシュなどの最適化を行うことがありますが、外部から値が変わる変数に対してはこの最適化が誤動作を引き起こします。各型の実際のサイズは『sizeof』で確認できます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。