본문 바로가기

별걸다하는 IT/프로그래밍언어

문자열 헤더파일 string.h 파헤치기 (strtok, strtok_s 왜 NULL을 전달하는가)

[C언어, C++언어, Java언어 기초 프로그래밍 목차 링크 모음]

 

오늘은 문자열을 특정 구분자를 기준으로 나눠주는 strtok 함수를 살펴볼게요

 

string.h 라이브러리 파헤치기 strtok, strtok_s편

 

strtok()

문자열을 토큰으로 분리해주는 함수는 strtok입니다! 문법은 이와 같아요. 첫 번째 매개변수로 탐색할 문자열을 넣어주시고 두 번째 매개변수로 구분자를 넣어주시면 됩니다. 띄어쓰기를 기준으로 문자열을 분리하고 싶다면 " "를 넣어주고, 컴마를 기준으로 문자열을 구분하고 싶다면 ","을 넣어주면 되겠죠?

 

char * strtok(char str[], const char *delims);

 

리턴 값은 토큰이예요. 토근이 뭐냐.. 첫 구분자를 만나서 기존 문자열에서 그 구분자 만나기 전까지 문자열을 똑 떼어냅니다. 

그게 token이예요. Hello world!를 " "를 기준으로 구분한다 하면 첫 토큰은 Hello가 되겠죠?!

strtok를 반복적으로 사용하면 구분자로 구분된 문자열들을 모두 알 수 있어요!

 

바로 예시를 볼까요?

#include <stdio.h>
#include <string.h>

int main()
{
    char str[] = "my name is Ella";
    char* token = strtok(str, " ");
    while (token != NULL)
    {
        printf("%s\n", token);
        token = strtok(NULL, " ");
    }
    return 0;
}

 

-----------------------------------------------------------------------------------------------------------------------

잠깐! 코드를 돌렸는데 비주얼 스튜디오에서 돌리면 다음과 같은 에러가 뜰 수 있어요

'strtok': This function or variable may be unsafe. Consider using strtok_s instead.

strtok는 안전하지 않으므로 strtok_s를 대신 사용하세요.

To disable deprecation, use _CRT_SECURE_NO_WARNINGS.

이렇게 경고뜨는 거 없애고 싶으면 _CRT_SECURE_NO_WARNINGS 사용하랍니다. 

 

이와 같은 에러를 보신다면 앞에 

 

1
#pragma warning (disable: 4996)
cs

이 코드를 넣어주시면 경고가 뜨지 않습니다.

gcc컴파일러를 사용하시는 경우에는 에러가 뜨지 않습니다. 밑에서 strtok_s사용방법도 알아볼게요 

-----------------------------------------------------------------------------------------------------------------------

 

이어서~~~ 예시 코드를 보면 분명 첫 매개변수는 문자열이였는데 (7라인 코드 참조)

두 번째 strtok를 사용할 때에는 다음 토큰을 얻기 위해서 왜 NULL을 넣었지 의문이 들 수 있어요 (11라인)

그 해답은 내부 코드에 있습니다.

내부 코드를 잠깐 살펴볼게요

/* Copyright (c) Microsoft Corporation. All rights reserved. */

#include <string.h>

/* ISO/IEC 9899 7.11.5.8 strtok. DEPRECATED.
 * Split string into tokens, and return one at a time while retaining state
 * internally.
 *
 * WARNING: Only one set of state is held and this means that the
 * WARNING: function is not thread-safe nor safe for multiple uses within
 * WARNING: one thread.
 *
 * NOTE: No library may call this function.
 */

char * __cdecl strtok(char *s1, const char *delimit)
{
    /* 스태틱으로 내부에 데이터를 항상 가지고 있고 이를 공유하고 있어요. */
    static char *lastToken = NULL; /* UNSAFE SHARED STATE! */ 
    char *tmp;

    /*만약 첫 번째 아규먼트가 NULL이면 문자열을 19번째 라인에 저장된 lastToken으로 생각합니다. */ 
    /*즉 두 번째 호출에서 첫 번째 아규먼트가 NULL이라고 해서 탐색할 문자열이 NULL이 되는 것이 아니다.*/
     if ( s1 == NULL ) {
        s1 = lastToken;
        /*근데 저장되어 있는 문자열조차 NULL일 경우에 널을 리턴하고 종료됩니다. */ 
        /*(41라인에 의해 찾는 문자열이 더 이상 없을 경우가 됨).*/
        if (s1 == NULL)
            return NULL;
    } else {
    /* strspn은 delimit가 아닌 문자가 시작되는 위치를 리턴하기 때문에 delimit가 공백일 경우 공백이 앞에 와도 무시됩니다.*/
        s1 += strspn(s1, delimit);
    }

    /* 34번 라인: 문자열에서 delimit을 찾은 후 주소값을 리턴 (strpbrk는 찾은 문자열 주소 값을 리턴)*/
    tmp = strpbrk(s1, delimit);
    if (tmp) {
        /* 만약 또 다른 delimit을 찾았으면, 문자열을 분리하고 상태를 저장 */
        /* 두번째 구분자 주소가 0이 되버리므로 s1이 자동으로 두번째 구분자전까지 잘라집니다.*/
        *tmp = '\0';
        lastToken = tmp + 1;
    } else {
        /* 문자열 못찾으면 lastToken에 NULL을 저장해요. */
        /* 즉 찾고자 하는 문자열이 없을 경우에만 내부의 데이터가 NULL이 됩니다. */    
        lastToken = NULL;
    }
    return s1;
}

나름 자세히 주석을 달려고 했는데 이해하셨나요?

간단하게 규칙을 아래와 같이 이해하면 됩니다. ↓

만약 같은 문자열에서 탐색을 계속 이어나가고 싶다면 두 번째 strtok 호출 시 첫 번째 아규먼트로 널 포인터를 전달해준다.

이유는 첫 아규먼트가 널이면 현재 저장되어 있는 남은 문자열을 데이터로 사용하기 때문!

만약 널이 아니면 새로운 검색이 시작된다 생각해 내부 데이터를 초기화하기 때문이다. 

 

여기까지 문자열을 구분하는 strtok원리와 사용 방법에 대해서 알아봤어요.

 

strtok_s()

strtok_s에는 strtok에 없던 context라는 새로운 매개변수가 들어가는데요 context는 분리된 후 남은 문자열이 들어갑니다.

이 차이 빼면 strtok랑 똑같아요 ㅎㅎ  

#include <stdio.h>
#include <string.h>

int main()
{
    char str[1000] = " hello my name is Ella";
    char * token = NULL;
    char * context = NULL;
    token = strtok_s(str, " ", &context);
    
    while (token != NULL)
    {
        printf("토큰 문자열 :%s, 남은 문자열:%s\n", token, context);
        token = strtok_s(NULL, " ", &context);
    }
    return 0;
}

결과를 확인하면 아래와 같습니다.

쉽죠잉~!?

참고로 이와 비슷한 함수로 자바에서 StringTokenizer() 메소드가 있습니다. ㅎㅎ

 

오늘은 간단하게 strtok 함수에서 NULL포인터를 전달하는 이유를 파헤쳐보았어요.

다음에 또 봐요