티스토리 뷰
☞ strtok
<string.h>
char *strtok(char *src, const char *deli);
어휘 분석 과정에서 변수, 연산자등 하나의 분석 단위가 되는 문자 및 문자열을 토큰이라 하는데 특정 스트링에 존재하는 토큰을 추출할 때 strtok() 함수를 사용할 수 있습니다.
이러한 어휘 분석을 수행하는 대표적인 도구가 컴파일러와 DBMS로 텍스트 입력에 대해서 토큰을 추출하면서 파싱(Parsing)을 수행하여 작업을 위한 준비를 합니다. 이런 도구들은 복잡한 문법을 효과적으로 표현하고 처리하기 위해서 통상 lex와 같은 전문 도구를 활용합니다.
반면에 C언어 문법이나 SQL 문법 처럼 복잡한 것이 아니라 아주 단순한 문법 규칙을 가지고 있는 경우라면 strtok()로 토큰을 추출하면서 간편하게 처리를 수행할 수 있습니다.
strtok() 다른 str...() 함수들과 큰 차이점이 있는데 그중에 하나는 분석 대상으로 전달하는 src스트링에 변경이 가해진다는 것입니다. 문자열을 복사하는 strcpy()나 검색하는 strstr()의 예를 보더라도 원본 src 스트링은 변경되지 않습니다.
그렇지만 strtok()의 경우에는 토큰 구분자로 지정하는 deli 문자 집합에 속하는 문자에 널(null)을 저장해서 토큰을 구분해 주기 때문에 작업 과정에서 원본 스트링이 변경되는 것입니다. 원본 스트링을 보존해야 한다면 토큰 추출 이전에 별도의 보전 작업을 수행한 이후에 함수를 호출해야 합니다.
strtok() 함수가 str...() 함수들과 다른 큰 차이점은 str...() 함수들은 멀티 쓰레드 환경에서 여러 쓰레드가 해당 함수를 동시에 호출하더라도 별 문제가 없지만
strtok()의 경우에는 여러 쓰레드가 해당 함수를 동시에 호출한다면 원하지 않는 결과를 가져올 수 있는 위험성이 있습니다. strtok()는 스트링 내의 토큰을 추출해가면서 다음 토큰 위치를 저장하기 때문입니다.
☞ 예제1
char *teststr1 = ",,This is,sample test"; char teststr2[64]; strcpy(teststr2, teststr1); printf("\nStart = %s",teststr2); printf("\nStep1 = %s",strtok(teststr2, ",")); printf("\nStep2 = %s",strtok(NULL, ",")); printf("\nStep3 = %s",strtok(NULL, ",")); printf("\nEnd = %s",teststr2);
Start = ,,This is,sample test Step1 = This is Step2 = sample test Step3 = (null) End = ,,This is
위의 예제 코드에서 "Start", "End"로 표시한 원본 스트링에서도 확인할 수 있듯이 strtok()를 호출하면 토큰 추출 과정에서 원본 스트링이 변경됩니다. strtok()를 사용하는 방법은 첫 호출시에는 분석할 스트링의 시작위치를 포인터로 넘겨주고 다음 부터는 위치를 NULL 포인터로 넘겨 주면 strtok()는 연속된 호출로 인식하여 직전 호출시 저장해둔 마지막 토큰 위치 이후를 찾습니다. strtok()가 호출되면 우선 deli로 지정한 구분자에 속하지 않는 문자열을 찾습니다. 물론 앞서 설명한 것처럼 src가 NULL이면 직전 검색 위치 다음부터 검색합니다. deli에 속하지 않는 토큰 시작 위치를 찾으면 deli에 속하는 문자 위치를 찾아서 널(NULL)을 입력하고 토큰 시작 위치를 리턴합니다. src가 NULL이 아닐때 토큰을 찾지 못하거나 NULL일때 해당 스트링에 더이상 토큰이 존재하지 않으면 NULL을 리턴합니다.
☞ 예제2
char *teststr1 = ",,This is,sample test"; char teststr2[64]; strcpy(teststr2, teststr1); printf("\nStart = %s",teststr2); printf("\nStep1 = %s",strtok(teststr2, ", ")); printf("\nStep2 = %s",strtok(NULL, ", ")); printf("\nStep3 = %s",strtok(NULL, ", ")); printf("\nStep4 = %s",strtok(NULL, ", ")); printf("\nStep5 = %s",strtok(NULL, ", ")); printf("\nEnd = %s",teststr2);
Start = ,,This is,sample test Step1 = This Step2 = is Step3 = sample Step4 = test Step5 = (null) End = ,,This
위의 예제 코드는 예제1에서 구분자 deli에 콤마 ','외에 공백 문자 ' '를 추가한 것입니다. deli로 지정하는 구분자 그룹의 문자 순서는 동작에 영향을 미치지 않습니다.
주의할 점은 분석 대상 스트링이 널로 끝나는 문자열이고 수정가능한 사용자 메모리 영역에 있는 것이어야 합니다. 널로 끝나지 않는 스트링이라면 프로그램의 비정상 종료를 초래할 것이고
상수 스트링처럼 수정 불가한 영역에 대해 strtok()를 호출하면 마찬가지로 프로그램의 비정상 종료를 초래할 수 있습니다. 예를 들어,
위의 예제 코드에서 스트링 상수인 teststr1에 직접 strtok()를 호출하면 strtok()가 보호 영역에 쓰기를 시도하는 것이므로 시스템 오류를 발생시킵니다.
☞ 예제3
for (p = tmp_err_msg;; p = NULL) { token = strtok (p, "\n"); if (token == NULL) { break; } fprintf (fp, "# %s\n", token); }
위의 예제 코드는 큐브리드 DBMS 코드의 일부로 전달된 로그 메시지가 여러 라인에 걸친 것이면 각 라인별로 잘라서 로깅하는 과정에 strtok()를 사용하고 있습니다.
'C | C++' 카테고리의 다른 글
스트림 입출력 개관 (0) | 2018.05.31 |
---|---|
검색과 정렬 함수 (0) | 2018.05.31 |
스트링 및 메모리 검색 (0) | 2018.05.31 |
스트링과 메모리 비교 (0) | 2018.05.31 |
스트링 연결과 제한된 복사 (0) | 2018.05.24 |