アプリ版:「スタンプのみでお礼する」機能のリリースについて

初めて質問させて頂きます。C言語初心者です。

実は講義で「ファイル中の英文を単語に分けてその出現頻度をカウントするコードを木構造を用いて出力せよ」という課題が出ました。

そこで、参考にするコードを検索しましたところ、以下のURLにあるベストアンサーのコードが近いと感じました。

http://oshiete.goo.ne.jp/qa/4155655.html

コードの内容は以下の通りになります。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct node Node;
struct node{
char *word;
int count;
Node *left,*right;
};

Node *root=NULL;
void compose(FILE *fp);
void inorder(Node *p);
void strlower(char *s);

int main(int argc, char *argv[]) {
FILE *fp;
Node *new;

fp=fopen(argv[1],"r");
if(fp==NULL){puts("ファイルを開けません");return(-1);}
compose(fp);
inorder(root);
return (0);
}

void strlower(char *s){
while(*s!=NULL){*s=tolower(*s);s++;}
}

void compose(FILE*fp){
Node **p,*new;
char buf[256];
while(1){
fscanf(fp,"%[^a-zA-Z0-9]",buf);
if(fscanf(fp,"%[a-zA-Z0-9]",buf)==EOF)break;
strlower(buf);
if(root==NULL){
new=(Node *)malloc(sizeof(Node));
new->left=NULL; new->right=NULL; new->word=strdup(buf); new->count=1;
root=new;
}else{
*p=root;
while(1){
if(strcmp(buf,(*p)->word)==0){
(*p)->count++;break;
}else if(strcmp(buf,(*p)->word)<0){
if((*p)->left==NULL){
new=(Node *)malloc(sizeof(Node));
new->left=NULL;new->right=NULL;new->word=strdup(buf);new->count=1;
(*p)->left=new;
break;
}else{
*p=(*p)->left;
}
}else{
if((*p)->right==NULL){
new=(Node *)malloc(sizeof(Node));
new->left=NULL; new->right=NULL; new->word=strdup(buf); new->count=1;
(*p)->right=new;
break;
}else{
*p=(*p)->right;
}
}
}
}
}
}

void inorder(Node*p){
if (p==NULL) return;
inorder(p->left);
printf("%s %d\n",p->word, p->count);
inorder(p->right);
}

しかし、これをそのままコンパイル・実行すると、コンパイル時に以下の注意が出ます。

warning
comparison between pointer and integer ('int' and 'char *')
while(*s!=NULL){*s=tolower(*s);s++;}

上記の注意を無視してそのまま実行すると、segmatation faultが出てしまいますorz

おそらく、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と思うのですが、どうコード文を変えれば良いのかがよくわかりません。

どなたかお教え頂けると幸いです。どうぞよろしくお願いしますm(_ _)m

A 回答 (8件)

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

この回答への補足

ご回答ありがとうございますm(_ _)m

NULLを'\0'に変えたら注意が消えました!

本当に助かりました><

補足日時:2014/12/19 23:56
    • good
    • 0

>おそらく、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;

この回答への補足

ご回答ありがとうございますm(_ _)m

申し訳ありません、「char*」と「char[]」が同義だと勘違いしていました・・・。

注意の解説もしてくださり、ありがとうございます(使用コンパイラはmacの「Xcode」です)。

segumentation faultを起こしているのは「*p=root;」の部分で、wormhole様もそこの「初期化がされていない」とご指摘されています。

つまり、*pに代入するrootの初期化がされていない、ということでしょうか?

コードの上の方に「Node *root=NULL;」とありますがこれで初期化がされていないと・・・?

申し訳ありません、頭が悪い私にも分かりやすく説明して頂けますでしょうかorz

補足日時:2014/12/20 00:07
    • good
    • 0

おっと、warningを消したいわけではなかったんですか。



> *p=root;

segったのは確かに初期化抜けですね。
元ネタの方の処理系では自動変数のスタックが強制的に初期化されるシステムだったんでしょうね。

この回答への補足

申し訳ありません、warningを消せばsegmation faultも治るとばかり思い込んでいました・・・。

warningとは別に問題があったのですね;;

お教えくださり、ありがとうございますm(_ _)m

補足日時:2014/12/20 00:09
    • good
    • 0

「自動変数のスタックが強制的に初期化されるシステム」でもダメだと思うけどなぁ>#3.



例えば
int foo(void)
{
int x = 5;
int *y;

*y = x;
}
が「おかしい」ことはわかりますか? どこがどう「おかしい」か説明できますか?
    • good
    • 0

>申し訳ありません、「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;

これがダメなのと同じです。

この回答への補足

英語力に乏しく、エラーメッセージを読み違えておりました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

補足日時:2014/12/20 03:25
    • good
    • 0

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 ** とする必要ってあるのでしょうか?

この回答への補足

大変分かりやすく教えてくださってありがとうございます!


>さて。「int型の1」が格納されるのは、どこでしょうか?

「「200番地からの領域」からの領域」になりますね。訳が分かりません。
これじゃ混乱してしまいますね。


>p = malloc(sizeof(Node *)) ;

これをNode **p;のすぐ下に入れたら正常に動きました!本当にありがとうございますm(_ _)m


>ですがよく見ると、そのプログラム *p の形でしか使っていません。Node ** とする必要ってあるのでしょうか?

試しに「Node *p;」としてみたら正常に動きました・・・。「*」が何故2つあったのかよくわかりませんねorz

補足日時:2014/12/22 01:41
    • good
    • 0

>英語力に乏しく、エラーメッセージを読み違えておりましたorz



読み間違えたというのが

>おそらく、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と思うのですが、

のつもりなら英文全く読んでなくて見ただけでしょ(メッセージ中の単語の意味どれ一つ出てないし)。

>がダメ・・・int型なのに「*p」としてるからですか?
(以下略)

もしかしてポインタ理解できていませんか?

この回答への補足

>英文全く読んでなくて見ただけでしょ

仰る通りです。見て何となく「int型とchar*型を比較しているからおかしいんだろう」と考えただけです。
ご親切にアドバイスして頂いたのにも関わらず、嘘を言ってしまいました。申し訳ありません。

>ポインタ理解できてませんか?

お恥ずかしながら、ポインタについて理解できておりませんでしたorz
幸い、他の回答者がそれについて詳しく説明してくださり、助かりました。

補足日時:2014/12/22 01:46
    • good
    • 0

> #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言語を使っている」といった類の手法ですが、ポインタの基礎的な知識が不足している状態でそのレベルのソースコードを引っ張って来て流用するというのは、相当の覚悟と努力が必要だと思います。

まさに賞賛に値します。

この回答への補足

>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

補足日時:2014/12/22 02:06
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!