【C言語】
strtok()の基本的な使い方とSegmentation faultする使い方

この記事の概要

この記事では、C言語のstrtok関数を使用する際に文字列リテラルを渡すとセグメンテーションフォルトが発生する理由を解説しています。strtokは文字列を書き換えるため、変更不可なリテラルを渡すと異常終了します。安全な利用法として、変更可能な文字列バッファを使用する方法が示されています。


strtokの意味

strtok=string+token
string=文字列
token=単語
strtokは文字列を単語に分解する関数です。

strtokの基本的な使い方

//strtokの簡単な使い方
#include <stdio.h>
#include <string.h>
int main(void){
    char        str[] = "1st,2nd,3rd,4th,5th";
    puts(strtok(str,    ","));//初回は分割対象文字列指定 
    puts(strtok(NULL,   ","));//2回目以降NULL指定
    puts(strtok(NULL,   ",")); 
    puts(strtok(NULL,   ","));
    puts(strtok(NULL,   ","));
}
./a.out
1st
2nd
3rd
4th
5th

strtokを呼ぶ初回は分割対象文字列を指定し
2回目以降はNULLを指定します。


区切り文字は複数指定できる

//区切り文字が複数の場合
#include <stdio.h>
#include <string.h>
int main(void){
    char        str[] = "1st,2nd/3rd,4th/5th";
    const char *sep = ",/";//区切り文字が複数の場合
    
    puts(strtok(str,    sep)); 
    puts(strtok(NULL,   sep));
    puts(strtok(NULL,   sep)); 
    puts(strtok(NULL,   sep));
    puts(strtok(NULL,   sep));
}

strtokの区切り文字に”,”と”/”を指定した例です。

注1:strtokは原文を書き変える

//strtokは原文を書き変える
#include <stdio.h>
#include <string.h>
int main(void){
    char        str[] = "1st,2nd,3rd,4th,5th";
    
    printf("処理前=%s\n",str);
    puts(strtok(str,    ",")); 
    puts(strtok(NULL,   ","));
    puts(strtok(NULL,   ",")); 
    puts(strtok(NULL,   ","));
    puts(strtok(NULL,   ","));
    printf("処理後=%s\n",str);
}
./a.out
処理前=1st,2nd,3rd,4th,5th
1st
2nd
3rd
4th
5th
処理後=1st

strtokは原文内の区切り文字を¥0に書き換えます。

注2:strtokに文字列定数を指定するとSegmentation faultする

//strtokに文字列定数は指定できない
#include <stdio.h>
#include <string.h>
int main(void){
    char *str = "1st,2nd,3rd,4th,5th";//文字列定数指定は誤り
    
    printf("処理前=%s\n",str);
    puts(strtok(str,    ",")); 
    puts(strtok(NULL,   ","));
    puts(strtok(NULL,   ",")); 
    puts(strtok(NULL,   ","));
    puts(strtok(NULL,   ","));
    printf("処理後=%s\n",str);
}
./a.out
処理前=1st,2nd,3rd,4th,5th
Segmentation fault

strtokは第一引数の指す先を書き換えようとするので、書き換えられない文字列定数を指定すると環境によって Segmentation fault 等の異常終了します。


注3:戻り値のNULLチェックが必要

//strtokの戻り値をチェックしないとNULL参照が発生する
#include <stdio.h>
#include <string.h>
int main(void){
    char        str[] = "1st,2nd,3rd,4th,5th";
    
    printf("処理前=%s\n",str);
    puts(strtok(str,    ","));//1st   
    puts(strtok(NULL,   ","));//2nd
    puts(strtok(NULL,   ","));//3rd 
    puts(strtok(NULL,   ","));//4th
    puts(strtok(NULL,   ","));//5th
    puts(strtok(NULL,   ","));//6thは無いのでNULLが返る
    printf("処理後=%s\n",str);
}
./a.out
処理前=1st,2nd,3rd,4th,5th
1st
2nd
3rd
4th
5th
Segmentation fault

strtokは次の単語が無ければNULLを返り値とします。
NULLチェクをしないと環境によって Segmentation fault 等の異常終了します。


strtokをループの中で使う方法

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void    文字列を分離文字で分解(char *文字列)
{
    char *単語;
    const char *分離文字 = "/";
    while((単語 = strtok(文字列,分離文字)) != NULL){
        printf("\t処理中->:%s:\n",単語);
        //strtok第1引数は2回目からNULL
        文字列 = NULL;
    }
}
int main(void)
{
    char    文字列[] = "/usr/include/stdio.h";// 正常終了
    printf("処理前:%s\n",   文字列);
    文字列を分離文字で分解( 文字列);    
    printf("処理後:%s\n",   文字列);
}
./a.out
処理前:/usr/include/stdio.h
        処理中->:usr:
        処理中->:include:
        処理中->:stdio.h:
処理後:/usr
-------------------

参考:

STR06-C. strtok() が分割対象文字列を変更しないと想定しない