C言語では、変数などの記憶域を 希望のデータで設定する場合、
変数などの記憶域を表現する型と、
設定するデータの型を
合わせなければならない場合がありました。
注意すべきは、次の3箇所になるでしょう。
=演算子を使った変更で、=演算子の左辺と右辺の型 |
=初期化指定を使った設定で、対応する記憶域と初期化したい値表現の型 |
関数呼び出しで、仮引数の型と実引数の型 |
変更対象の記憶域の型と設定するデータの型 において、型が合わなくても、問題ないケースは、次の通りなどです。
変更対象の記憶域の型 | 設定するデータの型 |
---|---|
int | char |
double | int |
void * | 任意のポインタ |
特にポインタ操作にいたっては、間違えた場合、変更ができてしまうと危険なプログラムになります。
それを、以下の例で示します。
char a[5];
int * p = a; //コンパイルエラー
*p = 48 ;
この場合、pの型は(int*)型で、本来は(char *)型のaを設定する表現で、
コンパイルエラーの指摘がでます。
しかし、仮にでききてしまうと、pが指し示す型はint型なので、
48は、a[0]だけを変更するのではなく、aのアドレスから始まるint型領域を、
int型記憶方法に従って変更することになります。
仮にintが4byte整数とすると、a[0], a[1], a[2], a[4] の4byteが変更されることになります。
このようなプログラムのミスに気づくように、ポインタ型が合わない箇所ではエラーを出すようにコンパイラが作られて
いるのです。
型の違いにより情報を失うことを利用したい場合や、宣言時の記憶域をその型以外で扱いたい場合もあります。
そのような場合は、キャスト演算子のカッコを使います。つまり、 ( ) は、
カッコの中の演算を先行させる優先順最高位としての利用以外で他に、単項演算子として、
単純な型を、 ( と ) の中で指定した型に変換する
使い方があるのです。
( 型の名前 ) 型変換したい式
という表現の式で、 型変換したい式 を、型の名前へ、強制的に変換したデータに
します。単項演算子なので、オペランドは右にあるデータです。
(変換できるのは、単純な型のみで、構造体では使えません。
なお、構造体を指し示すポインタは、実質的にアドレスという単純な値なので可能です。)
型が異なるポインタでも、キャスト演算子を使えば、初期化や、=演算が可能になります。
これにより、その記憶域を、キャストした型で使うことができます。
char buf[16];
int * p = (int *)buf;
*p = 48 ;
p[1] = 49 ;
*p = 48 による変更で、
pが指し示す &buf[0]の位置からintのサイズの記憶域を、int型の記憶方法に従って48に変更しています。
int型が4byteであれば、buf[0]、buf[1]、buf[2]、buf[3]の4yteを変更することになります。
p[1] = 49 による変更は、どうでしょうか?
pから1進んだところですが、pがint型なので、p+1は実質的に4byte進んだ位置になります。
つまりbuf + 4の位置になります。その位置に対する変更なので、
buf[4]、buf[5]、buf[6]、buf[7]の4yteを、int型記憶方法に従って、変更することになります。
このように記憶域が存在して、そのアドレスをポインタ変数に記憶して管理することにより、
その記憶域をポインタを介して、そのポインタが指し示すデータ型で使うことができます。
この時 記憶域bufの型は、記憶先メモリとして使えれば、何でもよいことになります。
(もともとコンピュータの中では、このようにしてメモリを変数に割り当て使っているのです。)
例えば、*(double *)p = 12.34; のような使い方もできます。
*のポインタ演算子も、( )のキャスト演算子も、同じ優先順位です。単項演算子なので 結合規則により右から働きます。
この場合は、まず pの位置から始まる記憶域へのポインタを、(double *)型に変換します。
つまり、double型を指し示すポインタにするので、それに対する *演算子で、
そこから始まる double型記憶域を表現させ、その記憶内容を12.34に変更していることになります。
このように、記憶域を用意し、そのアドレスさえ分かれば、
その記憶域を希望の型で管理できるわけです。
次のようなコードがあるとして、問題作成ボタンをクリックして、それに答えてください。
(なお、以下はint型が2byteのコンパイラのイメージです)
char buf[12];
char * p = buf;
;