公開日
2007-08-03
公開元
灰塵

PC小噺2 - C言語の型名

プログラミング言語Cには變數に型が存在し、夫々大きさや用途が異つてゐます。それにより、此の變數は何の爲に使用するかが分りやすくなつたり、といふ效果があります。その型には整數や文字などがありますが、C言語は配列や構造體などを用ゐて、どんどん型を派生させることができます。普通、派生させる前、詰り導出元の型となるものにはint,short,long,char(それぞれunsignedとsignedが付く場合がある)がありますが、それらをスカラ型と言ひ、それらから派生した型を派生型と呼びます。スカラ型の型名は、int型、long型,unsigned char型などと言はれ、sizeof演算子を使つてその型のサイズを求めるときも、sizeof(int),sizeof(long),sizeof(unsigned char)などと記述しますが、派生型の場合の型名は一體どうなつてゐるでせうか。

スカラ型の型名

Cでは、變數宣言の際、複數の變數を宣言することが可能ですが、ここでは説明のため、一つしか宣言してゐません。先づ、以下の宣言文たちを見てください。

int a;
short b;
long c;
char d;

此の場合、變數aの型名はint、變數bの型名はshort、變數cの型名はlong、變數dの型名はcharとなつてゐます。之は詰り、宣言文から識別子とセミコロンとコロンを取り除けば型名になるといふ訣です。

配列の型名

Cでは、配列を宣言する際、識別子のあとに[]をつけ、其の中に要素數を記述します。詰り、intを要素型とする要素數5の配列は以下の樣に宣言します。

int e[5];

さて、此の場合、配列である變數eの型名は何になるでせうか。先程述べた、宣言文から識別子とセミコロンとコロンを取り除けば型名になるといふ原理が配列にも適應できるとすれば、變數eの型名はint [5]になります。之は合つてゐて、配列にも先程の原理は適應できることがわかりました。ヨシ介の處理系では、sizeof(e)とsizeof(int[5])は共に20と、同じ値を示して呉れました。

今の例は、intから導出された派生型です。では次は、【《"int"から導出された要素數5の配列》から導出された要素數3の配列】を考へてみませう。之はつまり配列の配列ですから、二次元配列です。先づ、以下の宣言文を見てください。

int f[5][3];

さて、此のページを讀んでゐる人はもう氣付いてゐると思ひますが、二次元配列に就いても同樣、宣言文から識別子とセミコロンとコロンを取り除きます。すると、二次元配列である變數fの型名はint [5][3]となりました。今囘も、ヨシ介の處理系は同じ値を表示して呉れました。

之は、配列が3次元、4次元になつても適應可能な原理です。

構造體の型名

構造體は、澤山の型を纏めて一つの型とし、扱へるやうにしたもので、一纏りに出來るデータを扱ふ際にはもつてこいのものです。宣言の仕方は以下の樣になります。

struct タグ名{
    變數の宣言;
    變數の宣言;
};

構造體に含まれる變數をメンバと言ひますが、メンバの數は一つ以上なら幾つでも構ひません。また、定義した構造體を實際にオブジェクトとして宣言するには以下の樣にします。

struct タグ名 識別子;

では、構造體の定義、宣言の方法を知つたところで、型名の話に戻りませう。先づ、以下の例を見てください。

struct Line{
    int x0, y0;
    int x1, y1;
};

struct Line g;

此の例では、Lineといふタグ名の構造體を定義し、その構造體の實體gを宣言してゐます。この場合、構造體である變數gの型名は何になるでせうか。はい、もうお決りではありますが、宣言文から識別子とセミコロンとコロンを取り除きます。さうすれば、構造體である變數gの型名はstruct Lineとなります。

次に、派生型の派生型を考へます。

// 構造體struct Lineの定義文は省略
struct Line h[8];
struct Line i[3][3];

變數hは派生型の派生型、變數iは派生型の派生型の派生型です。型名はそれぞれstruct Line[8]と、struct Line[3][3]となります。

ポインタの型名

普通の變數が、メモリ上に確保されて中に値を格納するのに對し、ポインタはメモリのアドレスを格納します。宣言の仕方は、以下の樣になります。

int *j;
long *k;
short *l;
char *m;

ポインタのサイズは、メモリのサイズによつて異りますが、大體現在のPCは32bitメモリ空間なので、全て4byteです。全てサイズが4byteなのに、何故intやlongなどが要るかといふと、之は、そのポインタを介してデータをやりとりする際、何バイト單位でデータをやりとりするかを示す爲に必要になります。

此のポインタの型名を考へてみませう。ポインタであることを示す、'*'の文字が識別子についてゐるので混亂するかもしれませんが、識別子はj,k,l,mであつて、*j,*k,*l,*mではありません。詰り、型名の原理を用ゐると、夫々の型名は、int *,long *,short *,char *となります。詰り、變數jはint *型の變數なのです。

では、このポインタから更に派生した型を考へます。

int **n;

之は、'*'が二個付いてゐますから、ポインタのポインタです。之は簡單ですね、型名はint **で正解です。

では、次はポインタと配列が組み合はさつた派生型を考へてみます。先づは、ポインタの配列を考へます。

int *o[3];

之は非常に簡單です。當然のことながら、變數oの型名はint *[3]で正解です。次に考へるのは、配列へのポインタです(宣言が少し複雜です)。

int (*p)[3];

之で、配列へのポインタが宣言できます。宣言文に括弧を使ふといふのは餘りなく、複雜な宣言のやうに見えますが、ここから型名を得るのは簡單で、今まで通りで良いのです。詰り、變數pの型名はint (*)[3]となります。


さて之で、變數へのポインタの型名は理解できました。では、關數へのポインタの場合はどうなるでせうか。

變數がメモリ上に存在し、そのアドレスを取得してポインタに格納できるといふのと同じやうに、關數もメモリ上のどこかに存在してゐますから、そのアドレスをポインタに格納することもできます。先づ、宣言の構文を示します。

戻り値 (*識別子)(引數1,引數2,...);  // 「戻り値」と「引數」はそのポインタに格納する關數の戻り値と引數です。

複雜ですが、宣言例を見てみませう。(かなり複雜です)

void func1(void); // 戻り値無し(void),引數無しの關數
int  func2(void); // 戻り値int,引數無しの關數
char *func3(int, char *, long);  // 戻り値char *,引數3つ(int, char *, long)の關數

void (*q)(); // 戻り値無し、引數無しの關數を格納できる
int (*r)();  // 戻り値int、引數無しの關數を格納できる
char *(*s)(int, char *, long);  // 戻り値char *,引數3つ(int, char *, long)の關數を格納できる

宣言文を見ると、訣が分らなくなりさうですが、型名を求める原理は變はりませんから、變數qの型名はvoid (*)()、變數rの型名はint (*)()、變數sの型名はchar *(*)(int, char *, long)です。

複合例

正直、殆ど遊び。

struct Line **(*t)(int, int, int, int)[4];

struct Lineへのポインタのポインタを返し、四つの引數(int, int, int, int)を受け取る關數へのポインタを要素型とする要素數4の配列です。まあ、こんな訣の分らない型を使ふことなんてないでせうが、かういふ風な宣言も可能です。型名は、struct Line **(*)(int,int,int,int)[4]で、その型のサイズを求めたい場合は、sizeof(struct Line **(*)(int,int,int,int)[4])で求められます。

void *(**u[3])[5];

void *を要素型とする要素數5の配列へのポインタへのポインタを要素型とする要素數3の配列です。型名は、void *(**[3])[5]です。