情報処理4レポート課題(8)の解答例

更新:2008年11月18日


  1. 次のプログラムは「1行に国名と地域名が記述されているテキストファイルから、 国名と地域名を独立に文字型配列に格納して画面表示します。
    file_fgets_sscanf_buffer.c
    #include<stdio.h>
    #include<stdlib.h>
    
    #define BUF_SIZE	1024
    #define NATION_SIZE	20
    #define REGION_SIZE	20
    
    int main(void){
    	FILE *fp;
    	char *ch_fgets;
    	int ch_sscanf;
    	char region[REGION_SIZE], nation[NATION_SIZE];
    	char buf[BUF_SIZE];
    
    	fp=fopen("for_file5.5.1buffer.txt", "r");
    	if(fp==NULL){
    		printf("Can't Open File\n");
    		exit(1);
    	}
    	while(1){
    		ch_fgets=fgets(buf, BUF_SIZE, fp);
    		if(ch_fgets==NULL){
    			break;
    		}
    		ch_sscanf=sscanf(buf, "%s%s", 
    			nation, region);
    		if(ch_sscanf!=2){
    			printf("Read Error\n");
    			break;
    		}
    		printf("%s\t%s\n", nation, region);
    	}
    	fclose(fp);
    	return 0;
    }
    
    for_file5.5.1buffer.txt
    Algeria	Africa
    Argentina	Latin-America
    Australia	Oceanea
    Austria	Europe
    Bangladesh	Asia
    Belgium	Europe
    Bolivia	Latin-America
    Brazil	Latin-America
    Bulgaria	Europe
    Burkina-Faso	Africa
    Cameroon	Africa
    Canada	North-Americal
    Chile	Latin-America
    China	Asia
    Colombia	Latin-America
    Cote-d'Ivoire	Africa
    Denmark	Europe
    Ecuador	Latin-America
    Egypt	Africa
    Ethiopia	Africa
    Finland	Europe
    France	Europe
    Germany	Europe
    Ghana	Africa
    Greece	Europe
    Guatemala	Latin-America
    Hungary	Europe
    India	Asia
    Indonesia	Asia
    Iran	Asia
    Ireland	Europe
    Israel	Asia
    Italy	Europe
    Japan	Asia
    Kazakstan	Asia
    Kenya	Africa
    Korea(South)	Asia
    Kuwait	Asia
    Madagascar	Africa
    Malawi	Africa
    Malaysia	Asia
    Mali	Africa
    Mexico	Latin-America
    Morocco	Africa
    Mozambique	Africa
    Nepal	Asia
    Netherland	Europe
    New-Zealand	Oceanea
    Niger	Africa
    Nigeria	Africa
    Norway	Europe
    Pakistan	Asia
    Peru	Latin-America
    Philippines	Asia
    Poland	Europe
    Portugal	Europe
    Rumania	Europe
    Russia	Europe
    Singapore	Asia
    South-Africa	Africa
    Saudi-Arabia	Asia
    Spain	Europe
    Sri-Lanka	Asia
    Sweden	Europe
    Switzerland	Europe
    Tanzania	Africa
    Thailand	Asia
    Tunisia	Africa
    Turly	Asia
    Uganda	Africa
    Ukraine	Europe
    U.K.	Europe
    U.S.A.	North-America
    Venezuela	Latin-America
    Vietnam	Asia
    Yemen	Asia
    Zambia	Africa
    Zimbabwe	Africa
    
    本来、文字型配列の大きさは十分に確保しておく必要がありますが、 十分に確保できていなくてもコンパイルエラーになりませんし、 実行時に必ずエラーになる訳でもなく、 密かに不思議な挙動を示している場合があり、 誤りが見つけにくくなります。 ここでは、 十分に確保できていない場合にどのような挙動を示すかを確認します。
    1. 国名用の要素数を減らしていって画面表示に異常を来すことを確認してください。
      たとえば、NATION_SIZEを3に設定した場合の画面表示を以下に示します。
      AlgeAfrica      Africa
      ArgeLatin-America       Latin-America
      AustOceanea     Oceanea
      AustEurope      Europe
      BangAsia        Asia
      BelgEurope      Europe
      BoliLatin-America       Latin-America
      BrazLatin-America       Latin-America
      BulgEurope      Europe
      BurkAfrica      Africa
      CameAfrica      Africa
      CanaNorth-Americal      North-Americal
      ChilLatin-America       Latin-America
      ChinAsia        Asia
      ColoLatin-America       Latin-America
      CoteAfrica      Africa
      DenmEurope      Europe
      EcuaLatin-America       Latin-America
      EgypAfrica      Africa
      EthiAfrica      Africa
      FinlEurope      Europe
      FranEurope      Europe
      GermEurope      Europe
      GhanAfrica      Africa
      GreeEurope      Europe
      GuatLatin-America       Latin-America
      HungEurope      Europe
      IndiAsia        Asia
      IndoAsia        Asia
      IranAsia        Asia
      IrelEurope      Europe
      IsraAsia        Asia
      ItalEurope      Europe
      JapaAsia        Asia
      KazaAsia        Asia
      KenyAfrica      Africa
      KoreAsia        Asia
      KuwaAsia        Asia
      MadaAfrica      Africa
      MalaAfrica      Africa
      MalaAsia        Asia
      MaliAfrica      Africa
      MexiLatin-America       Latin-America
      MoroAfrica      Africa
      MozaAfrica      Africa
      NepaAsia        Asia
      NethEurope      Europe
      New-Oceanea     Oceanea
      NigeAfrica      Africa
      NigeAfrica      Africa
      NorwEurope      Europe
      PakiAsia        Asia
      PeruLatin-America       Latin-America
      PhilAsia        Asia
      PolaEurope      Europe
      PortEurope      Europe
      RumaEurope      Europe
      RussEurope      Europe
      SingAsia        Asia
      SoutAfrica      Africa
      SaudAsia        Asia
      SpaiEurope      Europe
      Sri-Asia        Asia
      SwedEurope      Europe
      SwitEurope      Europe
      TanzAfrica      Africa
      ThaiAsia        Asia
      TuniAfrica      Africa
      TurlAsia        Asia
      UganAfrica      Africa
      UkraEurope      Europe
      U.K.Europe      Europe
      U.S.North-America       North-America
      VeneLatin-America       Latin-America
      VietAsia        Asia
      YemeAsia        Asia
      ZambAfrica      Africa
      ZimbAfrica      Africa
      
      以上のように、国名表示部分の先頭4文字について表示された後に続いて地域名が表示されてしまっています。
    2. 次の文章の(a)から(c)までに適切な問言を挿入して下さい:

      for_file5.5.1buffer.txtの1列目,すなわち, nationに格納される国名の最大長は(a)なので, 終端文字を含めて(b)個の要素が確保されていれば 正常に動作するはずです.

      逆にいえば,nationの要素数を(c)以下にしたら 何か不具合があってもおかしくありません.


      for_file5.5.1buffer.txtの1列目,すなわち, nationに格納される国名の最大長は13なので,終端文字を含めて14個の要素が確保されていれば正常に動作するはずです. 逆にいえば,nationの要素数を13以下にしたら何か不具合があってもおかしくありません. しかし, nationの要素数を13以下にしたら常に不具合が起きるわけでもありません.
    3. 国名用の要素数を変化させていったときに, nationとregionの先頭アドレス,その差がどうなるかについて, 次の表を埋めて下さい.
      国名用の要素数regionの先頭アドレスnationの先頭アドレスregionの先頭アドレスとnationの先頭アドレスとの差
       1   
       2   
       3   
       4   
       5   
       6   
       7   
       8   
       9   
       10   
       11   
       12   
       13   
       14   
       15   
       16   
       17   

      次のプログラムを用います:
      report8-1-3.c
      #include<stdio.h>
      #include<stdlib.h>
      
      #define BUF_SIZE	1024
      #define NATION_SIZE	1
      #define REGION_SIZE	20
      
      int main(void){
      	FILE *fp;
      	char *ch_fgets;
      	int ch_sscanf;
      	char region[REGION_SIZE], nation[NATION_SIZE];
      	char buf[BUF_SIZE];
      
      	fp=fopen("for_file5.5.1buffer.txt", "r");
      	if(fp==NULL){
      		printf("Can't Open File\n");
      		exit(1);
      	}
      	while(1){
      		ch_fgets=fgets(buf, BUF_SIZE, fp);
      		if(ch_fgets==NULL){
      			break;
      		}
      		ch_sscanf=sscanf(buf, "%s%s", 
      			nation, region);
      		if(ch_sscanf!=2){
      			printf("Read Error\n");
      			break;
      		}
      	}
      	printf("%d\t%p\t%p\t%d\n", NATION_SIZE, (void *)region, (void *)nation, region-nation);
      	fclose(fp);
      	return 0;
      }
      
      国名用の要素数regionの先頭アドレスnationの先頭アドレスregionの先頭アドレスとnationの先頭アドレスとの差
      10xbfffe0900xbfffe08f1
      20xbfffdd100xbfffdd0e2
      30xbfffd9900xbfffd98016
      40xbffff6100xbffff60c4
      50xbffff2900xbffff28016
      60xbfffef100xbfffef0016
      70xbfffeb900xbfffeb8016
      80xbfffe8100xbfffe8088
      90xbfffe4900xbfffe48016
      100xbfffe1100xbfffe10016
      110xbfffdd900xbfffdd8016
      120xbfffda100xbfffda0016
      130xbffff6900xbffff68016
      140xbffff3100xbffff30016
      150xbfffef900xbfffef8016
      160xbfffe7100xbfffe70016
      170xbfffeb100xbfffeaf032
    4. 次の(a)から(z)および(alpha)から(zeta)までに適切な問言を挿入して下さい.

      nationの要素数を2に設定し,AlgeriaとAfricaを読み込んだ場合の動作を説明します. sscanf関数によってあるアドレスから順に 文字(a),(b),(c),(d),(e),(f),(g),(h)が格納されていき, nationから(i)バイト後ろから 文字(j),(k),(l),(m),(n),(o),(p)が格納されていきます. よって, 文字(q),(r),(s),(t),(u),(v)が格納された場所に 文字(w),(x),(y),(z),(alpha),(beta)が上書きされたことになります. printf関数でnationを画面表示する際には, nation[(gamma)]に格納されている文字(delta)から文字(epsilon)の直前までの 文字列(zeta)が表示されることになります.


      nationの要素数を2に設定し,AlgeriaとAfricaを読み込んだ場合の動作を説明します. sscanf関数によってあるアドレスから順に 文字'A', 'l', 'g', 'e', 'r', 'i', 'a', '\0'が格納されていき, nationから2バイト後ろから 文字'A', 'f', 'r', 'i', 'c', 'a', '\0'が格納されていきます. よって, 文字'g', 'e', 'r', 'i', 'a', '\0'が格納された場所に 文字'A', 'f', 'r', 'i', 'c', 'a'が上書きされたことになります. printf関数でnationを画面表示する際には, nation[0]に格納されている文字'A'から文字'\0'の直前までの 文字列AlAfricaが表示されることになります.

    5. 異常表示の原因を説明してください。 (ヒント: (0)sscanf関数の%s変換は文字列を格納するもので、 先頭文字から区切り文字(空白文字や改行)の直前までを、 指定されたアドレスから順次格納していきます。 このときsscanf関数は、格納先である文字型配列の要素数が幾つかを一切気にしていません。 (1)printf関数の%s表示は文字列を表示するもので、 指定された文字列の先頭(アドレス)から終端文字の直前までをそれぞれ文字として表示しようとします。(2)文字型配列も配列の一種で、要素数分の領域がメモリ内に連続して確保されます。)
      画面表示の異常は,格納領域が重複してしまったことが原因です. 格納領域が重複しているのはregion-nationが1,2,4,8なので、 そのときに正しく画面表示できなくなっています。
    6. このような誤りを完全に防ぐためには、 sscanf関数を用いて入力する文字型配列の要素数をどのように決めれば良いかを 考察してください。

      このような誤りを防ぐには,読み込まれるべき文字数を保持するために十分な大きさの要素数を設定すれば良いことになります.読み込む対象はfgets関数で読み込んだ文字列なので, nationもregionもその要素数をBUF_SIZEにすれば十分です。

      ちなみに,region-nationにはある規則性が現れていますが,必ずこの規則にしたがうわけではないので,この規則を覚える必要はありません.むしろ,独立な変数(や配列)のアドレス配置に規則性があることを前提にプログラミングしてはいけません.


以下は、皆さんの幾つかの考察・感想に対するコメントです。
問題4について、(w)からのAfricaの後になぜヌル文字が入らないのか分からなかった。
その前の記述「文字(q)...が格納された場所」の場所の数に合わせると'A','f','r','c','a'までしか入りません。
プログラム中では、 char region[REGION_SIZE], nation[NATION_SIZE]; と宣言されていますが、宣言の順番は配列のアドレスの順番に関係するのでしょうか?
関係します。 試しに宣言の順を逆にしてアドレスを表示してみて下さい。
問題5の異常表示とは、#defineの値よりも多くの文字が表示されてしまう事を説明すれば良いのでしょうか? それとも、#defineを3に設定したときに正常に表示された事の説明でしょうか?
主眼は前者です。しかし、その原因追求の際に後者についても疑問が生じるはずで、 結果的には両方の原因を説明することになります。
sscanfの動作には悩まされます。今後、このようなプログラムを作る時は徹底的に動作の確認をしなければいけないのでしょうか?
本レポート課題1-6の解答を守っている限りにおいては、 その動作確認の程度はそれほどでなくても良いと思われます。 入力のエラーに完全対処することは非常に難しいので、 ある程度「入力は正しい」といった宗教めいた割り切りも 心の平穏には必要です。
なんで2の累乗の時変な風になるんだろう
コンパイラがそのようにメモリを割り付けるからです。 詳しくは計算機アーキテクチャやコンパイラ関連の書籍・講義でキーワード「アラインメント」で参照して下さい。 基本的には組み込み型char(1バイト),short(2バイト),int(この計算機では4バイト),double(8バイト)が多用されることを基にしています。
sscanfの動作や仕様について取り扱っているから、C言語の範疇を外れた学習内容ではないのは把握しているが、 ここまで深くエラーを考察する必要性には疑問を感じる。 限られた時間の中での授業なのだから、このようなことよりももっと別のことをやった方がよいのではないだろうか。例えば構造体とか。 そもそもこの授業が目指すものは何なのだろうか? CやC++で実用的なプログラムを僕らが作って、それを世の中の人たちに使ってもらうことなのか。 それとも、ハードウェア内での挙動を勉強して、現存する言語に変わる何か新しいプログラミング言語を生み出すことなのか。 カプセル化という言葉が存在するように、それに近い概念としてC言語には関数があるが 仮に目指すものが前者だとしても、sscanf関数というカプセルの成分までを知る必要はあるのだろうか? 薬を正しい方法で飲んで、望まれる効果が得られればそれでいいと思う。 薬の副作用を抑える努力をするのは言語開発者の役目であって、開発者やエンドユーザの役目ではないはず。 この両方に手を出すというのは、何か薄っぺらくなってしまうような気がしてならない。 スポーツでもプロ野球選手かつサッカー選手というのはいないように。(←極端な例えではあるが・・・) 目指すならどちらかに狙いを定めた方がよいような気がする。 それともそんなことはないと思いますか? やはり自分の考え方が薄っぺらいだけですか? 極端かつ失礼な聞き方かもしれませんが、 神澤先生はこの授業を通して、学生に何ができるようになってほしいのですか? この答えをぜひ教えてください。 ※ちなみに自分はプログラミングが嫌いだとかそういうことではありません。(むしろ好きな方です。) 何か授業に不平不満があるということではなく、少し方向性がずれてきているように感じたことから生じた疑問です。
この科目を履修することを通して、 良いプログラミングができるための基礎を学んで欲しいと思っています。 ここでいう良いプログラミングとは
  1. バグのないプログラム作成だけでなく、
  2. バグを含むプログラムの原因追求と解決
も含みます。今回のレポート課題は(2)に相当します。 しばらくプログラム作成から離れてしまっているので 違和感を感じるのかも知れませんが、 あくまでもプログラミングの範疇だと思っています。 ちなみに次のレポート課題も残念ながらプログラム作成ではありませんが、 重要なことなのでしかたがありません。
家のPCにubuntuのlinuxを入れてみたのですが最初にCの標準ライブラリがいなくて少し焦りました
Linuxのディストリビューションによってはデフォルトインストールの場合にそうなることがあるようですね。 あらためてインストールすればうまくいったかと思います。 色々といじってみて下さい。
Gmailが使いづらいです。
どのように使いづらいかを説明してくれると何か解決策を回答できるかも知れません。
最近、首・肩・腰が毎日のようにボキボキ鳴って、疲れやすくなってしまったのですが何か良い解消策はないでしょうか?
ボキボキなるのは動かす頻度が落ちているから、疲れやすくなったのは筋力が落ちたからだと思われます。柔軟体操と筋力トレーニングをお勧めします。

何かありましたら、 まで。