- - PR -
C言語入門者:自作ライブラリの呼び出しでSegmentation fault
投稿者 | 投稿内容 | ||||
---|---|---|---|---|---|
|
投稿日時: 2009-02-20 13:33
あんとれさん
入門者にとってまたまたありがたいご助言をいただき 大変嬉しく思います。有難うございます!! <最初のコード> なるほど。 int n = 0; int *ret = &n; subpg(ret); とするのであれば、 int n = 0; subpg(&n); でも全く同じことだということですね。 納得しました。 <2つ目のコード> 動的にメモリを獲得(malloc)すれば、 segmentaion faultにならないのですね。 使い終わったらfreeでメモリを開放。 納得しました。 勉強になりました。 有難うございました!!! | ||||
|
投稿日時: 2009-02-20 17:02
mallocを使えばsegmentaion faultにならないかというと
そうでもなくて不用意に使うと容易に発生するかもしれません。 mallocの後に実際に確保出来たかどうかのチェックは入れた方がいいと思いますよ if (ret == NULL) { /* エラー処理 */ } あとmallocで確保した領域の内容は不定なので注意してください。 | ||||
|
投稿日時: 2009-02-20 17:51
kojiさん
ご助言有難うございます。 malloc、NULLについて調べてみました。 ■malloc mallocは必ず成功するとは限らない。利用可能な空きメモリ領域がないとき、プログラムが限界値を超えてメモリを使用しようとしたときなど、mallocは ヌルポインタを返す。(Wikipediaより引用) ■NULL ポインタの基本的な考え方は、どこかに実体があって、それがどこにあるかという情報を保存しておこう、というものです。すなわち、ポインタはどこかにある実体を指している矢印のようなものと考えられます。 ところで、ポインタを使う場合、どこも指していないことを明確に示したいことがよくあります。このような時に使う値が NULL です。すなわち、 NULL はどの実体も指さないことが保証されています。 (C MAGAZINE 1995年8月号より引用) 勉強になりました。 有難うございました!!! | ||||
|
投稿日時: 2009-02-21 11:36
アプリケーションが Segmentation fault なんてしたらコトですので、解析できるようにしておいた方が良いです。
Segmentation fault時には、coreファイルが出来ますので、デバッガ(gdb)を使って、Segmentation fault時の挙動を追うことができます。 ※coreファイルができない場合は、coreファイルサイズ制限がかかっているかもしれません。“ulimit -c”で設定を見て、小さいようなら“ulimit -c 値”で増やしましょう。( unlimited でも可 ) 前提としては、ライブラリやプログラムが -g オプション付でコンパイルされていること、です。 以下、実行例です。なお、デバッガが元ソースを追えるよう、core とプログラム本体と、ソースファイルを全てカレントディレクトリに置いています。
gdbを起動した時点で、どこで停止したかが分かります。( ※1 ) Segmentation fault ということは、不正なメモリアクセス ( 無効なメモリを読み書きした/読み込み専用メモリに書き込んだ ) なので、この時点で ret の値が怪しいと推測できます。 なお、※2 の bt コマンドにより、関数の階層を見ることができます。この場合は、main から呼び出された subpg で止まっていることが分かります。 さて、実際に ret の値を見ているのが ※3 です。i386-Linux の場合、ポインタ変数の値はCPUが扱う仮想メモリのアドレスと一致しますから、アドレス 0x804850c へ書き込むのはマズいよね、と分かります。 なお、subpg がサードパーティ製ライブラリにあって、デバッグ情報が拾えない場合。subpg を呼び出している main 側でないと情報が拾えません。 ※4 の up コマンドで関数の呼び出し階層をあがって、main 側の ret を見ているのが ※5 です。subpg に渡している ret 0x804850c がマズいというのが見て取れます。 [ メッセージ編集済み 編集者: angel 編集日時 2009-02-21 13:06 ] | ||||
|
投稿日時: 2009-02-21 13:32
なお、アドレス 0x804850c がなぜマズいのか、はカンで言っている部分もありますので、メモリ割り当てと対応させて見てみます。
デバッガ(gdb)上でプログラムを実行し、途中で停止させてみます。
※1で、main関数をブレークポイントと設定しておいて、※2 でプログラムを実行します。すると、main関数の開始時点でプログラムが一時停止します。 この時の mainpg のPIDは 18211 でしたので(※4で調査)、※5 でLinuxからメモリ割り当て状況を見てみました。 で、0x804850c というのは mainpg を読み込み専用(r-xp)で割り当てている 08048000-08049000 の領域にありますから、そもそも書き込みができないことがわかります。 なお、この領域は、プログラムコードや読み取り専用の変数等が格納されています。 ※mainpg.c(mainpg)で定義したグローバル変数であれば、08049000-0804a000 にあることが期待できます。同様に、subpg.c(libtest.so) で定義したグローバル変数であれば、00cc9000-00cca000 にあることが期待できます。 参考として、ret 自体はメモリのどこに格納されているか ※3 で見てみました。 0xbfeb07a4 は、bfeaf000-c0000000 の領域に含まれます。 ここは、局所変数が保存されるスタック領域になります。 ※mallocで確保される領域(ヒープ)はまた別になります。 今回は、不正なポインタ値を経由したメモリ書き込みが直ぐに Segmentation fault を引き起こしたため、バグに気付くことができましたが、誤って読み書きしたのがどこか別の有効なメモリ領域の場合、後になって不可解な動作として現れることになります。 ※破壊したのがスタック上の特定領域の場合、デバッガで挙動を追えなくなることもあります。 そのため、ポインタ周りのバグは非常にやっかいです。 [ メッセージ編集済み 編集者: angel 編集日時 2009-02-21 13:37 ] | ||||
|
投稿日時: 2009-02-21 13:52
以下、余談です。
・main の型 main は void main(void) ではありません。 int main(void) もしくは、int main(int argc, char *argv[]) ( argv は char **argv でも同等 ) です。 ・main の返り値 上記により、main は int型の値を返すことになります。 main から抜ける所では return 文を書きましょう。 なお、正常終了の場合は 0 を返すようにします。 ※main から return するのは、exit を呼ぶのと同じ効果になります。 ・プロトタイプ宣言 外部で定義している subpg のプロトタイプ宣言は、会議室に書くときに省略している、でしょうか? mainpg.c では、subpg関数の情報が分からないので、プロトタイプ宣言 ( void subpg(int *ret); ) を書きましょう。 ただし、一般的には、プロトタイプ宣言をヘッダファイルとして分離しておいて、mainpg.c では #include により取り込むようにするものかとは思いますが。 | ||||
|
投稿日時: 2009-02-23 09:54
angelさん
デバッグ視点からのご助言ありがとうございます。 Linux,C言語の経験が浅いため、 gdb(GNUデバッガ)の存在は今回はじめて知りました。 シンボリックデバッグ機能を持ったツールなのですね。 C言語を今後扱っていく上では必須のツールであると感じました。 詳細な具体例はわかりやすく大変勉強になりました。 書込みにも多くのお時間を割いていただいたと存じます。 感謝いたします。 あと、「余談」についても 入門者としては大変勉強になります。 ・void main(void)は実は会議室への書込時のパンチミスではあったのですが、あまり深く考えていませんでした。「void main(void)は駄目」とは思っていなかったのです。int main(void)もしくは、int main(int argc, char *argv[])のいずれかなのですね。勉強になりました。 ・外部の関数を使う際にプロトタイプ宣言が必要とは知りませんでした。今回のmainpg.cには実際書いていませんがコンパイルが通っています。何か限定的な条件でコンパイルが通っているだけなのでしょうか。今後はプロトタイプ宣言を書くようにいたします。 本当に有難うございました!!! | ||||
|
投稿日時: 2009-02-23 10:08
追記
先程プロトタイプ宣言をしなくてもコンパイルが通っていますと書き込みましたが、 警告: implicit declaration of function ‘subpg’ は出ていました。 プロトタイプ宣言をしたところ、 この警告は出なくなりました。 以上です。 |