C メモ
投稿日: | |
---|---|
タグ: |
目次
よくCの開発者本の特定のページを開くので,今回まとめてみた.ついでによく質問される内容もメモ.
型変換(キャスト)
Cでは,型によって利用可能な演算やその振る舞いが異なる. 例えば次のようなint型のaに対しint型のbを加算する場合,aには5が加算される.
int a = 5;
int b = 10;
a += b;
しかし次のようにint型のポインタのaに対しint型のbを加算する場合,aには,aのポインタが指すint型のサイズに5を掛けた値が加算される.
int c;
int *a = &c;
int b = 5
a += b;
例えばint型が4バイトの時,aに20加算される.
また,Cでは関数の多重定義もできないため,関数の返り値やそれに与える引数の型も関数毎に固定される.
これらのような場合,Cでは,プログラマが型変換演算子を使うことによって明示的に,あるいはコンパイラによって暗黙的に型変換を行う. 型変換とは言葉通りある型のオブジェクトを別の型のオブジェクトに変換することである. 変換後のオブジェクトは,変換前のオブジェクトとはサイズも値も異なる. 例えば,次のようにfloat型の変数をint型の変数に型変換して代入する場合,float型は小数を切り捨てint型に格納できるように変換される.
int i;
float f = (float)1.5;
i = f;
この変換は見た目ほど単純ではない.
なぜならプロセッサ内部でデータを保持するための表現方法が同じとは限らないからである.
例えば,プロセッサがint型を32ビットの2の補数表現,float型をIEEE754の32ビット表現で保持する場合,
float型の1.5は「00111111110000000000000000000000」,1.0は「00111111100000000000000000000000」になる.
これに対し,int型の1は「00000000000000000000000000000001」で表現される.
そのためこの場合,見た目上の値の変換だけでなく,変換後の値を変換後の表現方法で表現しなければならない.
また,この例ではintとfloatは共に同じサイズであるが,型変換は異なるサイズへの変換も行われる.
整数型から整数型への変換
char型やshort型,enum型のオブジェクトは,整数が利用可能な演算式で利用しても良い. その場合,int型で表現可能な時はint型に,そうでなければunsigned int型に変換される. これを整数への格上げと呼ぶ.
変換前の値が負でなく,かつ変換後の符号が無く型サイズが等しいか大きい場合,値は変わらない. 型サイズが小さい場合,余分な分だけ上位ビットを切り捨てる(変換後の型で表現可能な値+1で割った余り). 変換前の値が負の場合,変換後のサイズが小さければ,変換前のビット列を上位ビットを切り捨てられ,変換後のサイズが大きければ,ゼロ拡張か符号拡張される. このように計算されたビット列が変換後の値となるため,変換前後の値は大きく異なる.
変換後が符号付きの場合,変換後の型で表現可能であれば値は不変である. 例えば,「変換後の型サイズが大きい場合」や,「変換前後とも符号付きで型サイズが等しい場合」が挙げられる. また,型サイズが小さい場合であっても変換後で表現可能であれば,値は変わらないが,そうでない場合は処理系に依存する.
整数と浮動小数
浮動小数型から整数型へ変換する場合,小数部は無視される. この時,結果が整数型で表現できない場合のふるまいは不定である. 一例として,負の浮動小数値から符号無し整数値への変換の結果は処理系に依存する.
整数型から浮動小数型へ変換する場合,その値が表現可能な範囲であればその値か,処理系が決めるそれに最も近似な値になる. 表現可能な範囲でない時,ふるまいは不定である.
浮動小数型から浮動小数型への変換
精度の低い浮動小数型から高いまたは同じ浮動小数型へ変換される場合,その値は変わらない.
精度の高い浮動小数型から低い浮動小数型へ変換される場合,表現可能であれば表現し,表現可能な範囲であれば,処理系が決めるそれに最も近い値になる. 表現可能な範囲でない時,その結果は処理系に依存する(丸め).
ポインタと整数
ポインタは,それを保持するのに十分大きい整数型に明示的に変換して良い. 必要とされるサイズは,処理系に依存する.
整数型は,ポインタに明示的に変換できる.
ある型へのポインタは,別の型へのポインタへ変換できる. その結果できるポインタの先のオブジェクトが正しく整列されていない場合,処理系によってはアドレス割り込みを発生させる.
ある関数型へのポインタは,別の関数型へのポインタへ変換できる. ただし,変換されたポインタで指される関数の呼び出す場合の振る舞いは処理系に依存する.
void
voidから異なる型への変換は行うことができない.
また,ある型からvoidへの変換は可能であるが,それは変換後の値を捨てることを意味する.
void*
オブジェクトへの任意のポインタは,情報を失うことなくvoid*(voidポインタ)へ変換,または逆変換できる.
算術変換
特定の異なる型の演算では,暗黙的に型変換が行われる(算術変換). この時,それらの項の型から,全ての項の値を表現可能な型が選ばれる. 例えば,いずれかの項が浮動小数型の時,残りの型もその型と同じになる. また,整数型同士や浮動小数型同士の場合,大きい型に変換される. ただし,整数型同士か浮動小数型同士であり型のサイズも同じであるが符号の有無が異なる時,符号の有る項の型が,符号の無い項の値を表現できるならば,符号の有る項の型が選ばれる. 表現不可能な場合,符号のある項の型の符号を無しにした型に変換する.
型宣言
型宣言は左から右に結合され,次の優先順位(数字が低いほど優先順位が高い)に従って読む.
- ()
- []
- *
- 型
C言語 | char *argv[] |
---|---|
英語 | argv is an array of pointers to char |
日本語 | argvはchar型へのポインタの配列 |
また,複雑な宣言の例もいくつか示す.
C言語 | 英語 | 日本語 |
---|---|---|
char (*p)[] | p is pointer to an array of char type objects | pはchar型のオブジェクトの配列へのポインタ |
char p[5][10] | p is an array of 5 arrays of 10 char type objects | pはchar型のオブジェクトを10個持つ配列を5個持つ配列 |
int p() | p is a function returning an int type object | pはint型のオブジェクトを返す関数 |
int *p() | p is a function returning a pointer to an int type object | pはint型のオブジェクトへのポインタを返す関数 |
int (*p)() | p is a pointer to a function returning an int type object | pはint型のオブジェクトを返す関数へのポインタ |
int (*p[5])() | p is an array of pointers to a function returning an int type object | pはint型のオブジェクトを返す関数へのポインタの配列 |
int (*p[5])[10] | p is an array of 5 pointers to an array of 10 int type objects | pは10個のint型のオブジェクトを持つ配列への5つのポインタの配列 |
整数
整数リテラルは,8進数表記は0で,16進数表記は0xで始める. 整数リテラルの型は,接尾子によって決定する. long型の場合,接尾子にlかL,unsignedの場合,接尾子にuかUが付く. また,接尾子なしの10進数の場合,次の候補の中の表現可能なものから,優先順位によって決定する(数字が低い方が優先度が高い).
- int
- long int
- unsigned long int
- int
- unsigned int
- long int
- unsigned long int
文字
文字リテラルは1つの文字を'で括って表す(例:'a'). 文字は整数型として保持される. そのため,処理系は文字リテラルを'で括った文字の文字コードとして扱う. Cの開発者本によると,その値は非負である. また,多くの場合文字セットはASCIIコードである. ただし言語依存ではないため,'A'を65(ASCIIコードの'A')と記述する書き方は,移植性を低下させる.
拡張文字の文字リテラルは,頭にLを付ける.
一般的に文字はchar型で保持する. char型はANSI C規格で1バイトと規定されており,処理系で実装されている文字セットの任意の文字を格納するだけの大きさを持つ. char型は整数型であるため,文字リテラル以外も格納できる. ただし格納された値が符号付きか否かは処理系に依存する. 明示的に符号を付ける場合はsigned修飾子,符号なしにする場合はunsigned修飾子を付ける.
文字列
文字列リテラルは,連続する文字を"で括って表される. また,文字列リテラルの実体は,char型オブジェクトの配列で,記憶クラスはstaticである. 同じ内容の文字列リテラルが区別されるかどうかは処理系に依存する. また,文字列リテラルを変更しようとした場合の振る舞いは不定である.
浮動小数
浮動小数リテラルの値は,接尾子がfかFならばfloat型,lかLならば,long double型,それ以外はdoubleを表す.また,eを付けることで指数を指定できる.例えば"1e2"は100,"1e-2"は0.01を表す.
なお浮動小数は2進数と10進数での誤差がある.それゆえ例えば"(0.1+0.1+0.1)==0.3"という比較演算は,10進数で考えると一致することを期待するが,2進数では一致しない.
演算子の意味
よく誤解される演算についてのメモ.>, >=, ==, !=, <=, <
それぞれ比較しその条件に合えば1を返し,それ以外は0を返す.
&&, ||
&&は二項のいずれかが0の時0を,それ以外は1を返す. ||は二項のいずれかが0でない時1を,それ以外は0を返り値とする.
/, %
両方の項が正である時,余りは正で除数より小さい. それ以外の時,余りの絶対値が除数の絶対値より小さいことのみが保証される.
>>, <<
シフトにはいろいろな種類がある. 一般的なものには,算術シフトと論理シフトがある. これらは左シフトの場合同じ振る舞いだが,右シフトの時,項が負の時振る舞いが異なる. 論理シフトの場合指定された分だけ値を右にシフトし,ゼロ拡張するが,算術シフトの場合符号拡張する.
ANSI Cでは,符号無しや正の整数に対する右シフトの結果はどの環境でも同じである. しかし負の値をシフトする場合の振る舞いは,処理系に依存する.
また,左シフトか右シフトに関わらず,シフト量が負の場合の結果も処理系に依存する.
型や修飾子の省略
Cでは,特定の型や修飾子が省略できる.
- 関数内の変数を自動(auto)変数にするか静的(static)変数にするか.省略した場合,自動変数として扱われる.
- 符号の有無(signed/unsigned).省略した場合,符号有り変数として扱われる.
- 変数や関数の型.省略した場合,int型として扱われる.ただし変数の場合全て省略すると文法エラーになるかもしれない.
名前
変数や関数に利用可能な名前は,1文字目が英字かアンダースコア(_)で,2文字目以降が英字かアンダースコア(_),数字のいずれかである1文字以上の文字列. 付けられる名前の字数に制限はないが,ANSI C規格では,区別されるのは最初の31文字までである.一般的に大文字と小文字は区別される.ただし実際には処理系によって区別される文字数や大文字小文字を区別するかは異なる.
型のサイズ
ANSI Cの規格では,char型が1バイトであることが定められており,他の型のサイズについては処理系依存である. また,バイトとは各プロセッサの処理の最低単位であり,具体的なビット数は決まっていない(多くのマシンで8ビットだが,明確な定義でない). intやchar型などの基本的な型の最大で表現できる値は,limits.hで定義されている.
その他
- 関数に引数を与える際,引数が評価される順は不定である.
- 各演算式の項も評価される順は不定である.
- 構造体のサイズは各メンバの型のサイズの和になるとは限らない(コンパイラによっては,実行の最適化のため各メンバの間を整列しアドレスをある区切りでそろえる場合があるため).この時できる使われていない空間をパッディングと呼ぶ.
- Cでは,真偽を表す型がなく,0が偽,それ以外が真として扱われる(というか真とか偽とは言わない).
- Cに"else if"は存在しない.これは,単に2つのifを組み合わせているだけ.
- 開発者本(ANSI C準拠)によると,コンパイラはifの中に複合文を使わず入れ子でifを書いたような場合,elseを(elseの手前の)一番後ろのifに関連付ける(曖昧なので普通は中括弧で括る).
- switch文はswitch(条件)の後に文がくる.そのため別にラベル付き文が来なくても文法的に正しい(意味はないが).
- const付きのポインタはポイント先の値を変更できない.
- 修飾子volatileを付けた変数はコンパイラによる最適化を抑止される.volatileは組み込みやマルチスレッドのプログラミングなどで使用し,最適化によりアルゴリズムが変更されてしまうのを防ぐ.