• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:C言語に関する質問)

C言語初心者のためのファイル中の英文の単語出現頻度をカウントするコードの修正方法

このQ&Aのポイント
  • C言語初心者の方が「ファイル中の英文を単語に分けてその出現頻度をカウントするコードを木構造を用いて出力せよ」という課題に取り組んでいます。参考にしたコードのURLと注意事項を紹介し、警告メッセージが出た場合の修正方法を質問しています。
  • コードの内容は、ファイルを読み込んで単語に分割し、その出現頻度をカウントするための木構造を作成するものです。しかし、コンパイル時にポインタと整数の比較に関する警告が出ており、そのまま実行するとセグメンテーションフォルトが発生します。
  • 質問者は、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と考えています。しかし、正しい修正方法を知りたいと質問しています。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.8

> #4 #1です。ご指摘、ごもっとも。自動変数が初期化されるシステムでもダメですね。 > 質問者 warningはコンパイラからのツッコミなので文法的に是正していけばいいですが、segmentation faultや一般保護エラーは実行環境(OSとか)からのツッコミですので、実行環境であなたのプログラムが何か「悪いこと」をやらかしているわけです。 それが何かを理解する必要があるので、C言語の文法的な問題ではないのかもしれないという予想と、実行環境が何を嫌がっている(何をしたらシステムが発狂する)かを知るべきです。 ちなみにOSがないシステム(組み込み機器とか)なら誰からもツッコミが入ることなく、ただ暴走もしくはフリーズするだけですがね。 「int a;」という記述はコンパイラによって、とあるRAM領域のどこかにint型の値を記録できる領域を確保して、そのアドレスに紐付けされたaというラベルを提供するという処理に置き換えると考えることができます。そして「a=1;」は、aというラベルに紐付けされているRAM領域に1が書き込まれます。(感覚的な表現であって、アセンブリ言語レベルでは違う処理かもしれませんが。) それに対し「int *a;」という記述は、とあるRAM領域のどこかに「int型の値を記録できる領域」のアドレスを記録できる領域を確保して、そのアドレスに紐付けされたaというラベルを提供します。そして「a=1;」は冗長に書くと「a=(int*) 1;」であって、つまりint型変数のアドレスとして「1番地」をaに紐付けされたRAM領域に書き込みます。さらに「*a=1;」は、「1番地のRAM領域」にint型の1という値を書き込みます。 WindowsやMacのようなメモリ保護前提のOSでRAMアドレスを直接指定するということはまずないと思いますが、μITRON系OSやOS無しマイコンでは絶対的なアドレスを指定して値を書き込む場面は多々あります。 で、なんで「int *a; a = 1;」がダメなのか。 「int *a;」ではaは、書き込んでも良いアドレスで初期化されないからです。「int型を記録できる領域のアドレス値」の入れ物が定義されただけで、aが持っている値は不定なわけです。それは0番地かもしれないし1番地かもしれないし、100番地かも知れないし、1000番地かもしれません。 その後の「a=1;」はどこだかわからないアドレス番地に1を書き込んでいることになります。 で、ここからが実行環境の話です。 実行環境、特にOSがある場合は、RAM領域の中でシステム専用のアドレス領域やmalloc()で取り出す元になる領域などといったように用途ごとに区画分けがされています。そして1という値は、あなたにとってはint型の1であっても、それぞれの区画では別の意味でしょう。書き込むアドレスによっては、もしかするとパソコンを爆破する命令を指す値かもしれません。 そういった不定な領域に任意の値を書き込む行為は、(あなたにその意志がなくても)実行環境にとってはテロなわけです。テロへの対抗手段として、実行環境があなたの実行したプログラムを(自衛のために)殺すのが不正終了と呼んでいる挙動、その原因である不定な領域に不正な値を書き込む行為を指すエラー名がsegmentation faultや一般保護エラーです。 ちなみに0番地に書き込むのも大多数の実行環境ではテロにあたる事が多いので、「int *a = 0;*a=1;」もsegっちゃうよ、というのが#4の指摘だと思います。(外してたら恥ずかしいな・・・) だったらどうしたらいいのか?ですね。 int *a; としたら、 if ((a = (int*) malloc(sizeof int)) == NULL) exit 1; として、終了(exit)しなかったら、 *a = 1; といった感じで、まずはaに有効なアドレスを与えてあげなければいけません。 他には、 int b; int *a; a = &b; *a = 1; でもいいし、 int c[100]; int *a; a = c; *a = 1; でも(文法的には)いい。 で、ソースコードに立ち戻って、まずは「Node **p;」から「*p = root;」の間で「p=」で始まる処理を探しましょう。 もし無いなら、p(==不定なアドレス)への代入がsegの原因でしょう。 そして、じゃあどこに「p=」で始まる文を入れるべきなのかを検討しましょう。 それがいわゆるデバッグってやつです。 すべてを自分で考えてソースコードを書くならともかく、他人が別の実行環境向けに作ってテスト方法や検証結果も提供していないソースコードをせしめて使いたいなら、デバッグは必須と考えましょう。 特に今回のような、「Node **p;」のような「Node型へのポインタへのポインタ」とかいった多段の参照関係や、あるいは関数ポインタによるコールバックや抽象化は、「C言語で作ったプログラム」としてはごく普通というか、「それがしたくて未だにC言語を使っている」といった類の手法ですが、ポインタの基礎的な知識が不足している状態でそのレベルのソースコードを引っ張って来て流用するというのは、相当の覚悟と努力が必要だと思います。 まさに賞賛に値します。

archhuman07
質問者

補足

>warningはコンパイラからのツッコミなので文法的に是正していけばいいですが、segmentation faultや一般保護エラーは実行環境(OSとか)からのツッコミですので、実行環境であなたのプログラムが何か「悪いこと」をやらかしているわけです。 つっこんでくる相手がそもそも違っていたのですね。大変分かりやすくお教えくださりありがとうございますm(_ _)m >WindowsやMacのようなメモリ保護前提のOSでRAMアドレスを直接指定するということはまずないと思いますが、μITRON系OSやOS無しマイコンでは絶対的なアドレスを指定して値を書き込む場面は多々あります。 >実行環境、特にOSがある場合は、RAM領域の中でシステム専用のアドレス領域やmalloc()で取り出す元になる領域などといったように用途ごとに区画分けがされています。そして1という値は、あなたにとってはint型の1であっても、それぞれの区画では別の意味でしょう。書き込むアドレスによっては、もしかするとパソコンを爆破する命令を指す値かもしれません。そういった不定な領域に任意の値を書き込む行為は、実行環境にとってはテロなわけです。テロへの対抗手段として、実行環境があなたの実行したプログラムを(自衛のために)殺すのが不正終了と呼んでいる挙動、その原因である不定な領域に不正な値を書き込む行為を指すエラー名がsegmentation faultや一般保護エラーです。 OSがsegmantationfaultを起こす背景にはこんな事情があったんですね。すごく勉強になりました。 >int *a;としたら、 int b; int *a; a = &b; *a = 1; でもいいし、 int c[100]; int *a; a = c; *a = 1; でも(文法的には)いい。 「int型を記録できる領域のアドレス値の入れ物」の定義に加えて、int型の値を記録できる領域を確保して、そのアドレスに紐付けされたラベル(上記の例ですとbとcがそれにあたりますね)を代入すればいいということですね。そしてその代入にあたる行為が#4様の仰った「初期化」だというわけですね。 他の方々もご親切に詳しくお教え頂いたので全員をBAとさせて頂きたいところですが、全体的に最も丁寧に解説してくださった(C言語に関して全く無知な私に一から教えてくださった)貴殿を本質問のBAとさせて頂きます。 本当にありがとうございましたm(_ _)m

その他の回答 (7)

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.7

>英語力に乏しく、エラーメッセージを読み違えておりましたorz 読み間違えたというのが >おそらく、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と思うのですが、 のつもりなら英文全く読んでなくて見ただけでしょ(メッセージ中の単語の意味どれ一つ出てないし)。 >がダメ・・・int型なのに「*p」としてるからですか? (以下略) もしかしてポインタ理解できていませんか?

archhuman07
質問者

補足

>英文全く読んでなくて見ただけでしょ 仰る通りです。見て何となく「int型とchar*型を比較しているからおかしいんだろう」と考えただけです。 ご親切にアドバイスして頂いたのにも関わらず、嘘を言ってしまいました。申し訳ありません。 >ポインタ理解できてませんか? お恥ずかしながら、ポインタについて理解できておりませんでしたorz 幸い、他の回答者がそれについて詳しく説明してくださり、助かりました。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.6

int i ; で、 int を入れるための領域が確保されます。ここでこれを 「100番地からの領域」 とします。 このとき、「100番地からの領域」に何が入っているかはわからない、というのがC言語です。 i = 1 ; は 「100番地からの領域」に「int型の1」を格納する という意味です。 この「100番地からの領域」というのは、ポインタそのものです。よって *(「100番地からの領域」) = 1 ; と同じことになります。 同様に int *p ; で、 int * を入れるための領域が確保されます。ここでこれを 「200番地からの領域」 とします。 このとき、「200番地からの領域」に何が入っているかはわからない、というのがC言語です。 *p = 1; は 「pからの領域」に「int型の1」を格納する という意味です。pは「200番地からの領域」だったので 「「200番地からの領域」からの領域」に「int型の1」を格納する となります。 さて。「int型の1」が格納されるのは、どこでしょうか? 解決策は (1)int *p ではなく int i を使う。 (2)int *p で、 p=「intが収めることができる領域」 と初期化して使う。 p = &i ; /* int i で宣言した変数iのアドレス */ p= malloc(sizeof(int)) ; /* intが収まる領域をmallocで確保→あとでfreeすることを忘れずに */ があります。 どちらがいいかは、プログラム次第です。 Node **p=NULL ; では、(2)をしようとして、「使えない領域(NULL)」を設定してるのですから、動く保証はありません。 「初期化」と「NULL、0に設定する」ことは同じではありません。 今回の場合なら Node *q ; /* Node* を収めることができる変数q を用意*/ p = &q ; /* pにqのアドレスを設定する */ か p = malloc(sizeof(Node *)) ; /* Node*を収めることができる領域を確保 */ となります。 ですがよく見ると、そのプログラム *p の形でしか使っていません。 Node ** とする必要ってあるのでしょうか?

archhuman07
質問者

補足

大変分かりやすく教えてくださってありがとうございます! >さて。「int型の1」が格納されるのは、どこでしょうか? 「「200番地からの領域」からの領域」になりますね。訳が分かりません。 これじゃ混乱してしまいますね。 >p = malloc(sizeof(Node *)) ; これをNode **p;のすぐ下に入れたら正常に動きました!本当にありがとうございますm(_ _)m >ですがよく見ると、そのプログラム *p の形でしか使っていません。Node ** とする必要ってあるのでしょうか? 試しに「Node *p;」としてみたら正常に動きました・・・。「*」が何故2つあったのかよくわかりませんねorz

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.5

>申し訳ありません、「char*」と「char[]」が同義だと勘違いしていました・・・。 似たように使えるというだけであって同義ではないです。 ただし関数の仮引数のchar[]のような配列表記はchar *と同義です。 void foo(char s[]); // char s[]はchar *sと同じ void foo(char *s); >注意の解説もしてくださり、ありがとうございます(使用コンパイラはmacの「Xcode」です)。 英文なので避けたいかもしれませんがメッセージを読めるように努力をしましょう(質問するなという意味じゃないです)。 ただし質問などされる際には、今回のようにそっくりそのまま書きましょう。 >つまり、*pに代入するrootの初期化がされていない、ということでしょうか? いいえ。 #4でも書かれてますけど int *p; *p = 1; これがダメなのと同じです。

archhuman07
質問者

補足

英語力に乏しく、エラーメッセージを読み違えておりましたorz int *p; *p=1; がダメ・・・int型なのに「*p」としてるからですか? int i; i=1; は大丈夫ですよね・・・?「int *p=1;」としなければならない、ということでしょうか。 ちなみに「void compose(FILE*fp)」のところで「Node **p=NULL,*new;」としてもsegmentation faultが発生しました・・・orz

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

「自動変数のスタックが強制的に初期化されるシステム」でもダメだと思うけどなぁ>#3. 例えば int foo(void) { int x = 5; int *y; *y = x; } が「おかしい」ことはわかりますか? どこがどう「おかしい」か説明できますか?

回答No.3

おっと、warningを消したいわけではなかったんですか。 > *p=root; segったのは確かに初期化抜けですね。 元ネタの方の処理系では自動変数のスタックが強制的に初期化されるシステムだったんでしょうね。

archhuman07
質問者

補足

申し訳ありません、warningを消せばsegmation faultも治るとばかり思い込んでいました・・・。 warningとは別に問題があったのですね;; お教えくださり、ありがとうございますm(_ _)m

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.2

>おそらく、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と思うのですが、どうコード文を変えれば良いのかがよくわかりません。 sの型はchar *であって配列(char[])ではないです。 また >warning > comparison between pointer and integer ('int' and 'char *') の警告は*s(型がcharから暗黙の型変換が行われたint)とNULL(型がchar *。使用コンパイラは何だろう・・・)を比較してることについてです。 segumentation faultを起こしてるのは *p=root;

archhuman07
質問者

補足

ご回答ありがとうございますm(_ _)m 申し訳ありません、「char*」と「char[]」が同義だと勘違いしていました・・・。 注意の解説もしてくださり、ありがとうございます(使用コンパイラはmacの「Xcode」です)。 segumentation faultを起こしているのは「*p=root;」の部分で、wormhole様もそこの「初期化がされていない」とご指摘されています。 つまり、*pに代入するrootの初期化がされていない、ということでしょうか? コードの上の方に「Node *root=NULL;」とありますがこれで初期化がされていないと・・・? 申し訳ありません、頭が悪い私にも分かりやすく説明して頂けますでしょうかorz

回答No.1

whileの条件文のNULLを'\0'にすればいいんじゃない?

archhuman07
質問者

補足

ご回答ありがとうございますm(_ _)m NULLを'\0'に変えたら注意が消えました! 本当に助かりました><

関連するQ&A