初めて質問させて頂きます。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
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言語を使っている」といった類の手法ですが、ポインタの基礎的な知識が不足している状態でそのレベルのソースコードを引っ張って来て流用するというのは、相当の覚悟と努力が必要だと思います。
まさに賞賛に値します。
この回答への補足
>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
No.7
- 回答日時:
>英語力に乏しく、エラーメッセージを読み違えておりましたorz
読み間違えたというのが
>おそらく、sの型が*s=s[]なので、注意の中の「s++」の部分で誤作動を起こしている(s++を実行するにはsはint型でなければならない)と思うのですが、
のつもりなら英文全く読んでなくて見ただけでしょ(メッセージ中の単語の意味どれ一つ出てないし)。
>がダメ・・・int型なのに「*p」としてるからですか?
(以下略)
もしかしてポインタ理解できていませんか?
この回答への補足
>英文全く読んでなくて見ただけでしょ
仰る通りです。見て何となく「int型とchar*型を比較しているからおかしいんだろう」と考えただけです。
ご親切にアドバイスして頂いたのにも関わらず、嘘を言ってしまいました。申し訳ありません。
>ポインタ理解できてませんか?
お恥ずかしながら、ポインタについて理解できておりませんでしたorz
幸い、他の回答者がそれについて詳しく説明してくださり、助かりました。
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 ** とする必要ってあるのでしょうか?
この回答への補足
大変分かりやすく教えてくださってありがとうございます!
>さて。「int型の1」が格納されるのは、どこでしょうか?
「「200番地からの領域」からの領域」になりますね。訳が分かりません。
これじゃ混乱してしまいますね。
>p = malloc(sizeof(Node *)) ;
これをNode **p;のすぐ下に入れたら正常に動きました!本当にありがとうございますm(_ _)m
>ですがよく見ると、そのプログラム *p の形でしか使っていません。Node ** とする必要ってあるのでしょうか?
試しに「Node *p;」としてみたら正常に動きました・・・。「*」が何故2つあったのかよくわかりませんねorz
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;
これがダメなのと同じです。
この回答への補足
英語力に乏しく、エラーメッセージを読み違えておりました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
No.4
- 回答日時:
「自動変数のスタックが強制的に初期化されるシステム」でもダメだと思うけどなぁ>#3.
例えば
int foo(void)
{
int x = 5;
int *y;
*y = x;
}
が「おかしい」ことはわかりますか? どこがどう「おかしい」か説明できますか?
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;
この回答への補足
ご回答ありがとうございますm(_ _)m
申し訳ありません、「char*」と「char[]」が同義だと勘違いしていました・・・。
注意の解説もしてくださり、ありがとうございます(使用コンパイラはmacの「Xcode」です)。
segumentation faultを起こしているのは「*p=root;」の部分で、wormhole様もそこの「初期化がされていない」とご指摘されています。
つまり、*pに代入するrootの初期化がされていない、ということでしょうか?
コードの上の方に「Node *root=NULL;」とありますがこれで初期化がされていないと・・・?
申し訳ありません、頭が悪い私にも分かりやすく説明して頂けますでしょうかorz
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# バイナリファイルをコピーするのにかかる時間を測りたいのですが実行するとFatel error:gli 2 2022/11/03 01:10
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# C言語のエラーについて 2 2022/07/11 13:56
- C言語・C++・C# プログラミング c言語 4 2023/03/07 01:05
- JavaScript 画像の表示位置 3 2022/12/23 08:25
- C言語・C++・C# leetcode 155 minstack 1 2022/05/07 16:43
- C言語・C++・C# C言語で再起関数とポインタを用いて文字列反転をする方法がわかりません。 4 2023/04/29 20:32
このQ&Aを見た人はこんなQ&Aも見ています
関連するカテゴリからQ&Aを探す
おすすめ情報
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
mallocについて
-
メモリ領域確保に関して
-
プログラムが途中で強制終了し...
-
C++で、メンバもヒープに確保さ...
-
メモリ解放について
-
dllを使用しVB側に文字列...
-
c言語のポインタへの文字列入力...
-
動的メモリ確保の外部関数
-
仮想メモリでない環境でのmallo...
-
mallocで確保するメモリの領域...
-
allocってなんですか?
-
C言語 配列の長さの上限
-
関数から配列を返すには?
-
先頭アドレスとは何ですか?
-
C言語の関数と配列に関する質問
-
プログラミング MATLAB
-
fstream型オブジェクトを関数の...
-
C言語において、 配列要素をひ...
-
VBA基本構文の作り方 2列の...
-
MFC - ダイアログボックスのPic...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
mallocについて
-
c言語のポインタへの文字列入力...
-
allocってなんですか?
-
newしないオブジェクトについて
-
ヒープメモリの解放について
-
C++で、メンバもヒープに確保さ...
-
MSDNがgethostbynameではなくge...
-
プログラムが途中で強制終了し...
-
配列の添え字の最大数とは?
-
Accessで、メモリを開放するタ...
-
ヒープ領域の限界値設定
-
malloc呼び出し時のセグメンテ...
-
スタック破壊の上手な見つけ方...
-
C言語 mallocとfreeについて
-
指定したメモリアドレスの値の...
-
stringの最大サイズ
-
16進ダンプのプログラム
-
64ビットと32ビットの違い
-
入れ子になった構造体について
-
free関数で動作が止まる
おすすめ情報