プログラミングC
第12回 演習問題


すべての問題について、特別の指示が無い限り外部変数は使用しないで下さい。

A問題(50点)

問題1(マクロ関数)

下記のマクロ関数を使ったプログラムは、引数の与え方によって正しく動作しない場合がある。

#include <stdio.h>

#define func(x)  x*x-4*x+4

int main()
{
  int a = 2;
  double b = 1.5;

  printf("a=%d, b=%f\n", a, b);
  printf("func(a) = %d\n", func(a));
  printf("func(a+2) = %d\n", func(a + 2));
  printf("func(a)*4 = %d\n", func(a) * 4);
  printf("func(b) = %f\n", func(b));
  printf("func(b+2.5) = %f\n", func(b + 2.5));

  return 0;
}
実行結果(一部の結果は正しくない)
% ./a.out
a=2, b=1.500000
func(a) = 0
func(a+2) = 6     ←???
func(a)*4 = 12    ←???
func(b) = 0.250000
func(b+2.5) = 8.250000     ←???
  1. 上記のプログラムでは、func(a) は a*a-4*a+4 に置き換えられる。 では、func(a+2) や func(a)*4 がどんな式に置き換えられるか考えて、 正しくない実行結果が出力される理由を prog01.txt に記述しなさい。
    例:
    元のプログラムでは、
    func(a+2)は、"どのような式に置き換えられ、どのような答えが得られるのか?"
    正しい結果を得るためには、"func(x)をどのようにすればいいのか?"
    そうすれば、func(a+2)は、"どのような式に置き換えられ、どのような答えが得られるのか?"
    となるから、正しい結果を得られるはずである。
  2. プログラムが正しく動作するように、マクロの定義のみを修正し、 prog01.c というファイル名で保存しなさい。 ただし、引数に自己の値を変更するような式(インクリメント、デクリメント、i=i+2のような式) は含まれないと仮定してよい。
(提出ファイル名:prog01.txt, prog01.c)

正しい実行例
% ./a.out
a=2, b=1.500000
func(a) = 0
func(a+2) = 4
func(a)*4 = 0
func(b) = 0.250000
func(b+2.5) = 4.000000

問題2(条件コンパイル)

問題の前に、講義Lec12-12~16 (p140~141)で学んだこと(条件コンパイル)の確認を行う。 こちらのプログラム は、キーボードから奇数を入力すると、 その数以下の正の奇数の和を計算して表示するプログラムである。 (例えば、9 を入力すると 1 + 3 + 5 + 7 + 9 = 25 を表示する)
このプログラムを、以下の4通りのやり方でコンパイルして実行し、動作の違いを確かめなさい。

  1. gcc でそのままコンパイルする
  2. gcc-DDEBUG というオプションを付けてコンパイルする
  3. プログラム中のマクロ JAPANESE の値を 0 に変えて保存し、コンパイルする
  4. プログラム中のマクロ JAPANESE の値を 0 に変えて保存し、さらに -DDEBUG オプションを付けてコンパイルする
コンパイル・実行の例(1の場合)
% gcc compile_sample.c
% ./a.out
奇数を入力してください: 9
1 から 9 までの奇数の和 = 25

他の3つの場合も同じように試し、それぞれの表示結果とプログラムを見比べ、 コンパイル時にプログラムのどの部分が有効になっているか確認すること。
#if, #ifdef, #else 等の機能を理解できたら、以下の問題に進みなさい。


プログラム lec12-6a.clec12-6b.c とヘッダファイル lec12-4.h は第12回のハンドアウト(p142~143)に出てきた分割コンパイルの例である。 まず、各ファイルを

  1. ヘッダファイルは、 prog02_header.h
  2. メイン試験用プログラムは、prog02_main.c
  3. calcarea試験用プログラムは、prog02_calc.c
という名前で自分のディレクトリにコピーした上で、以下の課題を行いなさい。

  1. まず、ファイル名を変えてコピーしたことに伴い、プログラム中に出てくるファイル名も、 変更が必要なものは変更しなさい。
  2. prog02_main.cprog02_calc.c では、単体テスト用に追加された部分が常に有効になっているため、これらを結合して 全体の動作確認を行うことができない (両方一緒にコンパイルすると、同じ名前の関数が二つあるのでエラーになる)。 そこで、単体テスト用の部分が、コンパイル時にマクロ定義のオプションを付けた時のみ有効 になるように修正して、単体テストと結合テストが同じソースコードのままで行えるように書き改めなさい。
  3. 書き改めたソースコードで、単体テスト/結合テストを行う際のそれぞれのコンパイル方法を prog02.txt に書きなさい。

(解答ファイル名:prog02_header.h, prog02_main.c, prog02_calc.c, prog02.txt)

実行例(prog02_main.cの単体テスト)
% ./a.out
0 0 0 1 1 0
0 0
0 1
1 0
Area = 0.000000
実行例(prog02_calc.cの単体テスト)
% ./a.out
Area = 0.500000
実行例(結合テスト)
% ./a.out
0 0 0 1 1 0
Area = 0.500000

B問題(50点)

問題3(分割コンパイルによるプログラム開発)

以下のプログラム prog03_main.c は、関数 input_planet によって太陽系の惑星のデータ planet.txt を Planet型の構造体配列 psolsys[] に読み込み、表示を行うためのものである。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "prog03.h"

int main() {
  int i;
  Planet psolsys[NPLANETS];

  if( input_planet( psolsys ) != 0 ){
    fprintf(stderr, "Input Error!\n");
    exit(3);
  }

  for (i = 0; i < NPLANETS; i++) {
    printf("Name: %s\n", psolsys[i].name);
    printf("  Radius: %.0f [km]\n", psolsys[i].radius);          /* 半径 */
    printf("  Density: %.2f [g/cm3]\n", psolsys[i].density);     /* 密度 */
    printf("  Orbital Radius: %.3e [km]\n", psolsys[i].orbital_radius);   /* 軌道半径 */
  }
		  
  return 0;
}

#ifdef TEST0 /* 単体テスト用 input_planet 正常動作(戻り値 0 )の場合 */
int input_planet(Planet *planet) {
  int i;
  for (i = 0; i < NPLANETS; i++) {
    strcpy(planet[i].name, "Sample");     /* テスト用のサンプルデータ */
    planet[i].radius = 1000;
    planet[i].density = 1.00;
    planet[i].orbital_radius = 100000000;
  }
  return 0;
}
#elif TEST1 /* 単体テスト用 input_planet 異常動作(戻り値 -1 )の場合 */
int input_planet(Planet *planet) {
    return -1;
}
#endif
正しくデータを読み込めたときに想定される実行結果:
Name: Mercury
  Radius: 2440 [km]
  Density: 5.43 [g/cm3]
  Orbital Radius: 5.790e+07 [km]
Name: Venus
  Radius: 6052 [km]
  Density: 5.24 [g/cm3]
  Orbital Radius: 1.082e+08 [km]
Name: Earth
  Radius: 6378 [km]
  Density: 5.52 [g/cm3]
  Orbital Radius: 1.496e+08 [km]
Name: Mars
  Radius: 3396 [km]
  Density: 3.93 [g/cm3]
  Orbital Radius: 2.279e+08 [km]
Name: Jupiter
  Radius: 71492 [km]
  Density: 1.33 [g/cm3]
  Orbital Radius: 7.783e+08 [km]
Name: Saturn
  Radius: 60268 [km]
  Density: 0.69 [g/cm3]
  Orbital Radius: 1.429e+09 [km]
Name: Uranus
  Radius: 25559 [km]
  Density: 1.27 [g/cm3]
  Orbital Radius: 2.875e+09 [km]
Name: Neptune
  Radius: 24764 [km]
  Density: 1.64 [g/cm3]
  Orbital Radius: 4.504e+09 [km]

しかし、関数 input_planet はまだ実装されておらず、 代わりに単体テスト用のダミーのコード(#ifdef 以降の部分; 実際に読み込みはしていない)が prog03_main.c の末尾に付加されている。

0.(準備)
まず、ファイル prog03_main.cprog03.hplanet.txt を自分の作業ディレクトリにコピーしておくこと。

1.
コンパイル時のマクロ定義を利用して、prog03_main.c 単体で動作テストを行う際のコンパイル方法を prog03.txt に書け。その際、関数 input_planet戻り値が 0 の場合(正常)と -1 の場合(異常)の二通りの動作 を確認する方法を調べること。

プログラム中で使われている #elif などの条件コンパイルのための マクロ・プリプロセッサについては、ハンドアウト Lec12-12, 13,教科書(応用編)p.58 や C言語入門講座 9.プリプロセッサとメイクファイル などを参考にすること。

コンパイルできた時の実行例(関数input_planetの戻り値 0 の場合)
% ./a.out
Name: Sample
  Radius: 1000 [km]
  Density: 1.00 [g/cm3]
  Orbital Radius: 1.000e+08 [km]
  (以下同じ出力の繰り返し)
コンパイルできた時の実行例(関数input_planetの戻り値 -1 の場合)
% ./a.out
Input Error!
%

2.
prog03_input.c に、下記の仕様に基づいてファイルからデータ読み込みを行う正規の関数 input_planet を作成せよ。 さらに、prog03_main.c と prog03_input.c を組み合わせて全体での動作テストを行う際のコンパイル方法を prog03.txt に書け

なお、prog03_main.c と prog03.h の内容を変更してはならない。

(解答ファイル名:prog03_input.c, prog03.txt

Extra問題

問題4

以下のプログラムでは、2つの三角形についてそれぞれの3頂点の平面座標を キーボードから入力して構造体に格納し、面積を求める。ただし面積が 0 の場合は 3点が三角形を成していないので、再度頂点の座標の入力を促す。
三角形2つの座標が入力できたら、どちらの三角形の面積が大きいか判定して表示し、 さらに2つの三角形それぞれの重心を求めて表示する。 この未完成のプログラムを下記の指示にしたがって完成させよ。
#include <stdio.h>
#include "prog04_1.h"     /* (1) */
/* 必要に応じて追加、修正してもよい */

int main() {     /* (2) */
  /* 必要に応じてコードを追加 */

  /* 1つ目の三角形の3頂点の座標を入力する */
  InputTriangle( );     /* 引数・戻り値は適宜設計せよ (3) */
  /* 1つ目の三角形の面積を求める */
  CalcArea( );     /* 引数・戻り値を入れる変数等は適宜設計せよ (4) */
  
  /* CalcAreaの結果をもとに、先へ進むか,再入力を求める */
  
  /* 2つ目の三角形の3頂点の座標を入力する */
  InputTriangle( );   /* 引数・戻り値は適宜設計せよ (3) */
  /* 2つ目の三角形の面積を求める */
  CalcArea( );   /* 引数・戻り値を入れる変数等は適宜設計せよ (4) */

  /* CalcAreaの結果をもとに、先へ進むか,再入力を求める */
  

  printf( /* CalcAreaで求めた三角形の面積を2つ表示 */ );

  /* 面積の大きい方を判定して、表示 */

  /* 1つ目の三角形の重心を求める */
  CalcCentroid( );   /* 引数・戻り値等は適宜設計せよ (5) */
  /* 2つ目の三角形の重心を求める */
  CalcCentroid( );   /* 引数・戻り値等は適宜設計せよ (5) */
  
  printf( /* CalcCentroidで求めた三角形の重心2つを表示 */ );

  return 0;
}
実行例(出力内容を厳密にこの例に合わせる必要はない)
% ./a.out
Input 1st triangle:
Vertex 1: 2.0 2.0
Vertex 2: 2.0 8.0
Vertex 3: 2.0 12.0
These points do not form a triangle!
Again, input 1st triangle:
Vertex 1: 1.0 3.0
Vertex 2: 4.0 9.0
Vertex 3: 7.0 2.0
Input 2nd triangle:
Vertex 1: 0.0 0.0
Vertex 2: 4.0 0.0
Vertex 3: 2.0 9.0

Area of 1st one: 19.500   Area of 2nd one: 18.000
The 1st one is larger than the 2nd one!

Centroid of 1st one:  (4.000, 4.667)
Centroid of 2nd one:  (2.000, 3.000)
必要な作業は以下の通りである。
(1)
必要な構造体宣言、関数のプロトタイプ宣言を含むヘッダファイル prog04_1.h の作成
(2)
上記の main関数を含むプログラム本体 prog04_2.c の完成。 (上記未完成プログラムに含まれている関数の削除や外部変数の使用は禁止する。 関数の実行位置の変更や引数、戻り値は任意に決めてよい。 また、必要な変数・if や for などの制御文の追加、他の関数の追加も自由にしてよい)
(3)
3頂点の平面座標をキーボード入力から受け取り構造体に格納する関数 InputTriangle の作成(prog04_3.c
(4)
三角形の面積を求める関数 CalcArea の作成(prog04_4.c
(5)
三角形の重心を求める関数 CalcCentroid の作成(prog04_5.c
(6)
このプログラムの実行可能形式ファイルを作成するために必要なコンパイル、リンクの手順を 簡潔に記述する prog04_compile.txt の作成

(解答ファイル名:prog04_1.h, prog04_2.c, prog04_3.c, prog04_4.c, prog04_5.c, prog04_compile.txt)