C/C++ 문자열 분리 함수 strtok 심층 분석

strtok 함수의 이해와 활용

1. 기본 사용법 및 작동 원리

strtok는 C 언어에서 제공하는 문자열 토큰화 함수로, 지정된 구분자를 기준으로 문자열을 조각내는 데 사용됩니다. 이 함수는 <string.h> 헤더에 정의되어 있으며, 반복 호출을 통해 연속적인 토큰을 추출할 수 있습니다.

함수 시그니처

char *strtok(char *str, const char *delim);
  • str: 첫 번째 호출 시 분석할 문자열 포인터. 이후 호출에서는 NULL을 전달하여 이전 상태를 이어갑니다.
  • delim: 하나 이상의 구분자 문자로 구성된 문자열.
  • 반환값: 현재 추출된 토큰의 시작 주소. 더 이상 토큰이 없으면 NULL 반환.

기본 예제

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

int main() {
    char input[] = "apple:banana,orange;grape";
    const char* delimiters = ":,;";
    char* token;

    // 첫 번째 토큰 추출
    token = strtok(input, delimiters);
    while (token != NULL) {
        printf("토큰: %s\n", token);
        token = strtok(NULL, delimiters); // 계속 진행
    }

    return 0;
}

출력 결과:

토큰: apple
토큰: banana
토큰: orange
토큰: grape

2. 주의사항 및 제한점

원본 문자열 수정

strtok는 내부적으로 구분자를 \0으로 치환하여 문자열을 파괴적으로 분할합니다. 따라서 리터럴 문자열이나 읽기 전용 메모리에 접근할 경우 프로그램 충돌이 발생할 수 있습니다.

다음은 잘못된 사용 예입니다:

char *literal = "one-two-three";
strtok(literal, "-"); // 위험: 문자 상수 영역 수정 시도

올바른 방법은 가변 배열을 사용하는 것입니다:

char buffer[] = "one-two-three";
strtok(buffer, "-"); // 안전: 스택에 할당된 수정 가능한 배열

다중 구분자 처리

두 번째 인자 delim은 단일 문자가 아니라 문자 집합일 수 있습니다. 예를 들어 ",;:"처럼 여러 종류의 구분자를 동시에 처리할 수 있습니다.

선두/후미 구분자 무시

입력 문자열의 시작 또는 끝에 구분자가 있어도 strtok는 이를 자동으로 건너뛰고 유효한 토큰부터 반환합니다. 예를 들어 "-aaa-bbb-"는 첫 번째 토큰으로 "aaa"를 반환하며, 선두의 -는 무시됩니다.

3. 내부 동작 원리와 재구현

strtok는 정적(static) 변수를 이용해 이전 호출 상태를 유지합니다. 아래는 표준 라이브러리와 유사하게 동작하는 사용자 정의 구현입니다.

char* my_strtok(char* src, const char* delim) {
    static char* saved_pos = NULL;

    // 새로운 문자열 시작 또는 이전 상태 복원
    if (src != NULL) {
        saved_pos = src;
    } else if (saved_pos == NULL) {
        return NULL;
    }

    // 구분자로 시작하는 부분 건너뛰기
    while (*saved_pos && strchr(delim, *saved_pos)) {
        ++saved_pos;
    }

    // 더 이상 문자가 없을 경우
    if (*saved_pos == '\0') {
        return NULL;
    }

    char* start = saved_pos;

    // 다음 구분자까지 이동
    while (*saved_pos && !strchr(delim, *saved_pos)) {
        ++saved_pos;
    }

    // 구분자 위치를 널 문자로 대체
    if (*saved_pos) {
        *saved_pos = '\0';
        ++saved_pos;
    }

    return start;
}

이 구현은 정적 포인터 saved_pos를 통해 상태를 보존하며, strchr을 사용해 구분자 존재 여부를 확인합니다. 실제 glibc 구현과 유사한 방식으로, 호출 간 상태를 유지하면서 안전하게 토큰을 분리합니다.

대안 함수 고려

strtok는 스레드 비안전하고 원본 문자열을 변경한다는 단점이 있어, 현대 코드에서는 다음과 같은 대안을 권장합니다:

  • strtok_r: 재진입 가능 버전 (POSIX)
  • strsep: 더 유연한 토큰 분리 함수
  • 직접 구현: strspn, strcspn 등을 조합

특히 멀티스레드 환경에서는 정적 상태를 사용하지 않는 strtok_r 사용이 필수적입니다.

태그: strtok C C++ 문자열처리 토큰화

8월 1일 01:16에 게시됨