第7章 基本型

テキスト『新・明解C言語 入門編』第7章の内容に関する補足説明を提供します.

7-1 基本型と数

算術型と基本型

汎整数型(integral type)のひとつである 列挙型(enumeration type)については第8章で学習します.

基数

このセクションについての補足説明は現在のところありません.

基数変換

n 進数から10進数への変換でも,10進数から n 進数への変換でも, 「集めて束にする」という理解が重要です.

10進数は,1 を10個集めて束にすると 10 になり, 10 を10個集めて束にすると 100 になります. 同様に,n 進数では,1 を n 個集めて束にすると 10 になり, 10 を n 個集めて束にすると 100 になります.

テキストの例にある8進数の 123 で,「2」は8進数での 20 です. これは,1 を8つ集めた束が2つあることを意味します. したがって,8進数での 20 は,10進数での 16 と等しくなります. 1を8つ集めた束(つまり,8進数の10)を8束集めると, 8進数での 100 になります. したがって,8進数での 100 は10進数の 64 と等しくなります.

以上の説明で,n 進数から10進数への変換は理解できるでしょう. 次に,10進数から n 進数への変換を考えます.

2進数では 1 を2つ集めて束にすると 10 になり, 10 を2つ集めて束にすると 100 になります. Fig. 7-3 を見てください.10進数の 57 を2進数に変換するには, 1 を2つずつ集めた束を作ります. そうすると,28束できて,1 あまります. あまった 1 は,2進数での1の位の数値になります. つぎに,2進数での 10 の束が28あるので, これをさらに2つずつ束にします. そうすると,2進数での 100 の束が14できます. あまりはないので,2進数での10の位の数値は 0 になります. この14の束を2つずつ集めると, 2進数での1000の束が7つできます. あまりはないので,2進数での100の位の数値は 0 になります. このように,「2つ集めて束にする」ことを繰り返せば, 10進数を2進数に変換することができます. 他の n 進数への変換も同様の方法でできます.

7-2 整数型と文字型

整数型と文字型

このセクションについての補足説明は現在のところありません.

<limits.h>ヘッダ

この授業を行っている,情報基盤センターの演習室にある Mac での, limits.h の内容をのぞいてみましょう. いくつかのディレクトリに同じ名前の limits.h がありますが, ここで内容を見るのは /usr/include/i386/limits.h です.

ターミナルで


more /usr/include/i386/limits.h

と入力して [Return] キーを押してください. この more コマンドは,テキストファイルの内容をページ単位で表示します. スペースキーを押すと次のページを表示します. [Return] キーを押すと1行だけ表示を進めます. [Q] キーを押すと表示を終了します.

表示を進めていくと,以下の図のように, 文字型と整数型で表現できる値の最大値と最小値が見つかります.

limits.h での各型の最大値と最小値の定義

先頭に 0x とついている数は16進数での表示です(テキスト p.194 で学習します). 数字の末尾についている U および L は, テキスト p.195 で学習する 整数接尾語(integer suffix)です.

List 7-1 では,printf 関数において, これまでに学習していない変換修飾子および変換指定子が使われています. 変換指定子 u は,unsigned int 型の実引数を, 符号なし10進表記に変換することを意味します. 変換修飾子の l(エル)は,変換指定子 d または u と共に使われたとき, 対応する実引数の型が long int 型または unsigned long int 型であることを意味します. したがって,long int 型の値を表示するための変換指定は %ld, unsigned long int 型の値を表示するための変換指定は %lu となります. テキスト第13章 13-3 節の,printf 関数の仕様を参照してください.

演習室の環境では,List 7-1 をコンパイルすると下図のような警告が出ます. 「変換指定は %lu だから unsigned long int 型なのに, 表示する引数の値は int 型です」という内容です.

list 7-1 のコンパイルでの waring

警告が出ないようにするには, unsigned long 型の最小値として表示する引数 0 を, 0UL0 のあとに整数接尾語 UL をつける) としてください.すなわち,


printf("unsigned long: %lu ~ %lu\n", 0UL, ULONG_MAX);

とします.コンパイルして実行すると, 文字型および整数型の値の範囲が以下のように表示されます.

list 7-1

文字型

List 7-1 の実行結果で char 型の数値範囲が -128 から 127 となっていることからもわかるように, 演習室の環境での char 型は符号付き型です. List 7-2 を実行すると,以下のようになります.

list 7-2

上で示したように,limits.h の中で, CHAR_MAX および CHAR_MIN は


#define CHAR_MAX   127
#define CHAR_MIN   (-128)

と定義されていました.テキストでの説明と異なり, 最小値と最大値が直接に数値で書かれています.

ビットと CHAR_BIT

C言語の規格(JIS X3100:2003)で, CHAR_BIT は8ビット(bit)以上と決められています. 8ビットとしている処理系が多いようです.

演習室の環境で,CHAR_BIT が何ビットに定義されているか確認してみましょう. 文字列を検索する grep コマンドを使うとよいでしょう. ターミナルで,


grep CHAR_BIT /usr/include/i386/limits.h

と入力して [return] キーを押してください(CHAR_BIT の前後に半角スペース). 下図のように,limits.h の中で,文字列 CHAR_BIT を含む行が表示されます. 演習室の環境では,CHAR_BIT は8ビットであることがわかります.

limits.h での CHAR_BIT の定義

sizeof 演算子

一般には,1バイト(byte)は8ビットです. しかし,C言語の規格では,char 型のサイズを1バイトとして扱います. したがって,<limits.h> ヘッダの中で CHAR_BIT が8と定義されていれば,1バイトは8ビットです. そうでなければ,1バイトは8ビットではありません.

C言語の規格(JIS X3100:2003)でのビットの定義はテキストに掲載されています(p.179). バイトの定義を引用します.

実行環境の基本文字集合の任意の要素を保持するために十分な大きさをもつ アドレス付け可能なデータ記憶域の単位。

参考1. オブジェクトの個々のバイトのアドレスは,一意に表現できる。

2. バイトは連続するビットの列から成る。 1 バイト中のビットの個数は,処理系定義である。 最も重みの小さいビットを最下位ビット(low-order bit)と呼び, 最も重みの大きいビットを最上位ビット(high-order bit)と呼ぶ。

演習室の環境で List 7-3 を実行すると, 文字型と整数型の大きさは下図のようであることがわかります.

List 7-3

Fig. 7-8 では,いずれの型においても, 横一列に並んだ8つのビットが1バイトの記憶域を占めています. 図には明示されていませんが, 1バイトの記憶域それぞれにはアドレスがあります.

size_t 型と typedef 宣言

テキスト p.181 での size_t 型の定義


typedef unsigned size_t

における unsigned は,unsigned int 型を意味します. 単独の unsigned は int とみなされるのでした(テキスト p.175).

演習室の Mac の環境では, size_t 型は unsigned long int 型の同義語として定義されています. この定義は2段階になっていて, 最初に __darwin_size_t 型が unsigned long int 型の同義語として定義され, 次に size_t 型が __darwin_size_t 型の同義語として定義されています. これを確認するために, プリプロセッサによる処理が終わったところでコンパイルのプロセスを止めて, 関連する typedef 宣言を grep コマンドで検索してみましょう. プリプロセッサによる処理が終わると, <stddef.h> ヘッダがソースファイルに取り込まれます. 授業用ディレクトリにある適当なソースファイル(ここでは,list0703.c とします)を, -E オプションをつけてコンパイルします. このオプションは,プリプロセッサの処理が終わったところでコンパイルのプロセスを止めます. ターミナルで,


clang -E list0703.c | grep __darwin_size_t

入力として [Return] キーを押してください(darwin の前は _ が2つあります). このコマンド中の "|" は「パイプ」と呼ばれるもので, 複数のコマンドを組み合わせる働きをします. ここでは,clang によってソースファイルをコンパイルした結果を grep コマンドに渡して,__darwin_size_t という文字列のある行を検索しています. その結果,下図のように,size_t 型が2段階で定義されていることがわかります.

typedef size_t

整数型の使い分け

このセクションについての補足説明は現在のところありません.

整数型の内部表現

演習 7-1 では,n は int 型の変数として宣言してください. テキスト p.182 では, sizeof 演算子の引数が式のときには式を囲む ( ) は不要だが, 本書では ( ) をつけて表記すると書いてあります. しかし,この演習問題ではそのような書き方になっていないところがあります. たとえば, sizeof+1 および sizeof-1 はそれぞれ, sizeof(+1) および sizeof(-1) と表記するはずです.式を理解する演習問題として, 意図的にこのように書いているのだと思います.

いま取り上げた sizeof+1 および sizeof-1 では sizeof 演算子の後ろに半角の空白は必要はありませんが(しかし, 空白があった方がわかりやすいです), sizeof 1 では空白が必要です. そうしないと,sizeof1 という識別子だと解釈されてしまいます.

算術的な引き算が行われる sizeof(unsigned)-1 および sizeof(double)-1 と並べられているので誤解しやすくなっていますが, sizeof((double)-1) は引き算を意味していません. どんな意味か考えてください.

算術的な記号ではなくアルファベットの綴りなので奇妙な感じがしますが, sizeof は演算子です.したがって, sizeof n + 2 としたときには, n + 2 の結果に sizeof 演算子が適用されるのか, sizeof n の結果に 2 を加えるのか, 決まりがあるはずです. これについては 7-4 節で学習します. この時点では,演習7-1 のプログラムを実行して, どちらの順序で演算が行われているのか確認してください.

符号無し整数の内部表現

ビット表現を10進数の数値に変換する式はしっかり理解してください. 整数を n ビットで表現するとき,最上位のビットを表す記号は B n でなく B n - 1 であり,最下位のビットを表す記号は B 1 でなく B 0 であることに注意してください. 変換式では,この添え字と,2のべき乗は対応しています, たとえば, B n - 1 2 n - 1 との積を構成します. 足し算を表すシグマ記号を用いて変換式を表現すると, k = 0 n - 1 B k × 2 k となります.

n ビットを使って表現できる符号無し整数は 0 から 2 n - 1 までです. 2 n までではないので注意してください. 表現できる数の種類(つまり,n 個の 1 と 0 の並べ方の総数)は 2 n 通りですが,表現すべき数字には 0 が含まれているので, 表現できる最大の数値は 2 n よりもひとつ小さな値になります. たとえば,2 ビットで符号無し整数を表現する場合, 1 と 0 の並べ方は 00, 01, 10, 11 の4通りで, これらはそれぞれ10進数で 0, 1, 2, 3 という数値を表します.

符号付き整数の内部表現

符号付き整数の3種類の表現方法のうち, テキストでは最後に説明されている 符号と絶対値表現(sign and magnitude representation) が最も単純でわかりやすいので,この表現から補足解説をします.

符号と絶対値表現では,最上位のビットは符号を表します. すなわち,このビットが 1 なら正の整数,0 なら負の整数となります. 最上位を除く残りのビットは,符号無し整数と同じ表現を使います. 最上位ビット以外のビットが同じ2つの数値は, 符号が異なり,絶対値が同じ数値です. たとえば,3ビットで符号付き整数を表す場合, 000,001,010,011 はそれぞれ, 10進法で 0,1,2,3 という数値を表し, 100,101,110,111 はそれぞれ, 10進法で -0, -1, -2, -3 という数値を表します. ただし,100 が表現している -0 は,000 が表現している 0 と同じ数値です.

人が数字を書くなら負の数のときにマイナスの符号をつければよいわけですが, 計算式でこれを実行するためには,どうすればよいでしょうか? 符号と絶対値表現では,最上位以外のビットが表す数に ( 1 - 2 × B n - 1 ) をかけることで,これを行います. この式の値は,最上位ビットが 0,すなわち, B n - 1 = 0 のとき 1 です.最上位ビットが 1,すなわち, B n - 1 = 1 のとき -1 です. したがって,最上位ビットが 0 のとき, 表現されている数値は,10進法で 1 × ( B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 ) となります.これは正の整数です. 最上位ビットが 1 のとき, 表現されている数値は,10進法で -1 × ( B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 ) となります.これは負の整数です.

次に,1の補数表現(1's complement representaion)について解説します.

符号と絶対値表現での負の整数は, 最上位ビット以外のビットが表す数値が増加していくとき, 最上位ビットを含めて表現する数値としては小さくなっていきます. たとえば,最上位ビットの他に2ビットを用いる場合, 100,101,110,111 はそれぞれ,10進法で -0, -1, -2, -3 という数値を表します. 最上位ビット以外を見たときの数値は増加しているのに, ビット全体が表している数値は小さくなっていきます. これは不自然なことです.

そこで,負の整数の場合には, ビットの並びとそれが表現している数値との対応関係を逆にすることが考えられます. つまり,最上位ビット以外がすべて 0 のときに最小値をとり, すべて 1 のときに -0 を表すようにします. たとえば,最上位ビットの他に2ビットを用いる場合, 100,101,110,111 はそれぞれ,10進法で -3, -2, -1, -0 という数値を表します.

1の補数表現では,負の整数を表現するのに, 正の整数を表すビット列からのビット反転を行います. つまり,0 は 1 に,1 は 0 にします. 正の整数 a を表すビット列を反転して得られるビット列のことを, a の1の補数と呼びます. テキスト p.187 の Fig. 7-14 では,16ビットで数値を表現しており, 表現される数値の最大値は 32767 です. このとき,最上位のビットは 0 で,他のビットはすべて 1 です. ここで全ビットを反転すると, 最上位ビットが 1 で,他はすべて 0 になります. このビット列が最小値 -32767 を表すことにします.ここから,最上位ビット以外を 1 ずつ増やしていくと, 16ビットで表現される負の数の値も 1 ずつ増加していきます. 全ビットが 1 になると,それは -0 を表します.

1の補数表現を10進法での数値に変換する計算式 - B n - 1 × ( 2 n - 1 - 1 ) + B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 を理解しておきましょう. 最上位ビットが 0,すなわち, B n - 1 = 0 のとき, - B n - 1 × ( 2 n - 1 - 1 ) の値は 0 となります. 最上位ビットが 1,すなわち, B n - 1 = 1 のとき, - B n - 1 × ( 2 n - 1 - 1 ) の値は - ( 2 n - 1 - 1 ) となります.したがって, 最上位ビットが 0 のとき,表現されている数値は,10進法で B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 となります.これは正の整数です. 数値の表現方法は符号無し整数の場合と同じです. 最上位ビットが 1 のとき,表現されている数値は,10進法で - ( 2 n - 1 - 1 ) + ( B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 ) となります.これは負の整数です.最上位ビットが 1 で, 他はすべて 0 のとき,そのビット列は最小値 - ( 2 n - 1 - 1 ) を表します.最上位ビット以外を 1 ずつ増やしていくと, n ビットのビット列全体が表す数値(負の整数)も 1 ずつ増加していきます. 全ビットが 1 になると,それは -0 を表します.

最後に, 2の補数表現(2's complement representaion)について解説します. 正の整数 a を表すビット列を反転して得られるビット列のことを, a の1の補数と呼びました. このビット列に 1 を加えてできるビット列を, a の2の補数と呼びます.

符号と絶対値表現,および,1の補数表現では, すべてのビットが 0 であるビット列と, すべてのビットが 1 であるビット列の両方が, 0 という数値を表現していました. これは無駄なことだと言えます.

そこで,ビット列と負の数値との対応をひとつずつずらして, もうひとつ小さい数値まで表現するようにすることが考えられます. これが2の補数表現です. テキスト p.187 の Fig. 7-14 では,16ビットで数値を表現しており, 表現される数値の最大値は 32767 です. 1の補数表現では,32767 を表すビット列を反転させて得られるビット列を, -32767 に対応させました. 2の補数表現では,このビット列を -32768 に対応させます. 一般に,2の補数表現では, n ビットで符号付き整数を表現する場合, 最上位ビットだけが 1 で他が 0 というビット列は -2 n - 1 を表します. ここから,最上位ビット以外を 1 ずつ増やしていくと, 表現される負の数の値も 1 ずつ増加していきます. 全ビットが 1 になると,それは -1 を表します.

2の補数表現を10進法での数値に変換する計算式 - B n - 1 × 2 n - 1 + B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 の意味は,1の補数表現の場合とほとんど同じですから, もうわかりますね. 最上位ビットが 0,すなわち, B n - 1 = 0 のとき, - B n - 1 × 2 n - 1 の値は 0 となります. 最上位ビットが 1,すなわち, B n - 1 = 1 のとき, - B n - 1 × 2 n - 1 の値は -2 n - 1 となります.したがって, 最上位ビットが 0 のとき,表現されている数値は,10進法で B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 となります.これは正の整数です. 数値の表現方法は符号無し整数の場合と同じです. 最上位ビットが 1 のとき,表現されている数値は,10進法で -2 n - 1 + ( B n - 2 × 2 n - 2 + + B 1 × 2 1 + B 0 × 2 0 ) となります.これは負の整数です.最上位ビットが 1 で, 他はすべて 0 のとき,そのビット列は最小値 -2 n - 1 を表します.

ビット単位の論理演算

~演算子(~ operator)は,4つある 単項算術演算子(unary arithmetic operator)のひとつです. このことはテキスト p.26 ですでに言及されていましたが, ここでようやく4つすべてを学習したことになります.

Column 7-3 で, 5 & 4 という式の評価結果がどうなるか述べられています. 前のセクションで学習した,3種類の符号付き整数の表現方法のどれを用いても, 5 および 4 の表現は同じです. 下位3ビットはそれぞれ,101 と 100 です. 最下位のビットは,一方が 1,もう一方が 0 ですから, 論理積をとると 0 になります. 次のビットは,両方とも 0 ですから,論理積をとると 0 になります. 次のビットは,両方とも 1 ですから,論理積をとると 1 になります. したがって,演算の結果は 100 となります. 式 5 & 45 && 4 の結果を比較するには, 以下のプログラムを実行するとよいでしょう.


#include <stdio.h>

int main(void)
{
  unsigned a, b;

  printf("非負の整数を2つ入力してください。\n");
  printf("a:"); scanf("%u", &a);
  printf("b:"); scanf("%u", &b);

  printf("a & b = %u\n", a & b);
  printf("a && b = %u\n", a && b);

  return 0;
}

2つの整数として5および4を入力すると, 以下のような結果が得られます.

bitwise AND operator and logical AND operator

論理和 5 | 4 と排他的論理和 5 ^ 4 の評価結果はどうなるか,考えてください.

シフト演算

正の整数 x<< 演算子 を適用した結果は必ず偶数となります. 正の整数 xn ビット左にシフトすると, x × 2 n となり,これは偶数です. このことは2進数で考えてもわかります. 偶数を2進数で表現すると,最下位ビットは 0 です. 奇数の最下位ビットは 1 です. どのような正の整数でも,ビットの左シフトを行えば, 最下位ビットは 0 になります. つまり,結果は偶数です.

正の整数 x が偶数であるとき, >> 演算子 を適用して n ビットの右シフトを行った結果は x ÷ 2 n の商の整数部となります. この計算の結果を整数の商と余りで表現すると, 右シフトによって消去されるビットが表す数値が余りとなります. たとえば,10進数の 23 に対して3ビットの右シフトをすることを考えてみます. 10進数の 23 を2進法で表現すると 10111 となります. 3ビットの右シフトを行った結果は2進法で 10 であり, これは10進数の 2 です.10進数の 23 を 2 3 で割ると,整数の商は 2 で,余りは 7 です. この余りは2進法で 111 ですから, 右シフトによって消去された下位3ビットと同じです.

演習 7-2 では, 符号なし整数を 255(8ビットの値がすべて1)として, 左右のシフトを1ビットから8ビットまで行ってみると, 結果がわかりやすいと思います. 左シフトは値が大きくなるので, 符号なし整数を 15(4ビットの値がすべて1)として, 1ビットから4ビットまでの左シフトを行ってもよいでしょう. この問題では2のべき乗での乗算と除算を行うので, 2のべき乗を計算する関数を最初に定義しておきます.

演習 7-3 から演習 7-5 は少し難しいです.

『解きながら学ぶC言語』に掲載されている演習 7-3 の解答では, 入力された回転数が負の数の場合と, unsigned 型のビット数を超える場合への対応を行っています. これら対応を行う代わりに, 回転数の範囲を指定するメッセージを提示して, その範囲にない値が入力されたら再入力を求めるようにしてもいいでしょう. その方がプログラムは簡単になります.

『解きながら学ぶC言語』に掲載されている演習 7-4 の解答では, 特定のひとつのビットだけが 1 である整数を生成しています. 整数 1 を表すビットを左シフトしていくと, 1, 10, 100, 1000, ... となります. 別解として,演習 7-2 で定義した, 2のべき乗を計算する関数を利用することができます. 2のべき乗は2進法で 1, 10, 100, 1000, ... となります.

『解きながら学ぶC言語』に掲載されている演習 7-5 の解答では, 第 pos ビットから第 pos + n - 1 ビットまでが 1 で, それ以外のビットが 0 である整数を生成しています. この生成方法はとてもエレガントです. 思いつかないかもしれません. この方法を思いつかなくても, 特定のひとつのビットだけが 1 である整数を使って, 指示されている関数を定義することが可能です. たとえば,set_n 関数は次のように定義できます.


unsigned set_n(unsigned x, int pos, int n)
{
  for ( ; n > 0; n--) {
    x = (x | power2(pos + n - 1));
  }
  return x;
}

ここで使用している power2 関数は,2のべき乗を計算する関数です. あらかじめ定義しておきます. この関数が返す値は unsigned int 型としてください. 特定のひとつのビットだけが 1 である整数の生成は, 2のべき乗を計算するかわりに, 1U のビットを左シフトすることでも実現できます. 『解きながら学ぶC言語』に掲載されている演習 7-4 の解答を参照してください.

整数定数

10進定数の構文図で先頭が「非 0 数字」と書かれているのは, 先頭が 0 だと8進定数になってしまうためです.

先頭が 0 だと8進定数になるということは, 一桁のゼロ(0)は8進定数であるということを意味します.

整数定数の型

整数接尾語(integer suffix)は, プログラム中で使うことのできる表記であって, キーボードからの入力としては使えません. これまでに何度か行ってきたように, 「整数を入力してください」と表示して整数の入力を促すとき, 整数接尾辞をつけて入力を行うことはできませんので注意してください.

整数接尾辞 U(u) と L(l) の順序は任意です. たとえば,3517UL と表記しても 3517LU と表記してもかまいません.

説明されていない,「整数リテラル」という語句が使われています. 文脈から判断がつくと思いますが, これは整数定数(integer constant)のことです. Java 言語と C++ 言語では整数リテラルという語句を使いますが, C言語の規格にこの語句はないので,整数定数と書くべきです.

「負の数を表す -10 は整数定数ではなく, 整数定数 10 に対して, 単項 - 演算子が適用された式である」という注意がなされています. どちらにせよ同じ数を表すことになるのだから, 注意すべきほどのことではないと思われるかもしれません. しかし,この違いに注意しなければならないこともあります. 負の数を2の補数で表現している環境で(ほとんどの環境が該当します), 以下のプログラムを実行してみてください. ただし,2147483647 という数値は int 型で扱うことのできる最大の整数(INT_MAX)であり, -2147483648 という数値は int 型で扱うことのできる最小の整数(INT_MIN)です. これは実行環境に合わせてください. このプログラムでは最初にこれらの値(INT_MAX および INT_MIN)を表示します. 負の数を2の補数で表現しているため, 最小の整数の絶対値は,最大の整数よりひとつ大きな値です.


#include <stdio.h>
#include <limits.h>

int main(void)
{
  printf("INT_MAX = %d\n", INT_MAX);
  printf("INT_MIN = %d\n", INT_MIN);

  printf("sizeof(int) = %u\n", (unsigned) sizeof(int));
  printf("sizeof(long) = %u\n", (unsigned) sizeof(long));

  printf("sizeof(2147483647) = %u\n", (unsigned) sizeof(2147483647));
  printf("sizeof(-2147483648) = %u\n", (unsigned) sizeof(-2147483648));
  printf("sizeof(-2147483647-1) = %u\n", (unsigned) sizeof(-2147483647-1));

  return 0;
}

下図はプログラムを実行させたときの出力例です.

整数定数の格上げ

この環境では,int 型の大きさ sizeof(int) は 4, long 型の大きさ sizeof(long) は 8 となっています. 2の補数を使用して負の数を表現する場合, 4ビットで表現できる整数の値は, -2147483648 から 2147483647 となります. 最大値 2147483647 の大きさである sizeof(2147483647) は 4 となっていますから, この最大値はたしかに int 型で表現されていることがわかります. ところが,最小値 -2147483648 の大きさである sizeof(-2147483648) は 8 となっています. これは long 型の大きさです.なぜ int 型で表現されないのでしょうか? これは,-2147483648 という表記が整数定数ではなく, 整数定数に単項 - 演算子が適用された式 -(2147483648) だからです. この式を評価するとき,2147483648 は int 型で表現できませんから, long 型に格上げされます. それから単項 - 演算子が適用されたとき,型は long 型のままとなるのです. 出力の最後に示されているように, sizeof(-2147483647-1) は 4 となっていて,整数型で表現されています. この式は sizeof(-(2147483647) - 1) という意味になり, 2147483647 は int 型で表現されます. この数に単項 - 演算子を適用し, それから 1 を引いた数は -2147483648 で,これは int 型で表現できます. このプロセスでは整数の格上げは必要ありません.

整数の表示

List 7-8 の print_nbits 関数の中に,


i = (n < i) ? n - 1 : i - 1;

という式があります. 条件演算子(? :)と単純代入演算子(=)では, 条件演算子の方が実行の優先順位が上です. そのため,この式は


i = ((n < i) ? n - 1 : i - 1);

と解釈されます.つまり,最初に右辺の条件式が評価され, その結果が左辺の i に代入されます. 演算子の優先順位については 7-4 節で学習します.

List 7-8 の main 関数では, printf 関数の引数である文字列において,出力する半角の空白を忘れずに入れてください. 最初の printf 関数では,%5u の後に半角のスペースを空けます. 次の printf 関数では,%06o の前と後に半角のスペースを空けます.

オーバーフローと例外

符号無し整数型で表現された整数の演算を実行したとき, その型で表現可能な範囲を超えた場合の動作は, 符号無し整数のラップアラウンド(unsigned integer wrapping)と呼ばれます. 奇妙な動作に思えますが,C言語の仕様として定められています.

演習室の環境では, unsigned int 型で表現できる最大値(UINT_MAX)は 4294967295 です. 演習 7-6 を行うときには, 演算の結果がこの最大値を超えるようにしてください. 以下のプログラムは解答例です(『解きながら学ぶC言語』の解答とは異なります).


#include <stdio.h>
#include <limits.h>

int main(void)
{
  unsigned i;

  printf("UINT_MAX = %u\n", UINT_MAX);
  
  for (i = 1U ; i <= 10; i++)
    printf("UINT_MAX + %u = %u\n", i, UINT_MAX + i);

  for (i = 0U ; i <= 10; i++) 
    printf("UINT_MAX + UINT_MAX + %u = %u\n", i, UINT_MAX + UINT_MAX + i);
  
  return 0;
}

演習室の環境でこのプログラムを実行すると, 以下のような出力がなされます.

ex0706

7-3 浮動小数点型

浮動小数点型

テキスト p.198 の最終行で, Fig.7-19 と書かれているのは Fig.7-20 の誤植です(初版).

List 7-9 で long double 型の変数 c の値を出力するときの変換指定が %lf となっていますが(初版),これは %Lf の誤植です. テキスト p.354-357 にある printf 関数の仕様で, (d) 変換修飾子 の項目を見てください. 対応する実引数の型が long double 型であることを指定する変換修飾子は L(小文字でなく大文字)です.

浮動小数点定数

テキスト p.200 の, 整数部や小数部を省略した浮動小数点定数(floating-point constant)の例で, 1L という表記を long double 型の 1.0 としているのはおかしいです(初版). ここには誤植がありますね. テキスト p.194-195 で解説しているように, 1L という表記は long int 型の 1 になります. 浮動小数点定数としたいのなら 1.0L とするのが通常の表記ですが, ここでは 1.L という表記を例示したかったのでしょう. テキスト p.200 にある浮動小数点定数の構文図を見てください. 整数部,小数点,浮動小数点接尾語という表記が許されています.

浮動小数点接尾語(floating suffix)は, プログラム中で使うことのできる表記であって, キーボードからの入力としては使えません. この点は整数接尾語(テキスト p.195)と同じです.

浮動小数点定数の型に応じた,printf 関数および scanf 関数での変換指定は,下の表のようになります.

関数float 型double 型long double 型
printf %f %f %Lf
scanf %f %lf %Lf

<math.h> ヘッダ

<math.h> ヘッダと <stdio.h> ヘッダを include する順序は任意です. List 7-10 では最初に <math.h> ヘッダを include していますが, 逆でも問題ありません.

繰り返しの制御

Fig. 7-22 の List 7-11 [改] のようにプログラムが終了しなくなったとき, プログラムの実行を止める必要があります. Mac のターミナルでは, コントロール [Ctrl] キーを押しながら [C] キーを押してください. Windows のコマンドプロンプトの場合も同じです.

List 7-12 の for 文で,変数 i を 100.0 で割る式は, i / 100 としないように注意してください. 整数を整数で割る計算になるので, 小数点以下が切り捨てられてしまいます(テキスト p.23 参照).

演習 7-11 で計算しようとしている, 0.00 + 0.01 + + 1.00 の正しい値は 50.5 です.高校数学で学習する,等差数列の和です.

7-4 整数型と文字型

演算子の優先順位と結合性

このセクションについての補足説明は現在のところありません.

型変換の規則

このセクションについての補足説明は現在のところありません.