2013년 5월 15일

문자열을 토큰으로 분리하는 strtok

C/C++ 기반에서 문자열을 구분자로 사용해 각각의 토큰(token)으로 분리하는 방법들은 상당히 많이 있습니다. 그중 가장 오래된(?) 함수가 바로 strtok() 일것이다?

하지만 이 함수는 치명적인 문제가 있으니 바로 멀티쓰레드를 사용할 경우에 발생합니다.
문자열을 토큰으로 분리해주는 strtok 함수의 실제 구현내용을 살펴보면 ...

http://research.microsoft.com/en-us/um/redmond/projects/invisible/src/crt/strtok.c.htm


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

#include 

/* 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;

    /* Skip leading delimiters if new string. */
    if ( s1 == NULL ) {
        s1 = lastToken;
        if (s1 == NULL)         /* End of story? */
            return NULL;
    } else {
        s1 += strspn(s1, delimit);
    }

    /* Find end of segment */
    tmp = strpbrk(s1, delimit);
    if (tmp) {
        /* Found another delimiter, split string and save state. */
        *tmp = '\0';
        lastToken = tmp + 1;
    } else {
        /* Last segment, remember that. */
        lastToken = NULL;
    }

    return s1;
}


위 구현내용을 보면 알겠지만 파싱과정에서 마지막 토큰을 가리키는 포인터(static char *lastToken ) 가 전역변수임을 알 수 있다.

결국 개별 쓰레드들이 strtok 함수를 사용하게 되면 지옥문이 열리는 것이다.

C언어의 오래된 몇몇 함수들은 멀티쓰레드 개념이 없던 때 작성된것이라 strtok 와 같은 쓰레드 안전하지 않은 함수들이 다수 있다는 사실. 

M$에서는 이런 문제를 해결하고자 TLS(Thread Local Storage)라는 것을 발명해 냈고, 위 코드처럼 오래된 C 함수들의 문제를 예방하고자,  전역변수이지만 개별 쓰레드에서만 전역이 되도록 해주는 방식을 고안해 냈다. 참신하다고 해야하나?

코딩시에는 개발툴에서 DLL 링크 옵션중 "MultiThread DLL Debug" 와 같이 멀티쓰레드 인지 여부를 선택하는것만으로 알아서(?) 해결해주고는 있다.

어쨋든 문자열 토큰분리를 하는데 strtok 와 같이 오래된 놈을 사용하고 싶지 않다면, 아래와 같은 것도 괜찮을것 같다.

http://stackoverflow.com/questions/289347/using-strtok-with-a-stdstring


void tokenize(const std::string& str, const std::string& delim, std::vector< std::string>& tokens)
{
std::size_t start, end = 0;
while (end < str.size())
{
start = end;
while (start < str.size() && (delim.find(str[start]) != std::string::npos))
{
start++;  // skip initial whitespace
}
end = start;
while (end < str.size() && (delim.find(str[end]) == std::string::npos))
{
end++; // skip to end of word
}
if (end-start != 0)
{
// just ignore zero-length strings.
tokens.push_back( std::string(str, start, end-start));
}
}
}

위 예는 원본 문자열을 주고 구분자를 전달하면 토큰으로 분리된 결과가 STL 벡터 컨테이너로 리턴되는 구조로 strtok 와는 구현 방식은 다르지만 멀티쓰레드와 관련된 귀찮은 걱정은 하지 않아도 되겠다.

댓글 없음:

댓글 쓰기

시리우스 라이브러리 홈페이지 오픈

현재 시리우스(Sirius) 라이브러리라는 제품을 개발하고 이를 소개하는 홈페이지를 오픈 하였습니다. 관심있는 분들의 많은 방문 요청드립니다. 앞으로 업데이트 소식및 변경사항은 스파이럴랩 홈페이지를 통해 진행할 예정입니다. 스파이럴랩 홈페이지 :  h...