データ型

基本的なデータ型

データ型とは、変数や関数の戻り値などで使用する、プログラムで実際に扱う「値」の形式のことです。
いままで登場したint型は整数を表す型、double型は小数を表す型です。

C言語には以下のようなデータ型が存在します。

char
1バイト符号付整数。
-128~127の値を記憶できる。
主に英数字を一字記憶させる用途に使用する。
short
2バイト符号付整数。
-32,768~32,767の値を記憶できる。
int
4バイト符号付整数。
-2,147,483,648~2,147,483,647の値を記憶できる。
(約-21億~21億)
64ビット環境では8バイトとなる環境もある。
(ちなみに16ビット環境では2バイト整数だった)
long
4バイト符号付整数。
64ビット環境では8バイトとなる環境もある。
(UNIX系OSなど)
long long
8バイト符号付整数。
-9,223,372,036,854,775,808~9,223,372,036,854,775,807の値を記憶できる。
(約-922京~922京)
float
4バイト単精度浮動小数点実数。
double
8バイト倍精度浮動小数点実数。
floatよりも高い精度の小数を表現できる。

バイトとは、コンピューターの情報量(記憶容量)の単位のひとつです。
1バイトで英数文字を一文字分記憶できます。
(1バイト=8ビット=2の8乗=256通りの情報を記憶できる)

整数のデータ型は、記憶できる数値の幅が違うだけで使い方は同じです。
大きい桁数の数値を使いたい場合には、より大きなバイト数を持つデータ型を使用します。

小数を含む値を使用する場合は、float型かdouble型のどちらかを使用します。
これらのデータ型が実際に記憶できる数値の幅はちょっと複雑なため、省略します。

64ビット版のWindowsでは、int型、long型が4バイト(32ビット)です。
64ビット版のUnix系OS(多くのLinux、MacOSなど)はint型が32ビット、long型が8バイト(64ビット)です。

符号なし整数

上記に加えて、整数のデータ型には以下の型が存在します。

signed
符号付き整数。
+-記号を付けた整数。
通常は省略される。
unsigned
符号なし整数。
+-の記号を付けない整数。
正の数(0以上の数)しか表現できない。
その代わり、正方向への表現できる数が倍になる。
(unsigned charならば0~255の範囲を表現できる)

これらは各データ型の名称の前に記述します。


int main()
{
    //signedは付けてもつけなくても同じ
    int a = 1;
    signed int b = 1;
	char c = 1;
	signed char d = 1;
    
    //unsigned型はマイナス値を持てない
    unsigned char e = 1;
	unsigned short f = 1;
	unsigned int g = 1;
	unsigned long h = 1;
	unsigned long long i = 1;
}

signedもunsignedも記述しない場合、signedを指定したものとみなされます。
なので、通常はsignedの記述は省略されます。

unsigned型整数はマイナスの値を保存することができません。
そのかわり、プラス側には符号ありint型の変数の倍の大きさの値を保存できます。

小数型には符号なし型はありません。

なお「signed」のみを記述した場合は「signed int」とみなされます。
「unsigned」のみを記述すると「unsigned int」とみなされます。


int main()
{
	//どちらも符号ありint型
	signed a = 0;
	signed int b = 0;

	//どちらも符号なしint型
	unsigned c = 0;
	unsigned int d = 0;
}

「short」「long」「long long」は、それぞれ「short int」「long int」「long long int」の省略形です。
しかし冗長になるだけなので、普通は省略形で記述します。


int main()
{
	//一緒
	short a = 0;
	short int b = 0;

	//一緒
	long c = 0;
	long int d = 0;

	//一緒
	long long e = 0;
	long long int f = 0;
}

リテラル

コード中に直接記述される、数値や文字列などの値をリテラルといいます。


int num = 10;
printf("あいうえお");

このコード中の「10」や「"あいうえお"」がリテラルです。

文字列リテラル

上のコードの「"あいうえお"」のように、コード内に直接記述される文字列を文字列リテラルと呼びます。

文字列リテラルの改行

文字列リテラルの途中に円記号(バックスラッシュ)を書くと、コード中の文字列を途中で改行することができます。
実際に出力される文字列は改行されません。


printf("いろはにほへと ちりぬるを \
わかよたれそ つねならむ");
いろはにほへと ちりぬるを わかよたれそ つねならむ

円記号は環境によって日本円の記号(¥)またはバックスラッシュ(\)で表示されます。
ここではバックスラッシュは全角文字で表示していますが、本来は半角文字です。

文字列リテラルの連結

文字列リテラル同士を連続で書くと、文字列は結合しているものとみなされます。
途中の空白やタブ文字、改行は無視されます。


printf("ABC" "DEF");
//↑↓同じ
printf("ABC""DEF");
//↑↓同じ
printf("ABCDEF");

これを利用してコード中の文字列を改行することもできます。


printf("いろはにほへと ちりぬるを"
    " わかよたれそ つねならむ");

数値リテラル

コード中にそのまま記述される数値を数値リテラルといいます。


//「10」が数値リテラル
int num = 10;

小数値の整数部が0の場合、0の記述は省略できます。


double d;

//どちらも同じ
d = 0.5;
d = .5;

サフィックス

数値リテラルは、整数値をそのまま書くとそれはint型の整数とみなされます。
小数値をそのまま書くとdouble型とみなされます。

これらの末尾に特殊な文字を加えることで、そのデータ型を変更することができます。
この特殊文字をサフィックスといいます。


int _int				= 10;
double _double			= 10.0;
unsigned int _uint		= 10U;
long _long				= 10L;
unsigned long _ulong	= 10UL;
long long _longlong		= 10LL;
unsigned long long _ull = 10ULL;
float _float			= 10.0F;

サフィックスには以下があります。

U,u
unsigned int
L,l
long
UL, ul
unsigned long
LL, ll
long long
ULL, ull
unsigned long long
F, f
float

大文字、小文字はどちらでも構いませんが、Lの小文字は数字の1と紛らわしいため、大文字で統一したほうがいいでしょう。

なお、サフィックスを付けずに整数値をそのまま記述した場合でも、int型で表現できる範囲外の大きな値である場合はunsigned int型やlong long型など、その値が表現できるうちの最小のデータ型であるとみなされます。
unsigned long long型で表現できる値以上を記述した場合はエラーとなります。

プレフィックス(プリフィックス)

数値リテラルの先頭に特定の文字を追加することで8進数や16進数を表現することができます。
これをプレフィックス(プリフィックス)といいます。


#include <stdio.h>

int main()
{
    int oct = 010;
    int hex = 0x10;

    printf("%d\n", oct);
    printf("%d", hex);
    getchar();
}
8
16
0
8進数を表す
0x
16進数を表す

8進数は桁ひとつで「0~7」を表しますから、2桁目を「1」にすると「8」を表します。
16進数は桁ひとつで「0~15」を表しますから、2桁目を「1」にすると「16」を表します。

16進数は数字だけでは数が足りないので、10以降はA~Fのアルファベットを使用します。


#include <stdio.h>

int main()
{
	int hex9 = 0x9;
	int hex10 = 0xA;
	int hex11 = 0xB;
	int hex15 = 0xF;
	int hex16 = 0x10;

	printf("%d\n", hex9);
	printf("%d\n", hex10);
	printf("%d\n", hex11);
	printf("%d\n", hex15);
	printf("%d\n", hex16);
    getchar();
}
9
10
11
15
16

8進数、16進数をそのまま画面に表示する場合は、printf関数の変換指定子を以下のように書き換えます。


#include <stdio.h>

int main()
{
    int oct = 010;
    int hex = 0x10;

    //8進数表示
    printf("%o", oct);

    printf("\n");

    //16進数表示
    printf("%x", hex);

    getchar();
}

このコードはどちらも「10」と表示されます。

コンパイラによっては「0b」で2進数を表すことができます。


int bin = 0b1101;
printf("%d", bin);
13

桁あふれ(オーバーフロー)

それぞれのデータ型には、保存できる値の幅に制限があります。
ではその幅を超えた値を変数に代入するとどうなるでしょうか。


#include <stdio.h>

int main()
{
    unsigned char _uchar = 255;
    _uchar = _uchar + 1;
    printf("%d", _uchar); //0
    getchar();
}

unsigned char型には0~255の値を保存できます。
そのため5行目の時点では問題はありません。

次の行で255に1を足すと256となり、unsigned charの限界を超えてしまいます。
これを桁あふれ(オーバーフロー)といいます。

このコードを実行すると、結果は「0」と表示されます。
255から一巡してまた0に戻ってきてしまったのです。

反対に、0から1を引くと255になります。

厳密に言えば、符号なし整数のオーバーフローはラップアラウンドといいます。

符号あり整数の場合

上記は符号なし整数(unsigned)で説明しましたが、符号あり整数の場合はこのような動作になる保証はありません。


#include <stdio.h>

int main()
{
    char _char = 127;
    _char = _char + 1;
    printf("%d", _char); //-128...?
    getchar();
}

char型の変数には-128~127の整数が保存できます。
すでに127が保存されている変数に1を足すとどうなるでしょうか。

実際にやってみると、おそらく「-128」と表示されると思います。
なんだ結局一巡するんじゃないか、と思われるかもしれませんが、この動作はただの偶然です。

符号付き整数でオーバーフローが発生した場合、どういった動作になるかはC言語では決められていません。
つまり一巡して最低値に戻るとは限らず、無茶苦茶な値になったりプログラムがクラッシュしたり…という可能性もゼロではないのです。
(Visual Studioではたぶん一巡してくれますが、C言語の仕様上はオーバーフローは禁則)

符号付き整数でも符号なし整数でも、オーバーフローが発生すると意図しない値になることは変わらないので、オーバーフローはできるだけ発生させないようにプログラミングする必要があります。
(ただし、符号なし整数をあえてオーバーフローさせることは可能です)

未定義の動作

C言語などのプログラミング言語には「規格」があり、各コンパイラ(Visual Studioやgccなど)はその規格に沿って作られています。
しかしすべての場合においての事細かな取り決めがあるわけではなく、規格に定義されていない動作も存在します。
オーバーフローがその代表的な例で、こういったものを未定義の動作といいます。

未定義の動作の実行については、C言語の規格としてはどのような動作をしてもかまわない、とされています。
つまり同じコードでもコンパイラによって動作が異なるので、あるコンパイラでは(一見して)エラーに見えないように処理されたり、あるコンパイラではプログラムが異常終了したり、ということになり得ます。
同じコンパイラでもバージョンアップによって動作が変更される可能性もあり得るので、こういったコードは可搬性(移植のしやすさ)がなく避けるべきです。