C言語 strtok で「セグメンテーション違反」

あるC の教本で勉強しており、以下のソースをコンパイルして実行したら
「セグメンテーション違反です」エラーとなりました。
>|c|
#include <stdio.h>
#include <string.h>

int main()
{
char *ptr;

ptr = strtok("abc,def;ghi", ",;");
printf("%s\n", ptr); /* "abc" と表示する */
ptr = strtok(NULL, ",;");
printf("%s\n", ptr); /* "def" と表示する */
ptr = strtok(NULL, ",;");
printf("%s\n", ptr); /* "ghi" と表示する */

return(0);
}
||<

上を下のように修正したら、エラーなく期待どおり表示しました。
>|c|
途中省略

int main()
{
char chk[15];
strcpy( chk, "abc,def;ghi");
char *ptr;

ptr = strtok(chk, ",;");
printf("%s\n", ptr);
ptr = strtok(NULL, ",;");
printf("%s\n", ptr);
ptr = strtok(NULL, ",;");
printf("%s\n", ptr);

return(0);
}
||<
最初のコードで「セグメンテーション違反」が出た理由は何でしょうか?
それらを説明してくれているサイトなど、参考になるものを教えていただけると幸です。

OS: Vine Linux 6.2
gccバージョン: 4.4.5
よろしくお願いします。

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2014/04/02 23:47:09
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:a-kuma3 No.2

回答回数4973ベストアンサー獲得回数2154

ポイント50pt

これが原因です。

これらの関数はその最初の引数を変更する。

Man page of STRTOK


C 言語での文字列リテラルは、コンパイラの実装に任されてはいますが、変更不能な領域に配置されることが多いです。
strtok() は、呼び出すたびに、最初の呼び出しで指定された文字列の中身を書き換えます。
質問の前半のコードでは、文字列リテラルの中身を書き換えているので、Segmentation Fault が発生します。
後半のコードでは、auto な char の配列の内容を書き換えているので、書き換えは許されており、Segmentation Fault は発生しません。

    strcpy( chk, "abc,def;ghi");
    ptr = strtok(chk, ",;");

を行うと、配列 chk に格納されている "abc" の後に続く "," が \0 に書き換えられています。
strtok() が返しているのは、実は chk の先頭アドレスです。
カンマが \0 に書き換えられているので、strtok() が返すポインタを printf で表示すると "abc" の後に続く \0 までが表示されるので、"abc" と表示されます。

同様に、次の、

    ptr = strtok(NULL, ",;");

では、"def" に続く ";" が \0 に書き換えられて、'd' が格納されている位置を strtok() が返します。


この挙動は、コンパイラによって違ってきます。
今では、ほとんど触れることはできなくなっていますが、MS-DOS の環境や、組み込み系の環境だと、文字列リテラルの内容を書き換えても、許されることがあります。

id:gdwtseq

解りやすく丁寧な説明を返していただきましてありがとうございます。
大変勉強になりました。

2014/04/02 23:46:42

その他の回答1件)

id:Mook No.1

回答回数1314ベストアンサー獲得回数393

ポイント50pt

C でハマりやすいポインタの理解でしょうか。

下記に書かれているように、対象文字列を変更しながらポインタを返すので、
対象データとしてリテラルを使用することはできません。
C:strtok
http://www9.plala.or.jp/sgwr-t/lib/strtok.html

文字列リテラル
http://mkubara.com/index.php/%E6%96%87%E5%AD%97%E5%88%97%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB

id:gdwtseq

リテラルの参考サイトを教えていただきましてありがとうございます。
なんだか奥が深いですね。

2014/04/02 23:44:45
id:a-kuma3 No.2

回答回数4973ベストアンサー獲得回数2154ここでベストアンサー

ポイント50pt

これが原因です。

これらの関数はその最初の引数を変更する。

Man page of STRTOK


C 言語での文字列リテラルは、コンパイラの実装に任されてはいますが、変更不能な領域に配置されることが多いです。
strtok() は、呼び出すたびに、最初の呼び出しで指定された文字列の中身を書き換えます。
質問の前半のコードでは、文字列リテラルの中身を書き換えているので、Segmentation Fault が発生します。
後半のコードでは、auto な char の配列の内容を書き換えているので、書き換えは許されており、Segmentation Fault は発生しません。

    strcpy( chk, "abc,def;ghi");
    ptr = strtok(chk, ",;");

を行うと、配列 chk に格納されている "abc" の後に続く "," が \0 に書き換えられています。
strtok() が返しているのは、実は chk の先頭アドレスです。
カンマが \0 に書き換えられているので、strtok() が返すポインタを printf で表示すると "abc" の後に続く \0 までが表示されるので、"abc" と表示されます。

同様に、次の、

    ptr = strtok(NULL, ",;");

では、"def" に続く ";" が \0 に書き換えられて、'd' が格納されている位置を strtok() が返します。


この挙動は、コンパイラによって違ってきます。
今では、ほとんど触れることはできなくなっていますが、MS-DOS の環境や、組み込み系の環境だと、文字列リテラルの内容を書き換えても、許されることがあります。

id:gdwtseq

解りやすく丁寧な説明を返していただきましてありがとうございます。
大変勉強になりました。

2014/04/02 23:46:42

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません