[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
C言語で扱える型は、あまり多くはありません。大きな整数や、固定小数点、BCD
演算などは標準機能では扱えないので、別に用意されたライブラリを使うか、自
分で作るしかありません。にもかかわらず、
int
や
float
、
double
といったいくつかの型を使うだけでも結構落とし穴があるものです。今回は、値
の処理に関連するよくある質問をまとめてみました。
int
の変数が負になるべきでない所で負の値になっているようだ。
int
が扱える値の範囲は、
INT_MIN
から
INT_MAX
の間です。
INT_MIN
が-32767以下
であることと、
INT_MAX
が32767以上であることは、ANSI Cでは保証されています。しかし、この範囲を超
えてはいけないわけではありません。
int
と
long int
が同じ範囲の値を扱うことのできる処理系は結構あります。言い換えれば、
int
の表現に32ビットが使える場合です。この場合、少なくとも-2147483647〜
2147483647の値を扱うことができます。
プログラムが、
long int
の範囲の値を扱うことを想定し、かつ
int
と宣言した変数を用いている場合、
int
が16ビットである処理系に移植する時に問題が生じます。
もしこの問題によっておかしな動作をしているのであれば、プログラムの該当
箇所の
int
で宣言されている変数を
long int
に修正することによって解決するでしょう。
だからといって、
#define int long
のようなことはしないように。新たな問題の種を撒くことになります。
INT_MIN
が-32767までの場合を許しているのか。
INT_MIN
が-32768であっても何の問題もありませんが、-32767までしか扱えなくても、
ANSI Cの規格には適合することになっているだけの話です。単にそういう決まり
だ、ということです。
これは、本質的には数値の表現の問題です。-32768が許される処理系では、こ
の値に対応する正の値が
int
に入り切らないので、面白い結果になるかもしれません。例えば、次の結果がど
うなるか想像してみましょう。
int i; i = -32768; printf("i = %d, -i = %d\n", i, -i);
int
と
short int
、
long int
との使い分けは、どのような基準で判断すればよいか。
long
を使うべきでしょう。
int
が32ビットの処理系なら、単に
int
と書いても同じ結果になりますが、後から
int
が16ビットの処理系に移植したいという人が苦労することを考えると、
long
としておく方が世のためです。
要素数の多い配列を扱う場合は、メモリの効率を重視する必要があります。こ
の場合は、
short
で十分に表現できる範囲の数を扱うのであれば、
short
を選択しましょう。2MBの大きさになる配列を1MBで済ませることができるかもし
れません。もしかすると
char
で十分な場合もあるかもしれません。
ちょっとしたループカウンタや判断のための場合分けの変数には、あまり考え
ずに、
int
を使いましょう。
例えば、100回処理するためのループカウンタとして用いるのであれば、理論上
は
char
の変数でも十分かもしれません。しかし、この場合も単純に
int
の型にしておくのが普通です。
(参考: comp.lang.c FAQ 1.1)
int i, j; long l; i = 100; j = 500; l = i * j; printf("%ld\n", l);
int
の大きさが16ビット、すなわち-32768〜32767の範囲の数しか表現できない処理
系であると想像します。手元にある某コンパイラで実験したら、-15536と表示さ
れました。
このような結果になる理由は、掛け算を計算している行の処理を細かく考える
と分かります。まず、
i * j
が計算されますが、これらは両方とも
int
の変数なので、その結果は一旦
int
の値として保存されます。16ビットの
int
の範囲では50000という値は保持することができず、オーバーフローが発生しま
す。その結果は-(65536-50000)で、すなわち-15536です。これが
l
に代入されました。
掛け算の前にどちらかの変数を
long
にキャストしてやれば、期待通りの結果を得ることができます。
long
と
int
の掛け算なら、
その中間結果は
long
の値として保持されるからです。
l = (long) i * j;キャストは、掛け算の結果が出る前に行う必要があります。キャストの優先順 位は掛け算より高いので、先程の書き方は合っています。次のように書いてはい けません。これでは掛け算した結果はキャストする前に
int
の範囲の中間結果として保持されてしまうので、キャストしなかった時と何も変
わりません。
l = (long) (i * j);(参考: comp.lang.c FAQ 3.14)
static char prompt[] = ">"; int c1, c2; c1 = getchar(); c2 = getchar(); if (c1 == prompt[0] && c2 == prompt[1]) { /* ">" */ /* ここを実行してくれない */ }
prompt[0]
と
prompt[1]
が漢字コードの1バイト目、2バイト目になるというアイデアはよかったのですが、
比較する時の型が
int
と
char
なのがトラブルの原因になってしまいました。
c1
と
c2
には、読み込んだ文字に対応した0〜255の値が入ります。処理系にもよりますが、
漢字の一部分が読み込まれた場合は、128以上の値になるでしょう。これに対して、
prompt[0]
は、おそらく符号付きの
char
型で、表現し得る値は-128から127の値です。これらを比較しても、期待した通り
に一致してくれないのです。
解決する方法は、いくつかあります。一つは、
prompt[]
の型を
char
ではなく
unsigned char
にしてしまう方法です。それでよいのですが、
prompt
を標準的な関数の引数にしたい場合に、うるさい警告メッセージが出るかもしれ
ません。(例えば
puts
の引数は
unsigned char
へのポインタではなく
char
へのポインタというようにプロトタイプ宣言されているから)
もう一つの方法は、0xffでマスクすることにより、0〜255の範囲の整数にして から、比較する、というものです。
if (c1 == (prompt[0] & 0xff) && c2 == (prompt[1] & 0xff)) {
getchar
は文字を返す関数なのだから、
char
の変数に戻り値を受けた。なぜこれが非難の的になるのか。
getchar()
の型は
int
と書かれているし、ANSI Cではそのように定義されています。従って、
getchar()
の戻り値は
int
の変数で受けるのです。
getchar()
は、実際、0〜255の値と、
EOF
という値(おそらく-1)の、合計257通りの値を戻します。これは
char
の範囲では受け切れません。あえて
char
の変数で受けるなら、
EOF
と255は区別できないことになります。
「私の処理したいファイルの中には255などという文字コードは使われていない
から
char
で受けても実害はない」という主張を見たことがありますが、
int
で受ければさらに実害はないはずです。
int
を戻すものと想定されてリンクされるので、
long
や
double
を戻すつもりなら期待した結果にならないでしょう。
分割コンパイルする時には、呼ぶ側と呼ばれる側で、関数の型が一致していな ければなりません。プロトタイプ宣言をすることによって、この問題は解決する はずです。
sin
を使ったプログラムを書いたのだが、リンクの時にsin関数が見つからないとい
うエラーが出てしまう。
リンク時にライブラリを指定する場合、その順序も重要な意味を持つことがあ ります。-lmというオプションは後の方に指定した方がよいと思います。
(参考: comp.lang.c FAQ 14.3)
#include <math.h>
を忘れていませんか。コードを確認してみましょう。
これを忘れると、
double
を戻すつもりで作られている関数なのに、
int
を戻す関数を呼び出したようなコードが生成されてしまうことになります。呼び
出した関数と呼ばれた関数がうまくかみあわないで、変な値を使ってしまうこと
になります。
(参考: comp.lang.c FAQ 14.2)
(int) (x + 0.5)を実行することです。ただし、この方法は負の数に対して期待した結果を得る ことができないかもしれません。
(参考: comp.lang.c FAQ 14.6)
double
の変数xに1.005という値を入れて、小数点以下3桁目で四捨五入する関数を呼び
出した。結果は期待した1.01ではなく、1.00になってしまう。
1.005というのは、見た目ほどきりのよい数字ではないのです。この場合、内部 表現としては1.0049999..のような値になってしまい、四捨五入の判断によっては 4以下を切り捨てる結果になるのでしょう。
これはC言語の問題というよりも、むしろ一般的な数値表現の問題ですから、詳 細を知りたい型は、その種の教科書を見てください。
double
型を使ったままでこの問題を解決するのは困難です。BCD
演算や固定小数点の演算を行うライブラリを使うか、そのような関数を自作する
ことが、本質的な解決になります。あるいは、整数で演算し、必要な時に割り算
して値を使う、という手があります。
float x; for (x = 0.0; x < 1.0; x += 0.1) { /* 必要な処理 */ }
float x; int i; for (i = 0; i < 10; i++) { x = i / 10.0; /* 必要な処理 */ }
整数型は
char
を使った場合でもおそらく8ビット、場合によっては16ビットや32ビットの大きさ
を持ちますから、1と0という二通りの状態を表現するには極めて冗長になるかも
しれません。プログラムの工夫をすれば、整数の各ビットに意味を持たせて、複
数の状態を表現することも可能です。ただし、メモリを節約した結果、処理が複
雑になったり、オーバーヘッドが増えるかもしれません。C言語は、これらの選択
をプログラマーに任せているのだ、と解釈することもできます。
(参考: comp.lang.c FAQ 9.1)
(2 == 3)
という式の値は必ず0になり、
(4 != 5)
という式の値は必ず1となります。
中には、真の値として-1を使うことを好む人もいます。二の補数表現では、-1
は全てのビットが1となるため、分かり安いと考えるのでしょう。偽を0とした場
合に
(~0)
を真とする考え方です。同様な発想で、
(!0)
を真とすると考えれば、真の値は1に割り当てるのが自然です。
#define TRUE (1 == 1) #define FALSE (!TRUE)
#define TRUE 1 #define FALSE 0マクロの定義は、結局はこのように書いたのと同じ程度の意味しかありません。
#define TRUE 1 if (isupper(c) == TRUE) { /* 大文字の時の処理 */ }これは失敗します。
isupper
は、
c
が大文字の時に「0以外の値」を戻すとしか規定されていないからです。つまり、
1か0という値しか戻らないとは決まっていないので、
TRUE
という値と単純に比較することができません。
ただし、次の処理は、
FALSE
が0である限り、正しく動作します。
if (isupper(c) == FALSE) { /* 大文字でない時の処理 */ }なぜなら、
c
が大文字でないなら、0が戻ってくることが間違いないからです。
また、前述のように、中には-1という値を真だと定義したがるプログラマーが いるかもしれません。そのような人の書いたプログラムに手を加える時に、真が 1であると決めてかかると、もしかすると失敗するかもしれません。これに対して 偽が0であるという前提は大部分のプログラマーで異義がない定義なので、失敗す ることが少ないのです。
if (式) 文
」という処理において、式が0以外の時には文が実行されることになっています。
式の値が1である必要はありません。0以外の値なら何でも真とみなされます。実
際にプログラムを書く場合には、これが便利なことがあります。
前述の大文字の処理なら、次のように書くことができます。
if (isupper(c)) { /* 大文字の時の処理 */ }しかし、
(x == y)
のような比較演算子の結果は、1か0になることが決められているので、基本はや
はり1か0と考えた方がよいでしょう。
このような何度も繰り返し現れる類の質問はFrequently Asked Question (FAQ) と呼ばれています。FAQに対する回答を集めたものが、いくつも作られています。 comp.lang.cで頻出するFAQのリストは、Steve Summit氏が編集して、毎月1日にcomp .lang.cに投稿されています。このドキュメントは自由に配布できるため、あちこ ちに転載されているようです。
このドキュメントのオリジナルは英文ですが、北野欽一氏が日本語に訳したも のをfj.comp.lang.c等で読むことができます。
comp.lang.c FAQは、現在WWW上で見ることができます。また、1996年2月26日に 日本語訳が公開されています。