第
4
章C
言語入門4.1
ここでの目的ここではC言語の文法を中心に解説をするが,単にC言語を用いてプログラムが書けるようになること が目的なのではなく, C言語の言語構造を理解し, C言語の特徴である移植性の高いプログラムを書けるよ うにすることが重要である.
またプログラムは,自分自身だけが読むものではなく他人にも読めるように,わかりやすく簡潔に書くべ きである. これらのことを念頭において学ぶことを期待したい.
4.2 C
言語をはじめる前にC言語によるプログラミングをはじめる前に, 後の混乱を避けるための注意をする. 以下の注意書きは, 無用なトラブルを回避するためできる限り守った方がよい.
• 各ファイルには,その中身がわかるような簡潔なファイル名をつけること.
• 各プログラム毎にサブデ ィレクトリを作成することが望ましい.
• test.cなど,既存のプログラム名に.cを付加したファイル名は避けること.
• 不要になったファイルは削除すること.
多くのプログラムソースを書いた時に, それぞれのファイルがどのような内容のものかがわからなくなる ことが良くある.
また,プログラムソースを見やすくするために,「字下げ 」をきちんとすること. MuleでXXX.cという ファイルを編集する際の「字下げ 」の方法は各行頭で1223Tabを押すことにより行なう. これだけでMuleが 状況に応じて適当に処理してくれる1 .
4.3 C
のプログラムの書き方・実行の方法講義で利用する処理系は gccとよばれるコンパイラである. 実行コード を作成するには以下の手順で行 なう.
1. エデ ィタ(Muleなど)を利用して,プログラムソースを書く.
2. コンパイラを起動して,実行コード を作成する.
3. 作成した実行コード を実行する.
例えば, Muleを利用して,hello.cというプログラムソースを作成した時,これをコンパイルするには,以 下のようにする.
% gcc hello.c -o hello
最後の-o helloという部分は,実行コード をhelloという名前で作成することを指示している. もし,-o
helloという部分を省くと,コンパイラはa.outという名前で実行コード を作成する.
1 これはmuleの“c-mode”の特徴である.
ここで作成したhelloを実行するには
% ./hello
とする. ここで,./をわざわざ指定していることに注意せよ2. Exercise 4.3.1 test.cというプログラムを
% gcc test.c -o test として,作成して,
% test
とすると,どのようなことが起こるか. それは何故か?
より高度なプログラムを書く場合には,複数のファイルからなるプログラムを作成することがある. その ような場合には,一度にコンパイルすることはできないので,それぞれのファイルをコンパイルして,リン クと呼ばれる操作で実行コード を作成する.
4.4 C
言語の基本的な注意4.4.1 C Program
の基本的な構造Cのプログラムは,コメント(注釈),プリプロセッサ文,文,関数の集まりで構成されている. それぞれ の文は, ; で終るか,{ } で囲まれた文の集まりである複文で構成されている. 関数自身もまた文である.
実際には,main という名前の特別な関数がはじめに実行され,そこに記述されている順序にしたがって実 行される.
4.4.2 C
で利用できる文字Cでは, 英文字,数字,空白文字(スペース, タブなど),記号文字,改行文字などが利用できる. 記号文 字には特別な意味があることが多いので,注意すること. またCでは,大文字と小文字は区別される. Cの コンパイラにおいて,日本語が利用できるといっても,変数名などに日本語が利用できるわけではないので 注意すること. 日本語が利用できるのは,文字列に日本語が利用できるという程度の意味であり,今回利用 する処理系では,このコード はEUCでなければならない.
また,以下のものは全て空白と見なして無視される.
• 空白文字,改行文字,タブ, 改ページ記号,コメント.
行末に\を書くと, 行の連結を表し, 1行として扱われる.
4.4.3
コメントコメントとは,プログラミングの補助となるようソースプログラム中に書かれた注釈部分のこと. コンパ イラは単にこれを無視するので,プログラムには影響を与えない. コメントは,/* ではじまり,*/で終り, 入れ子にはできない. 即ち,コメントの中にコメントを入れることはできない.
また,文字定数,文字列の中にはコメントを書くことはできない.
2 UNIXではカレントディレクトリはデフォールトではコマンド サーチパスには入っていないし,入れない方が望ましい.
Id: C1.tex,v 1.2 2000/04/21 02:46:42 naito Exp
4.4.4
ト ークントークンとは,空白やコメントによって区切られた文字の列で,コンパイラが認識する最小の単位である.
トークンは以下のいずれかに分類される.
• 演算子 (+ - など)
• デリミタ(区切り子)({ } ( ) ; など)
• 定数(整数,浮動小数点数,文字定数)
• 文字列リテラル
• 識別子
• 予約語
4.4.4.1 定数
C 言語における定数は整数定数, 浮動小数点定数, 文字定数に分類される. 整数定数は, 8進数, 10進 数, 16進数による表現が可能である. それぞれを区別するには,以下の規約による.
• 16進数:0x で始まり,後に0〜9, a〜fがいくつか続く. a〜fとxは大文字でも良い.
• 10進数:0以外ではじまり,後に0〜9がいくつか続く.
• 8進数 :0 で始まり,後に0〜7がいくつか続く.
0x10を 0x0010と書いても良い.
また, 整数定数に u または U をつけると, 符号なしの数を表し, l または L をつけると “長い” 整数 (long)を表す.
浮動小数点定数は,以下の形をしている.
整数部 . 小数部 e 指数 接尾子
“e 指数”部分は省略することができる. 接尾子は以下のいずれか.
• fまたはF:float型
• 接尾子なし: double型
• lまたはL:long double 型
文字定数とは,’で括られた文字の列である. 例えばaという文字を表すには’a’と書く.
以下の特別な文字を表す以外には,文字定数は一文字でなくてはならない.
• \n改行
• \r復帰
• \f改ページ
• \t水平タブ
• \v垂直タブ
• \b後退
• \aベル
• \? ?
• \’ ’
• \" "
• \\ \
• \ooo 8進数でooo. 3桁以下
• \xhh 16進数でhh. 2桁以下
このような特別な文字のことをエスケープ文字と呼ぶ.
Id: C1.tex,v 1.2 2000/04/21 02:46:42 naito Exp
4.4.4.2 文字列リテラル
文字列定数とも呼ばれ, " で囲まれた文字の列である. 文字列リテラル3 が記憶域に格納される時には, 末尾に \0が付けられる. また,隣接する2つ以上の文字列リテラルは連結される. 例えば "abc" "ABC"
は"abcABC"となる.
4.4.4.3 識別子
識別子とは,変数,関数などに付けられる名前のことである. ここで与えられた名前にしたがって, コン パイラはそれぞれを区別する.
識別子として使える文字は,英文字, 数字, _であって,数字を先頭にすることはできない. また, C言語 の規約によって, 31文字までは区別される. 即ち, 32 文字目以後が異なるような2つの名前は区別される とは限らない.
また, C 言語には名前空間,スコープという概念があり, 同じ名前を与えても,違うものを示しているこ とがあるので注意すること. これについては後ほど解説する.
4.4.4.4 予約語
以下の単語は予約語と呼ばれ,特別な意味を持ち,識別子としては利用できない.
char double int long enum float short
unsigned signed void typedef auto register extern static volatile union struct const sizeof if
else switch case default break return for
while do continue goto
4.4.5
文文とは, C言語のプログラムの基本的な単位になるもので,それぞれの文は; によって終了する. 一つ の文が複数行にわたっても良い.
Example 4.4.1 次の各行は全て文である.
int n ;
printf("Hello World.\n") ; x + y ;
a = b ;
;
最後の行は空文と呼ばれる.
また,{,}によって文の集まりを一つの文(複文)にすることができる.
Example 4.4.2 次は一つの複文である.
{
a = b ; c = d ; }
3「 リテラル」(“literal”)とは,「文字通りの」という意味である.
Id: C1.tex,v 1.2 2000/04/21 02:46:42 naito Exp
4.4.6
式式とは,計算を行なう最小単位のことで,それぞれの式は値を持つ. その値は,次のようにして決まる.
• 計算式の場合は,その結果.
• 代入式の場合は,その左辺の値.
• 関数の場合は,その戻り値.
• 比較の場合,真ならば1,偽ならば0.
式に; をつけることにより,文にできる. それを式文と呼ぶ.
Example 4.4.3 次のようなものは式文である.
a ; /* 値は a */
x + y ; /* 値は x + y */
a = b ; /* 値は代入された a の値 */
printf("Hello World.\n") ; /* 値はこの関数の戻り値 */
a = b = c ; /* 値は代入された a の値 */
a = b, c = d ; /* 値は最後に代入された c の値 */
a < b ; /* 値は真ならば 1, 偽ならば 0 */
4.5
処理系依存と不定Cにおいて,処理系依存とは,その処理系ごとにどのように振舞いかが規定されているものである. しか しながら,コンパイラのオプションの与え方によって, その振舞いがことなることがあるが,その場合にも オプションごとに振舞いは定義されている.
一方,不定とは,同じ処理系であっても,その振舞いが規定されていないものを指す. 特に,最適化(オプ ティマイザ(optimizer))の指定によって振舞いが変わることが多い.
4.6
最も簡単なプログラムはじめに最も簡単と思われるプログラムを書いてみよう.
プログラムを実行すると画面に何かを表示するものである.
Example 4.6.1 もっとも簡単なプログラムの例 /* Program 1
* Hello World. を出力する.
*/
#include <stdio.h>
int main() {
printf("Hello World.\n") ; return 0 ;
}
以下では,このプログラムの内容を説明する. (ただし,コメント,空白行は行数として数えない.)
Id: C2.tex,v 1.1 2000/04/20 12:32:11 naito Exp
1行目
#include <stdio.h>
#ではじまる行はプ リプロセッサと呼ばれるものによって処理される.
Cコンパイラは,実際には以下の手順によって実行される.
1. プリプロセッサによる前処理.
2. コンパイラによるオブジェクト・コード の作成.
3. リンカによるオブジェクト・コード とライブラリの結合.
C言語のプログラム中に,#ではじまる行があらわれると,プリプロセッサはその文法にしたがって,コー ド を書き換える. 実際,#include という指示は,これに続くトークンで指示されたファイルを,その位置
に挿入する命令である.
C言語では,原則として全ての関数は,定義されたり,利用される前に宣言されなくてはならない. そこ で, 標準的な関数(このプログラムではprintf)を使うためには, その宣言が書かれているファイル(こ
こではstdio.h)を挿入することによって,その関数の宣言をする. このような(標準関数の)宣言が書か
れているファイルのことをヘッダ・ファイルと呼ぶ.
また,ヘッダ・ファイルの挿入には
#include <XXXX.h>
#include "XXXX.h"
の2つの書き方がある. コンパイラの実装によって決まる標準的な場所4 にあるファイルを挿入するには前 者の方法を使い,カレント・デ ィレクトリにあるファイルを挿入するには後者の方法を使う.
どのような関数が,どのヘッダ・ファイルで宣言されているかはオンライン・マニュアルを見ればわかる.
2行目
int main()
これはmainという関数の定義である. この関数の本体は3行目の{と6行目の}に囲まれた部分である.
この部分は次の3つの部分に分解される.
• int
• main
• ()
はじめのintは, この関数の戻り値が int型であることを示す. 関数の戻り値が書かれていない時には, コンパイラはintであると解釈する.
次のmainは関数の識別子である.
最後の ()の中には,( 存在すれば)その関数の引数が書かれる. 引数をとらない場合にも () または (void)と書かなくてはならない.
4行目
printf("Hello World.\n") ;
ここで利用されたprintf という関数は,その引数として,文字列リテラルをとり,その文字列リテラルを 標準出力に出力する.5 ここで,最後の;によって,この一行が文になっていることを注意せよ.
本来,この関数には戻り値が存在するが,ここではその戻り値は利用していない.
4 これは,コンパイル時のオプションで変更可能
5 ここの解説は本当は正しくない。この関数はもっと多くの引数をとり、最初の引数も文字列リテラルである必要はない.
Id: C2.tex,v 1.1 2000/04/20 12:32:11 naito Exp
5行目
return 0 ;
returnという文は次の形でなくてはならない.
return 式 ;
式の部分には,どのような式を書いても良い. ここでは,単に0という式を書いている.
この文は, main 関数の戻り値を与えている. main 関数の戻り値はプログラムを実行したシェルに返さ れる.
Exercise 4.6.1 Program 1を真似て,次のような出力を得るプログラムを書け.
各自の学籍番号 (改行) 各自の名前 (改行) 何でも好きなこと (改行)
Exercise 4.6.2 printf という関数の戻り値は,出力した文字数である. mainの戻り値として,出力した 文字数を返すようにProgram 1 を変更せよ. ただし,変数を用いてはならない. シェルに戻された戻り値 は, cshの場合は
% echo $status
を実行することで得ることができる。
4.7
変数とは変数とは,識別子によって区別された初期化,代入などが許される記憶領域のことである. C 言語の変数 には,多くの型があり,それぞれの型によって,どれだけの記憶領域が確保されるかが異なる. また, C言語 の変数には記憶クラス,スコープ,寿命, リンケージなどの概念があるが,それらについては関数,分割コン パイルの後で述べる. ここでは,変数の宣言,型などについて考える.
4.7.1
変数の宣言C言語では全ての変数は使う前に宣言しておかなくてはならない. 宣言は変数の性質を告げるためのも ので,
int step ;
int lower, upper ; float x ;
のように,型の名前と変数(の識別子)の名前のリストからなる.
これらの宣言を色々な場所に書くことで,それらの変数の意味が変わるが,ここでは,次のように,どのブ ロックにも含まれず,すべての手続きの前に書くことにする. (下の例を参照.)
#include <stdio.h>
int step ;
int lower, upper ; float x ;
main() { }
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
4.7.2
変数の初期値Cでは変数は,定義されただけでは値は定まらない(と考えた方が良い)6 . そのため,(必要なら)そ の変数を使う前に初期化を明示的に行なう必要がある.
変数の初期化の方法には2通りある.
int a=0, b ; main() {
b = 0 ; }
このように,宣言と同時に初期化することもできる. aの初期化は実行時にただ一度だけ行なわれるが, bの場合は,この文を実行されるたびにbに0が代入される.
C においては, 変数の宣言と定義は異なり, 宣言だけではメモリ領域が確保されない. これに関しては, extern宣言を参照.
4.7.3
変数の型Cで定義されている変数の型は以下の通りである.7 変数の型 型の名前
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は符号つき であると解釈される. しかし,charに関しては,どちらになるかは処理系依存である. 例えば, 今回使用す るgccの場合はcharはsigned char である.
4.7.3.1 const 修飾子
変数の型に constという修飾子をつけると, 初期化はできるが,プログラム中で変更のできない定数と して扱うことができる. 例えば,次のように宣言する.
const int a=0 ; float const b=1.0 ;
const宣言をした変数を変更しようとすると,文法エラーとなる.
6 [3, 2.4]によれば,次のように書かれている:外部変数,静的変数はゼロに初期化される.明示的な初期化式がない自動変数は不
定(ゴミ)の値を持つ. (External and static variablesare initialized to zero by default. Automatic variablesfor which there isno explicit initializer have undefined (i.e., garbage) values.)
7 void,enum,long doubleはANSIの規格ではじめて定義された. Kernighan-Ritchieの初版[2]では定義されていない.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
4.7.3.2 変数と記憶領域
変数は宣言と同時に対応する記憶領域が確保される. しかしながら,それぞれの型に対して,どれだけの 記憶領域が確保されるかは処理系依存である.
それぞれの型がどれだけの記憶領域をとるかを調べるには, sizeof演算子を使う. sizeof演算子の利 用法は以下の通りである.
sizeof (型) ;
sizeof オブジェクト ;
ここで,その結果として得られる値は,符号なし整数で表現され8, その意味は,char型の何倍の記憶領 域が確保されるかを表す. 即ち,sizeof(char)の結果は処理系によらず1である.
それでは, char 型がどれだけの記憶領域を使うかを知るには, どのようにすれば良いのだろうか. それ には, C言語の標準的なヘッダ・ファイルを見れば良い. 実際, limits.hに定義されているCHAR BITと いうマクロ9 の値がchar型のビット数である. Sun Sparc StationのCコンパイラの場合,
#define CHAR_BIT 0x8
となっているので,char型は8ビット(1バイト )であることがわかる.10 また,int型の長さは,その計 算機の自然な長さであると定義されている.
それぞれの変数が記憶領域に確保される時,宣言した順序で記憶領域内に確保されるという保証はない.
また,多くの処理系では,int,longはワード 境界にアロケートされる.
4.7.3.3 文字型と整数型
文字型とはその名の通り,(1)文字を変数として扱う型である. 例えば, char c ;
c = ’a’ ;
とすると, 変数cにはa という文字が代入される. 文字型では,その中身を文字の持つコード の値とし て扱う. したがって, 文字型変数の実体は“整数”と思って良い.(しかしながら, char型を文字として扱 う時には,常に正の数値として扱う.)その意味で,char は整数型(short,longなども含む)の一部とし て考えると都合が良いことが多い.
それでは,整数型がどれだけの記憶領域を使うかを考えてみよう. 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バイトであることがわかる.
8 正確にはsize t型で表現される.size t型がどの型に対応するかは処理系依存である.
9 マクロの意味は後日解説する.
10ANSIの規格書によれば,char型の占めるビット幅を1バイトと定義している.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
4.7.3.3.1 整数の内部表現 整数型の変数の内部での表現は, Section 1.4で述べた表現がとられているこ とが多い.
4.7.3.4 浮動小数点型
浮動小数点型についても, Cでは以下のことしか定義されていない.
float≤double≤long double
実際,それぞれの浮動小数点型の長さ(sizeofの返す値)を見てみると,今回利用する処理系では,次の ようになる.
float 4
double 8
long double 8
これで,char 型が1バイトであることを使うと,float は4バイトであることがわかる.
(10を底とする)浮動小数点数とは,以下のような型で表現された数のことである.
(0でない1桁の数).<仮数部> × 10^<指数部>
ここで,任意の 0でない実数は,このような表示が可能であることに注意せよ. (もちろん,その表示は有 限小数と仮定すれば一意的である.)
4.7.3.4.1 浮動小数点数の内部表現 浮動小数点数の変数の内部での表現は, Section 1.4で述べた表現が とられていることが多い.
オーバーフロー,アンダーフローをした数の演算,比較等の結果がどうなるかは規格では定められていな い. しかし,それぞれの処理系によって規定されている.
4.7.3.5 列挙定数
列挙定数とは,次のようなものである.
enum day {sun, mon, tue, wed, thu, fri, sat} ; この時,eum型の変数はintとして扱われる. 即ち,上の例では, sun <-> 0
mon <-> 1
などというように対応づけが行なわれて処理される.
4.7.3.6 変換
Cのプログラムで演算を行なう時には,数多くの型の変換が行なわれてから演算が実行される.
4.7.3.6.1 整数への格上げ 整数型(char,short,int,long,このような型をIntegral typeと呼ぶ.)に 対して, 演算を行なう時には, 整数への格上げ (Integral Promotion)と呼ばれる操作が行なわれることが ある.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
それは,以下のように定義されている11¿char,shortは,符号つきも符号なしも,整数が使える式で使っ て良い. この時,元の型の全ての値が intで表現できる時には,その値は intに変換される. intで表現 できない時にはunsigned intに変換される.
また, unsigned int にしか変換できないという状況は,short と intが同じバイト幅である時に起こ り, この時,unsigned shortはunsigned intに変換される.
ここでは,long, unsigned longについては規定されていない.
4.7.3.6.2 符号拡張 Cではchar は符号つきか符号無しかを規定していない. この時,最上位ビットが 1 であるようなchar を intに変換する時の振舞いは処理系依存である. 例えば, 最上位ビットが1 とし て負の数に変換される(これを符号拡張と呼ぶ)こともあれば, 0として正の数に変換されることもある.
例えば,charが1バイト,intが4バイトのときに,charがsigned charである時には,符号拡張され, intに変換され計算される. この時,前に述べた2進数の表現がとられ,負の数は2の補数表現がとられて いる時,負のsigned char型の変数は,上位ビットに1が埋められ,signed intと扱われる. 一方,char がunsigned charの場合は,常に0が埋められる.
したがって,0x7Fを越えるchar型の変数を扱う時には,必ずunsigned charとし,より広い整数への 変換がある時には,符号なしで受けなくてはいけない12.
例えば,charが signed charの時,char a = 0x80とすると,(int)aは 0xFFFFFF80となるが,char がunsignedの時には,0x80のままである.
4.7.3.6.3 整数への変換 任意の整数が符号つき型に変換される時,その数が新しい型で表現可能ならば,
その値は不変になるが,そうでない時の結果は処理系依存である.
4.7.3.6.4 整数と浮動小数点数 浮動小数点数を整数に変換する時には,小数部は無視される. また, 結
果として得られる整数が目的の型で表現できない時の振舞いは不定である.
逆に,整数を浮動小数点数に変換する時には,その結果が表現可能な範囲にある時でも,正確に表現がで きない時には,一番近い大きな数か小さな数のどちらかに変換される.
4.7.3.6.5 浮動小数点数 浮動小数点数がより精度の高い浮動小数点数に変換される時には,その値は不
定である.
逆に精度の高いものが低いものに変換される時には,その結果が表現可能な範囲にある時でも,正確に表 現ができない時には,一番近い大きな数か小さな数のどちらかに変換される.
4.7.3.6.6 算術変換 これは,算術演算が行なわれている時に,被演算数の型を揃え,その結果も同じ型に
するという操作である.
ほとんどの演算において,この変換が行なわれる. その手順は,以下の通りである. (条件に一致した最 初の変換が行なわれる).
1. いずれかの被演算数がlong doubleならば,他もlong double にする.
2. いずれかの被演算数がdoubleならば, 他もdoubleにする.
3. いずれかの被演算数がfloatならば,他もfloat にする.
4. 上のいずれも一致しない時には,整数への格上げを行なって,以下の変換を行なう.
(a) いずれかの被演算数がunsigned longならば,他もunsigned longにする.
(b) いずれかの被演算数がlongで,他がunsigned intである時には,次を調べる.
11[2]の定義によれば,「符号なし型はより広い符号なし型に変換される」とされているので注意すること.
12Cの定義によれば,「標準文字セットのすべての文字は正の値を持つ」となっている.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
i. longがunsigned intの全ての数を表現できれば,unsigned intの被演算数はlong int にする.
ii. そうでない時には,全ての被演算数はunsigned longに変換される.
(c) いずれかの被演算数がlongならば,他もlong にする.
(d) いずれかの被演算数がunsigned intならば,他もunsigned intにする.
(e) 上のいずれかも当てはまらない時には,被演算数をintとして計算する.
Example 4.7.1 例えば,longと unsigned intの和は,int=longの場合と,int<longの場合とで, 結果が異なる.
unsigned int a = 1U ; long b = -1L ;
a > b ; /* long = int の時, 正しくない. long > int の時正しい. */
これを正確に判定するには, unsigned int a = 1U ; long b = -1L ;
(long)a > b ;
とする. (後述のSection 4.7.5参照.) Example 4.7.2 intが2バイトである時,
unsigned int a = 256 ;
(a * a * 1L) == (a * (a * 1L)) ; /* この式は正しくない. */
ということが起こる.
Example 4.7.3 次の例は,符号拡張,算術変換などの例である.
int a,b,c ; char n ; double x ; a = 1 ; b = 2 ;
x = a/b ; /* x の値は 0 である. */
x = 1/b ; /* x の値は 0 である. */
x = 1.0/b ; /* x の値は 0.5 になる. 算術変換. */
a = -7 ; b = 2 ;
c = a/b ; /* この値が -3 となるか -4 となるかは処理系依存. */
n = 1 ;
a = n ; /* sizeof(int) = 4, char が符号付きの時,
* a = 0xFFFFFF01 となるか (符号拡張)
* a = 0x00000001 となるかは処理系依存. */
この値を正しく計算するには,後述する型変換を使う必要がある.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
4.7.4
演算4.7.4.1 2項算術演算
2項算術演算とは,通常の足し算,引き算,かけ算,割算, Moduloである.
4.7.4.1.1 加法演算子 加法演算子には+,-がある. これらは,左から右に作用する. 被演算数が算術的
13(即ち,整数や浮動小数点数)であれば,算術変換が適用される.
4.7.4.1.2 乗法演算子 乗法演算子には*,/,%がある. これらは,左から右に作用する. *(かけ算),/
(割算)14 は,被演算数が算術的でなくてはならない. %(余りを出す)は, 被演算数は整数でなくてはな らない. これらの演算には,算術変換が適用される. 即ち,整数同士の割算の結果は,再び整数となり,その 商が求められる.
/,%の第2被演算数が0 で無い場合には,(a/b)*b+a%bが aに等しいということが常に保証され,両方 の被演算数が非負の場合には,あまりが非負で, 除数よりも小さい. そうでないときには,あまりの絶対値 が除数の絶対値よりも小さいことが保証される.
4.7.4.2 単項算術演算子
ここでは, インクリメントのみを扱う. ここで述べる2種類の演算子は,整数かポインタに対してのみ適 用できる.
4.7.4.2.1 前置インクリメント 演算子 式の前に++もしくは--がついている式もまた,式になる. これ は, その値が使われる前に1 だけ増やされる(++の場合).
4.7.4.2.2 後置インクリメント 演算子 式の後に++もしくは--がついている式もまた,式になる. これ は, その値が使われた後に1 だけ増やされる(++の場合).
Example 4.7.4 インクリメントの例は以下のものである.
int a ; a = 1 ;
a++ ; /* この値を表示させると, 1 を表示したあとに, 1 増分される. */
a = 1 ;
++a ; /* この値を表示させると, 1 増分した後に, 1 を表示する. */
a = 1 ;
--a++ ; /* これはエラーである. */
次のような計算を考える.
int x, y ; x = 1 ;
y = (x++) - (x++) ;
この結果は,次の3通りが考えられる.
1. y = 0 これは,先に-を実行し,それからインクリメントをした.
13加法演算子は,後述するポインタにも作用する.
14もちろん,/,%の第2被演算数は0であってはならない./,%の第2被演算数が0の場合には結果は不定となる.一般には「0 除算による例外割り込み」発生する.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
2. y = -1これは,1 - 2を実行した.
3. y = 1 これは,2 - 1を実行した.
このように2項演算と各項の評価をどの順序で行なうかは,不定であるので注意すること.
4.7.4.3 代入演算子 次のような代入を考える.
a += 1 ; a -= 1 ; a *= 1 ; a /= 1 ;
これらは,それぞれ,aの値を1 加えた(減らした,掛けた,割った)値を再び,aに代入する演算である.
Example 4.7.5 代入の例は以下のものである.
int a ; a = 1 ;
a += 1 ; /* a の値は 2 となる. */
その他にも,%=,<<=,>>=,&=,^=,|=がある. もちろん,=も代入である.
4.7.4.4 単項演算子
単項ビット演算には,~,!がある.
~は1の補数をとるための演算子で,被演算数は整数でなければならない. この時,整数の格上げが行な われる. 1の補数とはビット反転のことである.
!は論理否定をとるもので,被演算数は算術型かポインタである. 被演算数が0であれば,結果は1とな り, そうでなければ, 0 である.
4.7.4.5 2項ビット 演算
2項ビット演算には,&,|,^,<<,>>がある.
&はビットごとのANDをとる関数である. 被演算数は整数でなくてはならない. また,算術変換が行な
われる.
|はビットごとのORをとる関数である. 被演算数は整数でなくてはならない. また,算術変換が行なわ れる.
^はビットごとのXORをとる関数である. 被演算数は整数でなくてはならない. また,算術変換が行な われる.
<<,>>はそれぞれ,ビットごとの左シフト,右シフトをとる関数である. 被演算数は整数でなくてはなら ない. また,整数への格上げが行なわれる. 結果は,格上げを受けた左被演算数の型である.
E1<< E2の値は,E1を左に E2だけシフトしたものである. オーバーフローがない場合には 2E2 をか けることに等しい. E1>> E2の値は,E1を右にE2だけシフトしたものである. E1が符号なし,または負 でない時には2E2 で割ることに等しい. そうでない時には,結果は処理系依存である.
E2が負である時には,結果は不定である.
また,左にシフトした時には,右には0がつめられる. 符号なし数を右にシフトした時には,左には0が つめられるが,負の数を右にシフトした時には,左には, 1 がつめられる(算術シフト )か0がつめられる
(論理シフト )かは処理系に依存する.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
Example 4.7.6 <<, >>の演算の例は,以下の通りである.
signed char a ; unsigned char b ; a = -0x02 ; b = 0x02 ;
a << 1 ; /* 結果は int で FFFFFFFC */
a >> 1 ; /* 算術 shift の時, 結果は int で FFFFFFFF, 論理 shift なら 7FFFFFFF */
b << 6 ; /* 結果は int で 128 */
a << 6 ; /* 結果は int で FFFFFF80 */
ここで,負の数のシフトは,整数への格上げを受け,符号拡張も受けていることに注意せよ.
4.7.4.6 論理演算
論理演算には,比較(関係演算子),等値演算子, AND, ORがある. これらの演算子は全て,左から右に 適用される.
比較には, <, >,<=,>=があり,それが正しければ, 1,そうでなければ0 が返される. 以下のようにして 使う.
式1 < 式2
等値演算子は,==で表される.
式1 == 式2
その結果が正しければ(式1と式2が等しい)1,そうでなければ 0 となる. ただし, ==は内部表現で 判定しているので,
0xFFFF == 65535 ;
0xFFFFFFFF == -1; /* int が4バイトで, 2の補数表現の時 */
はともに1 となる.
等値演算子の否定は,!=で表される.
式1 != 式2
その結果が正しければ(式1と式2が等しくない)1,そうでなければ0 となる.
論理AND演算子は&&で, 式1 && 式2
であって,被演算数の両方が非零の時1を返し,そうでない時0を返す. また,&&で評価されるのは,もっ とも左の式であり,それが0であれば,その式の値は0である. そうでなければ,右の被演算数が評価され る. それが0 であれば,式の値は0となり,そうでない時に1 となる.
論理OR演算子は||で, 式1 || 式2
であって,被演算数のどちらかが非零の時1 を返し,そうでない時0 を返す.
論理AND, OR演算子の結果はintである.
Example 4.7.7 論理AND, OR演算子の例は以下の通りである.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
int a=0,b=1,c=2,d=3 ; (a < b)&&(c < d) ;
/* これは a < b && c < d でも同じだが, */
/* 関係をはっきりさせるために, 括弧をつけた方がよい. */
/* この式の評価は, (1 ) && (1 ) となるので, 1 が値となる */
(a > b)||(c > d) ;
/* この式の評価は, (0) || (0) となるので, 0 が値となる */
a && (c < d) ;
/* この式の評価は, (0) && (1) となるので, 0 が値となる */
b || (c > d) ;
/* この式の評価は, (1 ) || (0) となるので, 1 が値となる */
(!(a < b))&&(!(c < d)) ;
/* この式の評価は, (0) && (0) となるので, 0 が値となる */
ここで,注意しなくてはならないのは,3,4番めの式である. 次の例を見よう.
Example 4.7.8 論理AND, OR演算子の変な例は以下の通りである.
int a=0,b=1,c=2,d=3 ; a && (c++ < d) ;
はじめの式はa = 0 であるので,&&は右の式の評価は行なわない. したがって,この式の後にcの値を 求めると,c = 2のままである.
int a=0,b=1,c=2,d=3 ; b && (c++ < d) ;
この場合はc++が実行されるが,その順序は,先にインクリメントが実行されるので,b = 1,(c++ < d)
= 0となり,結果は0となる.
int a=0,b=1,c=2,d=3 ; b || (c++ > d) ;
この場合は,b = 1 であるので,||は右の式の評価は行なわない.
このようにCでは, 評価が全て行なわれる前に式の値が決定することがあり,その時点で式の評価が終了 するので,注意しなくてはならない.
4.7.4.7 3項演算子
3項演算子? :は条件演算子とも呼ばれる演算子である.
式1 ? 式2 : 式3
まず, すべての副作用を含めて, 式1が評価され,それが0 でなければ結果は式2の値となり, そうでな ければ式3の値となる. この時評価されるのは式2または式3のいずれかである. 式2,式3の被演算数が 算術型であれば,通常の算術変換が行われて,それらは共通の型となり,それが結果の型となる.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
4.7.4.8 コンマ演算子
式1 , 式2
コンマ ,で区切られた式は左から右に評価され,式1の値は捨てられる. 結果の型と値は式2の型と値 である. 式1の被演算数の評価に伴うすべての副作用は,式2の評価を始める前に完了している.
4.7.4.9 演算子の優先順位と結合規則
これら演算子の優先順位,結合規則は,次の通りである. 上ほど優先順位が高い.
演算子 結合規則
( ) [ ] -> . 左から右
! ~ ++ -- + - * & (type) sizeof 右から左
* / % 左から右
+ - 左から右
<< >> 左から右
< <= > >= 左から右
== != 左から右
& 左から右
^ 左から右
| 左から右
&& 左から右
|| 左から右
?: 右から左
= += -= *= /+ %= &= ^= |= <<= >>= 右から左
, 左から右
ただし,単項の+ - * は二項のそれよりも上である.
4.7.5
型変換異なる型の被演算数が式に現れると,いくつかの規則にしたがって,明示的もしくは暗黙に共通の型への 変換が行なわれる.
前のセクションで述べた変換がその代表的なものであるが,ここでは,それ以外に明示的に型変換を行な う方法を述べる.
それは,次のような方法である.
(型名) 式
この方法によって,式はその前に書いた型名で示された型に変換される. その際の規則は, Section 4.7.3.6 で示した通りである.
Example 4.7.9 次のような例がある.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
char a ; int b ; a = 0x01;
b = (int) a ;
この場合は, charがより広い型のintに変換されている. しかし,この型変換は整数への格上げそのも のである. このように,整数への格上げがあっても,明示的に型変換をすることが望ましい.
次のような例もある.
int a,b ; double x ; a = 1 ; b = 2 ;
x = (double)a/b ;
この場合型変換を行なわないと,xの値は0 となるが,型変換を行なっているので,xの値は0.5となる.
4.7.6
左辺値左辺値(left value, lvalueとも書かれる)は,オブジェクトを参照し,変更できる式のことである. 左辺値
でないものに代入しようとすると,文法エラーとなる.
Cの定義によれば,オブジェクトとは,名前つきのメモリ領域のことであり,左辺値はオブジェクトを参 照する式であるとされている.
例えば,変数は左辺値となり得る. しかし例えば,a++などの演算を受けたものは左辺値にはなり得ない.
どのようなものが,左辺値となり得るか,なり得ないかは[3, A5]に定義されている.
4.8
いくつかのプログラムここまででは,変数の値を表示する方法は述べていなかった. それをするためには,printf関数を使う.
Example 4.8.1 その利用法は,次の通りである.
char a ; int b ; long c ; float x ; double y ; long double z ;
printf("a = %c\n", a) ;
printf("b = %d, c = %ld\n",b, c) ; printf("x = %f\n", x) ;
printf("y = %f\n", y) ; printf("z = %lf\n", x) ;
このように,%dなどと,変数を対応させ,表示させることができる. ここで,それらの意味は次の通り.
• %c文字を表示させる.
• %d整数値を表示させる.
• %f浮動小数点値を表示させる. (小数形式)
• %e浮動小数点値を表示させる. (指数形式)
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
• %x16進数で表示させる.
• %s文字列を表示させる.
また, %02Xと書くと, 2桁で, 余分なところには0 を埋めた表示を得ることができる. 詳しくはオンライ ン・マニュアルでman 3 printfとして見よ.
Exercise 4.8.1 色々な型の演算の値を出力するプログラムを書け. 例えば,次のようなものである.
#include <stdio.h>
int a,b ; main() {
printf("%d + %d = %d\n", a, b, a + b);
}
Exercise 4.8.2 次のプログラムの出力結果がなぜそのようになるかを考えよ.
#include <stdio.h>
int x=2, y, z ; int main() {
y = z = x ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y == z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = (y == z) ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; }
Exercise 4.8.3 次のプログラムの出力結果がなぜそのようになるかを考えよ.
#include <stdio.h>
int x, y, z ; int main() {
x = y = z = 1 ; ++x || ++ y && ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y = z = 1 ;
++x && ++ y || ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y = z = 1 ;
++x && ++ y && ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y = z = -1 ;
++x && ++ y || ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y = z = -1 ;
++x || ++ y && ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; x = y = z = -1 ;
++x && ++ y && ++z ;
printf("x=%d,y=%d,z=%d\n",x,y,z) ; }
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
Exercise 4.8.4 次のプログラムの出力結果がなぜそのようになるかを考えよ.
#include <stdio.h>
int x, y, z ; int main() {
x = 3 ; y = 2 ; z = 1 ; printf("%d\n", x | y & z) ; printf("%d\n", x | y & ~z) ; printf("%d\n", x ^ y & ~z) ; printf("%d\n", x & y && z) ; x = 1 ; y = -1 ;
printf("%d\n", !x | x) ; printf("%d\n", ~x | x) ; printf("%d\n", x ^ x) ;
x <<= 3 ; printf("x = %d\n", x) ; y <<= 3 ; printf("y = %d\n", y) ; y >>= 3 ; printf("y = %d\n", y) ; }
Exercise 4.8.5 次のプログラムの出力結果がなぜそのようになるかを考えよ.
#include <stdio.h>
char c ;
unsigned char n ; double d ;
float f ; long l ; int i ; int main() {
c = 0x7F ; printf("c=%X\n",c) ; c = 0x80 ; printf("c=%X\n",c) ; n = 0x7F ; printf("n=%X\n",n) ; n = 0x80 ; printf("n=%X\n",n) ; i = l = f = d = 100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; d = f = l = i = 100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; i = l = f = d = 100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; d = f = l = i = (float)100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; i = l = f = d = (float)100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; i = l = f = d = (double)100/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; i = l = f = d = 100.0/3 ;
printf("i=%8d\tl=%8ld\tf=%.8f\td=%.8f\n", i,l,f,d) ; }
Exercise 4.8.6 short,int,unsigned int,longのそれぞれの型の変数が何バイトの記憶領域をとるか を表示するプログラムを書け.
Id: C3.tex,v 1.8 2000-06-21 11:35:15+09 naito Exp
Exercise 4.8.7 int, longのそれぞれの型で表現される最大の数に1 を加えたらどうなるかを考察せよ.
Exercise 4.8.8 正の浮動小数点数の小数点以下を四捨五入した値を求めるプログラムを書け.
Exercise 4.8.9 AND, OR, NOTからXORを作れ.
Exercise 4.8.10 Example 4.7.2はどうしてかを考察せよ.
Exercise 4.8.11 intが16ビット,long が32ビットの時,-1L < 1U,-1L > 1ULとなる. 何故か?
4.9
文4.9.1
制御文制御文とは,プログラムの流れを制御する構造で,以下のものがある.
• 繰り返し文
• 条件文
• ラベルつき文
• ジャンプ文
ここでは繰り返し文と条件文のみを扱う.
ラベルつき文,ジャンプ文などは, BASICなどの言語では多用されるが, C言語においては,殆んど必要 ない(はず).
繰り返し文には,for 文, while文, do–while文がある. 条件文には, if文, if–else 文,if–else if 文, switch文がある.
4.9.2
繰り返し文繰り返し文とは,ある条件の元に文(複文)を繰り返すための制御文である.
4.9.2.1 for文
繰り返し文の代表例としては,for文がある. for文の構文は次の通りである.
for(式1;式2;式3) 文
ここで,式1から式3はどれを省いても良い. 式2は,算術型もしくはポインタでなくてはならない.
このようなfor文は,次のように制御される.
1. 式1が最初に評価される.
2. 式2は各繰り返しの前に評価される. もし,式2の結果が0 となると, forは終りとなる.
3. 繰り返しの部分が終る毎に,式3が評価される.
ここで,各式の副作用は,評価の直後に完了する.
Example 4.9.1 この例は, 0 から9までの整数を順に印字するものである.
Id: C4.tex,v 1.4 2000-05-09 08:46:25+09 naito Exp