第 6 章 C 言語入門
6.8 変数とは
6.8.3 変数の型
Cで定義されている変数の型は以下の通りである.11 変数の型 型の名前
char 文字型
short int 短い整数型shortと書いても良い
int 整数型
long int 長い整数型longと書いても良い
float (単精度)浮動小数点型
double 倍精度浮動小数点型
long double 長い倍精度浮動小数点型 void 何もない型
enum 列挙型
また,char, short int,int,long intに対しては,unsignedを前につけると,それぞれ符号無しの型を 表し,signedをつけるとそれぞれ符号つきの型を表す. 何もつけない時は,short,int,longは符号つき
10[2, 2.4]によれば,次のように書かれている: 外部変数,静的変数はゼロに初期化される. 明示的な初期化式がない自動変数は不
定(ゴミ)の値を持つ. (External and static variables are initialized to zero by default. Automatic variables for which there is no explicit initializer have undefined (i.e., garbage) values.)
11void,enum,long doubleはANSIの規格ではじめて定義された. Kernighan-Ritchieの初版[1]では定義されていない.
Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp
であると解釈される. しかし, charに関しては,どちらになるかは処理系依存である. 例えば, 今回使用す るgccの場合は charはsigned char である.
6.8.3.1 const 修飾子
変数の型に constという修飾子をつけると, 初期化はできるが, プログラム中で変更のできない定数と して扱うことができる. 例えば,次のように宣言する.
const int a=0 ; float const b=1.0 ;
const宣言をした変数を変更した時の振る舞いは不定である12 .
Remark 6.8.1 このremarkは非常に高度で面倒な内容を含んでいるので,興味のない人は無視してもよ い. また,ここでのプログラム断片は表示を少なくするため, あまりきれいな形にはなっていない.
実はconst宣言をした変数の扱いが非常に厄介で,以下のようなプログラム断片を調べてみよう.
int n ;
const int cn ; cn = n ; n = cn ;
この中で代入が許されるのはどの場合かを考えてみる. 当然 n = cnは許される. しかし, cn = nは gccの場合に は,assignment of read-only variable ‘cn’という警告が出される. SunのCコンパイラでは,left operand must be modifiable lvalueというエラーとなる.
しかし,次の例はどうだろうか?
char *cp ;
const char *ccp ; ccp = cp ; cp = ccp ;
こちらは ccp = cpが許され,cp = cpp の代入では, gccではassignment discards qualifiers from pointer
target typeという警告が出される. これでは何を言っているかわからないので, SunのCコンパイラに通してみる
と,assignment type mismatch: pointer to char "=" pointer to const charという警告が出る.
まず,cpp = cpが許される理由を考えてみよう.実は,const char *という型指定は,「const charへのポイン タ」という意味であり,ccp自身をconst宣言しているのではなく,ccpが指し示すオブジェクトがconstと言って いるのである(cf. [2, A.8.6.1]). したがって,次のような例は警告対象となる.
const char *ccp="abc" ;
*ccp =’b’ ; しかし,
char cp[4] ;
const char *ccp="abc" ; ccp = cp ; *cp =’b’ ;
のように,一旦const修飾子がついていないオブジェクトを経由して,const 宣言を行ったオブジェクトへのアクセ
スを行うことは,文法上問題は発生しない. しかし,const修飾子は,「読み出し専用」のメモリ領域にオブジェクト を配置して良いことをコンパイラに知らせるという役目も持ち,そのような場合も含めて,この例の結果は不定である と考えるべきである. なお,constポインタを宣言するには,char *const ccpとする. すなわち,
char *const ccp="abc" ;
とすれば,ccp = cpといった代入が許されなくなる.
また,cp = cppが許されない理由は,型の適合性の問題にある. 単純代入が許される条件の一つとして,次の条件が
規定されている. (cf. [3, X3010 6.3.16.1, p. 1890])
• 両オペランドが適合する型の修飾版または非修飾版へのポインタであり,かつ左オペランドで指される型が右オ ペランドで指される型の修飾子をすべて持つ.
12より正しくは,「const修飾型を持つオブジェクトを,非const修飾型の左辺値を使って変更しようとした場合,その動作は未 定義とする.」というのがANSIの規定.
Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp
cp = cppは右オペランドで指される型の修飾子 constを左オペランドで指される型が持たないため,この条件に違 反し,他の単純代入の条件にも合致しないため,文法エラーとなる.
さらに,次のような例もある. (cf. [6, p. 48]) int foo (const char **p) {}
int main(int argc, char **argv) { foo(argv) ; }
この例では, gccの警告はpassing arg 1 of ‘foo’ from incompatible pointer typeとなる.これは,関数foo の仮引数 const char **pが「const 修飾された char 型変数へのポインタのポインタ」であり,実引数 argv は
「char型変数へのポインタのポインタ」である. そこで, ANSI規格6.3.2.2 を見てみよう. (cf. [3, X3010 6.3.2.2,
p.1876])そこには,関数呼び出しの制約として,「各実引数は,対応する仮引数の型の非修飾版を持つオブジェクトに
その値を代入できる型を持たなければならない」と書かれている. つまり,引数を渡すと代入が行われ,実引数と仮引 数は代入が許される関係になければならないということである. したがって,
int foo (const char *p) {}
int main(int argc, char **argv) {
char *q ; foo(q) ; }
という例であれば,p = q という代入が行われることに相当し,上で述べた単純代入の規約を満たす. しかし,const char **の例では,仮引数pは「const char *へのポインタ」であり,実引数argvは「char *へのポインタ」で あるため,単純代入の規約を満たさない.
なお,char *を仮引数とする多くの標準ライブラリ関数(例えば,strcpyなど)は,仮引数としてconst char * を宣言している. これは,関数内で明示的に仮引数の指し示す値を変更しないための措置である.
6.8.3.2 変数と記憶領域
変数は宣言と同時に対応する記憶領域が確保される. しかしながら,それぞれの型に対して,どれだけの 記憶領域が確保されるかは処理系依存である.
それぞれの型がどれだけの記憶領域をとるかを調べるには, sizeof演算子を使う. sizeof演算子の利 用法は以下の通りである.
sizeof (型) ;
sizeof オブジェクト ;
ここで,その結果として得られる値は,符号なし整数で表現され13 ,その意味は,char型の何倍の記憶領 域が確保されるかを表す. 即ち,sizeof(char)の結果は処理系によらず1である.
それでは, char 型がどれだけの記憶領域を使うかを知るには, どのようにすれば良いのだろうか. それ には, C言語の標準的なヘッダ・ファイルを見れば良い. 実際, limits.hに定義されているCHAR BITと いうマクロ14の値が char型のビット数である. Sun Sparc StationのCコンパイラの場合,
#define CHAR_BIT 0x8
となっているので,char型は8ビット(1バイト)であることがわかる.15 また,int型の長さは,その計 算機の自然な長さであると定義されている.
それぞれの変数が記憶領域に確保される時,宣言した順序で記憶領域内に確保されるという保証はない.
また,多くの処理系では,int,longはワード境界にアロケートされる.
Example 6.8.1 intが2バイト,long が4バイトの処理系で, char c ;
int n ; long l ; char d ;
13正確にはsize t型で表現される.size t型がどの型に対応するかは処理系依存である.
14マクロの意味は後日解説する.
15ANSIの規格書によれば,char型の占めるビット幅を1バイトと定義している.
Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp
と変数を定義した場合,下の図のいずれのメモリ配置をとるかは処理系や最適化に依存する. これ以外の取 り方をする可能性もある.
16 bits c (padding) n l d
16 bits
c d
n l
16 bits
c n
n(続き)l d
(a) (b) (c)
この中で(c) のメモリ・アロケーションはアライメント(alignment, 境界調整)に適合していない環境が 多いため,ほとんどこのようなアロケーションは行われない.
Example 6.8.2 変数に値を代入する操作とは,変数に対して与えられたメモリに数値を書き込むことに他
ならない. 例えば, (intが16ビットの場合)
int n ; n = 1 ; とすることは,
16 bits
n 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 16 bits
n =⇒ または
16 bits
n 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 8 bits
と値を代入することになる. 下のように上位バイトと下位バイトを入れ替えて数値を表現する処理系(CPU) をbig endian,上のように数値を表現する処理系(CPU)をlittle endianと呼び, 8080, Z80などのIntel
社のCPUはbig endianになっていることが多く, 68000, SPARCなどのCPUは little endianになって
いる.
6.8.3.3 文字型と整数型
文字型(character type)とはその名の通り,(1)文字を変数として扱う型である. 例えば,
char c ; c = ’a’ ;
とすると, 変数cにはa という文字が代入される. 文字型では, その中身を文字の持つコードの値とし て扱う. したがって, 文字型変数の実体は“整数”と思って良い. (しかしながら, char型を文字として扱 う時には,常に正の数値として扱う.)その意味で, charは整数型(integer type)(short, longなども 含む)の一部として考えると都合が良いことが多い.
Example 6.8.3 例えば, ASCII コード体系の処理系で, char c ;
c = ’a’ ;
とすると,cには16進整数値0x60 が入り,整数値0x60 として計算や比較が行われる. したがって, Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp
char c, d ;
c = ’a’ ; d = ’A’ ;
の時,c + dは16進数値0x40 + 0x60 =0xA0 となる. また, char c ;
c = 0x60 ;
とすると,cには’a’が代入されたこととなる.
それでは,整数型がどれだけの記憶領域を使うかを考えてみよう. C では,short,int,longなどの記憶 領域については,全て処理系に依存していると定義されているが,次の関係だけはCの定義にある.
char≤short≤int≤long
ここで,char≤shortという意味は,short型の記憶領域はchar 型のそれよりも短くないという意味で ある. また,short,int型は最低16ビット,long型は最低32ビットが保証されている.
実際,それぞれの整数型の長さ(sizeofの返す値)を見てみると,今回利用する処理系では,次のように なる.
short 2
int 4
long 4
これで,char 型が1バイトであることを使うと,intは4バイトであることがわかる.
6.8.3.3.1 整数の内部表現 整数型の変数の内部での表現は, Section 2.3.2で述べた表現がとられている ことが多い. ANSI規格では整数型の内部表現の具体的な方法については何も規定していない.
6.8.3.4 浮動小数点型
浮動小数点型 (floating type)についても, Cでは以下のことしか定義されていない.
float≤double≤long double
実際,それぞれの浮動小数点型の長さ(sizeofの返す値)を見てみると,今回利用する処理系では,次の ようになる.
float 4
double 8
long double 8
これで,char 型が1バイトであることを使うと,float は4バイトであることがわかる.
(10を底とする)浮動小数点数とは,以下のような型で表現された数のことである.
(0でない1桁の数).<仮数部> × 10^<指数部>
ここで,任意の 0でない実数は,このような表示が可能であることに注意せよ. (もちろん,その表示は有 限小数と仮定すれば一意的である.)
6.8.3.4.1 浮動小数点数の内部表現 浮動小数点数の変数の内部での表現は, Section 2.3.2で述べた表現 がとられていることが多い. ANSI規格では整数型の内部表現の具体的な方法については何も規定してい ない.
オーバーフロー,アンダーフローをした数の演算,比較等の結果がどうなるかは規格では定められていな い. しかし,それぞれの処理系によって規定されている.
Id: C3.tex,v 1.17 2001-03-20 15:15:41+09 naito Exp