C言語/データ型と変数の高度な話題

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

記憶域クラス指定子[編集]

記憶域クラス指定子とは次のいずれかである。

記憶域クラス指定子 意味
typedef データ型に別名を付ける。
extern 複数のソースファイルで、1つの識別子を共有する。
static (ファイル有効範囲)他のソースファイルから隠ぺいする。
(ブロック有効範囲)前の値が保存されているようにする。
auto ふつうは省略する。
register レジスタを使う。

typedef[編集]

typedefとは、データ型に別名をつけるためのキーワードである。 typedefを使うときの記述は次のようになっている。

typedef データ型 別名;
//例 typedefの使用例

typedef unsigned int UINT;//unsigned int型にUINTという別名をつける。

int main(void)
{
	UINT value;
}

typedefを使うことで、マルチプラットホームにおいて可搬性を高めたり (データ型を変更したい場合、typedefの部分を変更するだけで、すべてのデータ型を変更できる。)、 データ型を隠蔽し、カプセル化を向上できる。

typedefを構造体に用いる[編集]

C言語/構造体・共用体#typedefを構造体に用いるを参照せよ。

typedefをポインタに用いる[編集]

C言語/ポインタ#typedefをポインタに用いるを参照せよ。

extern[編集]

externを使うことで、複数のソースファイルで、1つの識別子を共有することができる。

//例 externの使用例

//file1.c
#include <stdio.h>

extern int i;//外部結合をもつ。
extern void function();//外部結合をもつ。このexternは省略可能。

int main(void)
{
	i=1;
	printf("最初のiの値は%d。\n", i);
	function();
	printf("function関数を呼び出した後のiの値は%d。\n", i);
}
//例 externの使用例

//file2.c
int i;

void function()
{
	i=2;
}

上の例では、file1.cとfile2.cとでiとfunction()をそれぞれ共有している。

以下、やや難しい解説。

2回以上宣言された識別子が同じオブジェクト(変数や配列など)または関数を参照することを、結合と呼ぶ。 結合には、外部結合、内部結合、および無結合の3種類がある。 外部結合とは、プログラムが複数のソースファイルからなる場合、それら複数のソースファイルで、1つの識別子の各々の宣言が、同じオブジェクトまたは関数を表すことである。 内部結合とは、1つのソースファイルの中で、1つの識別子の各々の宣言が、同じオブジェクトまたは関数を表すことである。 無結合とは、識別子の各々の宣言が、それぞれ別々の実体を表すことである。 記憶域クラス指定子を伴わず宣言されたファイル有効範囲のオブジェクトの識別子は外部結合である。 記憶域クラス指定子externを伴わず宣言されたブロック有効範囲のオブジェクトの識別子は無結合である。 記憶域クラス指定子をもたない関数の識別子の宣言は、記憶域クラス指定子externを伴って宣言されたものとする。 [1]

記憶域クラス指定子externを伴って宣言された識別子は、 以前の宣言において内部結合または外部結合が指定されているならば、以前の宣言と同じ結合をもち、 以前の宣言がない場合、または以前の宣言が無結合である場合、外部結合をもつ。 [1]

static[編集]

ファイル有効範囲のオブジェクト(変数や配列など)や関数にstaticを使うことで、そのオブジェクトや関数を他のソースファイルから隠ぺいする。 また、ブロック有効範囲のオブジェクトにstaticを使うことで、そのブロックを含む関数が再び呼び出された時、前の値が保存されているようにする。

//例 staticの使用例1
//※このプログラムはコンパイルエラーとなる。

//file1.c
#include <stdio.h>

extern int i;
extern void function();

int main(void)
{
        i=1;
        printf("最初のiの値は%d。\n", i);
        function();
        printf("function関数を呼び出した後のiの値は%d。\n", i);
}

//file2.c
static int i;//file1.cからは見えない。

static void function()//file1.cからは見えない。
{
        i=2;
}
//例 staticの使用例2

#include <stdio.h>

void function()
{
	static int i=0;//staticを使うことで前の値が保持される。
	++i;
	printf("iの値は%d。\n", i);
}

int main(void)
{
	function();
	function();
	function();
}

識別子の結合については#externを参照せよ。

以下、やや難しい解説。

記憶域クラス指定子staticを伴って宣言されたファイル有効範囲のオブジェクト(変数や配列など)または関数に対する識別子は、内部結合をもつ。 記憶域クラス指定子staticを伴って宣言されたブロック有効範囲のオブジェクトは、無結合である。 記憶域クラス指定子staticを伴って宣言されたブロック有効範囲の関数はない。 [1]

記憶域クラス指定子staticを伴って宣言された識別子のオブジェクトは、静的記憶域期間をもつ。 その生存期間はプログラム実行の全体とする。 その値はプログラム開始処理の前に一回だけ初期化する。 [2]

auto[編集]

autoはふつう省略する。

//例 autoの使用例

int main(void)
{
        auto int i;//「int i;」と書いても同じである。
}

記憶域クラス指定子autoを伴って宣言されたブロック有効範囲の識別子は、自動記憶域期間をもつ。 [2]

register[編集]

registerを使うことで、レジスタを使う。

//例 registerの使用例
#include <stdio.h>

int main(void)
{
	register int i, sum=0;
	for(i=1;i<=100;++i)
		sum+=i;
	printf("1から100までの総和は%d。\n", sum);
}

記憶域クラス指定子registerを用いて宣言されたオブジェクトは、 そのオブジェクトへのアクセスを可能な限り高速にすべきであることを示唆する。 この示唆が効果を持つ程度は、処理系定義とする。 また、registerを伴って宣言されたオブジェクトのどの部分のアドレスも、計算することはできない。 [3]

変数は普通メモリ上に確保されるが、 メモリはCPUからの物理的距離が離れているため、 メモリへのアクセスは比較的低速である。 一方、レジスタはCPU内部にあり、 物理的距離が近いため、 レジスタへのアクセスは比較的高速である。

なお、記憶域クラス指定子registerを用いたからといって必ず高速になるとは限らず、 また、用いなくともコンパイラは自動的にソースコードを最適化するため、低速になるとは限らない。

型指定子[編集]

型指定子とは次のいずれかの組み合わせである。

型指定子 意味
void 型なし
char 文字型
int 整数型
float 単精度実浮動小数点型
double 倍精度実浮動小数点型
short
long
signed 符号付き
unsigned 符号なし
struct 構造体(後ページで説明)
union 共用体(後ページで説明)
enum 列挙体

主なデータ型[編集]

データ型とは、メモリ上に確保する領域のビット長や、確保した領域の扱い方などを決定するものである。 データ型は扱いたいデータの種類や値の範囲によって決定する。

【下の表を参照しつつ読むこと】データ型には型なし、文字型、整数型、実浮動小数点型、などがある。 型なしとは値を持たない特殊なデータ型でvoidを用いる。 文字型とは1バイト文字を格納するためのデータ型でcharを用いる。また、小さな整数を格納するためにも用いられる。負の数も扱う場合にはsignedを先頭に付ける。 整数型とは整数を格納するためのデータ型でintを用いる。整数型には扱う値の範囲に応じて様々な種類が存在する。扱う値の範囲が狭い順にshort int, long int, long long intなどがある。 負の数を扱わない場合にはunsignedを先頭につける。 実浮動小数点型とは浮動小数点数を格納するためのデータ型でfloatまたはdoubleを用いる。浮動小数点数とは有効数字部と指数部とによる実数の近似値の表現方式である。またfloatとdoubleとではdoubleのほうが精度が高い。

  • 次に主なデータ型を表にまとめた。ビット長及び扱える値の範囲は処理系によって異なる場合がある。以下は主な32ビット処理系の場合である。実際のビット長及び扱える値の範囲はlimits.h及びfloat.hにより確認できる。
データ型の種類 データ型 データ型の名称 ビット長 扱える値の範囲
型なし void void型 - -
文字型 signed char 符号付き文字型 8 -128~127
(unsigned) char (符号無し)文字型 8 0~255
整数型 (signed) short int (符号付き)短整数型 16 -32768~32767
unsigned short int 符号無し短整数型 16 0~65535
(signed) int (符号付き)整数型 32 -2147483648~2147483647
unsigned int 符号無し整数型 32 0~4294967295
(signed) long int (符号付き)長整数型 32 -2147483648~2147483647
unsigned long int 符号無し長整数型 32 0~4294967295
(signed) long long int (符号付き)長長整数型 64 -9223372036854775808~9223372036854775807
unsigned long long int 符号無し長長整数型 64 0~18446744073709551615
実浮動小数点型 float 単精度実浮動小数点型 32

最小の正の数1.175494351e-38、 最大値3.402823466e+38

double 倍精度実浮動小数点型 64 最小の正の数2.2250738585072014e-308、最大値1.7976931348623158e+308

※データ型の「()」は省略可能であることを表す。
※signed int型及びunsigned int型のビット長は処理系により異なる場合が特に多い。
一般に16ビット処理系ならば16ビット、32ビット処理系ならば32ビット、64ビット処理系ならば32ビットである。

Column 32ビット(x86)と64ビット(x64)

以前のパソコンは32ビット(x86)を使うことが多かったが、
現在のパソコンは64ビット(x64)を使うことが主流となっている。

DLL(Dynamic Link Library)は、
32ビットと64ビットで互換性がないため、
特に注意が必要である。

Visual Studio Express 2012を使用する場合、
以下の手順で64ビットに切り替えることができる。
上部ツールバーの「Win32」で「構成マネージャー」を選択し、
「アクティブ ソリューション プラットホーム」で「新規作成」を選択し、
新しいプラットフォームに「x64」が選択されているのを確認し「OK」を選択する。

タスクマネージャーで64ビットのプロセスであることが確認できる。

struct[編集]

構造体については、C言語/構造体・共用体を参照せよ。

union[編集]

共用体については、C言語/構造体・共用体を参照せよ。

enum[編集]

列挙体とは、識別子をもった整数定数のリストである。 列挙体を用いることで、整数定数の代わりに識別子を使うことができ、 ソースコードの可読性を高める。

//例 列挙体の使用例
#include <stdio.h>

enum eNumber
{
	ZERO,
	ONE,
	TWO,
};

int main(void)
{
	enum eNumber n;
	n=ZERO;
	printf("nの値は、%d。\n", n);
	n=ONE;
	printf("nの値は、%d。\n", n);
	n=TWO;
	printf("nの値は、%d。\n", n);
}

上の例では、ソースコード中で0, 1, 2の代わりにZERO, ONE, TWOを用いることができる。

列挙体の型枠の宣言の記述は次のようになっている。[4]

enum タグ名
{
	列挙定数=定数式,
		:
		:
};

「タグ名」で、列挙体の型枠に名前を付ける。 列挙体のメンバ(要素のこと)を列挙定数と呼ぶ。 使用する列挙定数の数だけ、「列挙定数=定数式,」を含める。 「列挙定数」で、その列挙定数の識別子を定める。 「定数式」はint型で表現可能な値をもつ整数定数式でなければならない。 「列挙定数」は、型intをもつ定数として宣言され、 この型の定数が許されるところならばどこに現れてもよい。 「=定数式」でその定数式をもつ列挙定数を定義する。 「=定数式」は書かなくてもよい。 最初の列挙定数に「=定数式」がない場合、その列挙定数の値は0とする。 =がない2番目以降の各列挙定数は、 直前の列挙定数の値に1を加えた値とする。

列挙体の宣言の記述は次のようになっている。

enum タグ名 変数名のリスト;

タグ名で、列挙体の型枠を指定する。 変数名のリストで列挙体に名前を付ける。 複数の列挙体を宣言するときは、変数名を「,(コンマ)」で区切る。

なお、列挙体の型枠の宣言と列挙体の宣言とを、同時に行うこともできる。 次のように記述する。

enum タグ名
{
	列挙定数=定数式,
		:
		:
}変数名のリスト;

この場合、タグ名は省略することができる。

列挙体を使用するときの記述は次のようになっている。

列挙定数

列挙定数の値は、列挙体の型枠の宣言で列挙定数に指定した定数となる。

型修飾子[編集]

型修飾子は次のいずれかの組み合わせである。

型修飾子 意味
const 定数にする。
restrict 最適化を促進する。
volatile 最適化を抑制する。

const[編集]

const修飾型をもったオブジェクトは初期化以外で変更不可能となる。 つまり、そのオブジェクトを定数として扱うことができ、 プログラム中で誤って変更するのを防ぐことができる。 [5]

//例 constの誤った使用例
//※このプログラムはコンパイルエラーとなる。

int main(void)
{
	const int i=1;
	++i;//iの値は変更不可能。
}

restrict[編集]

restrictとは、ポインタに用いる型修飾子であり、 そのポインタが指す先を、同一関数・ブロック内の別のポインタが指さないという情報をコンパイラに伝え、 コンパイラの最適化を促進することができる。 restrict型修飾子を付けても付けなくても、目に見える動作は変わらない。 [5]

restrictの例として、標準ライブラリstring.hのmemcpy関数とmemmove関数が挙げられる。

#include<string.h>

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);

これら2つの関数は、 どちらもs2をs1にn文字コピーするが、 memcpyは領域の重なり合わないオブジェクト間でコピーする必要があり、 そのためrestrict型修飾子を用いることができる。

volatile[編集]

volatileとは、 変数がコンパイラに未知の方法で変更され、 又はその他の未知の副作用を持つことをコンパイラに伝え、 コンパイラの最適化を抑制する型修飾子である。 volatile宣言は、 メモリ上に割り付けられた入出力ポートに相当するオブジェクト、 又は非同期的な割り込み機構によってアクセスされるオブジェクトを記述するのに使用してもよい。 [5]

つまりvolatileは、 割り込み処理や他のスレッドから変数が変更される可能性がある場合に指定したり、 レジスタを一定の手順でセットする必要があるが一見すると冗長な場合に指定したりすることで、 コンパイラの最適化によって起こる不具合を回避することができる。、 [6]


参考文献[編集]

  • 日本工業標準調査会『JISX3010 プログラム言語C』2003年12月20日改正
  • ^ 1.0 1.1 1.2 『JISX3010:2003』p.23「6.2.2 識別子の結合」
  • ^ 2.0 2.1 引用エラー: 無効な <ref> タグです。 「オブジェクトの記憶域期間」という名前の引用句に対するテキストが指定されていません
  • ^ 『JISX3010:2003』p.71「6.7.1 記憶域クラス指定子」
  • ^ 列挙体の型枠という用語はこの教科書独自のものであり、『JISX3010:2003』に出てくるわけではないので注意してください。
  • ^ 5.0 5.1 5.2 『JISX3010:2003』p.79「6.7.3 型修飾子」
  • ^ 『職業としてのプログラミング volatileで最適化を抑制する』http://proger.blog10.fc2.com/blog-entry-20.html