C/C++ 관련 포스팅 목록
2020/07/01 - [Linux/C] - C/C++ API 문자열 특정 문자로 나누기(strtok)
2020/06/28 - [Linux/C] - C/C++ API strchr(특정 문자 위치 검색)
2020/06/27 - [Linux/C] - C/C++ API access(파일 존재 여부 확인)
2020/06/26 - [Linux/C] - C/C++ API sprintf(문자열 붙이기)
2020/06/29 - [Linux/Python] - Python split 함수(문자열 자르기)
목차
API의 필요성
오늘 포스팅에서는 이전에 포스팅한 C언어의 strtok 사용법을 조금더 응용해보겠습니다. Python 강좌에서 설명한 Split 함수는 특정 문자열을 공백문자(" ")와 같은 문자로 나누어 단어단위로 처리할 수 있었습니다. 또한 C에선 strtok을 활용하여 이런 처리가 가능한 것을 확인하였습니다. 하지만 strtok 함수는 기존의 문자열 원본의 값을 변형시켜 재활용할 수 없는 문제점이 있었습니다.
반면 Python의 Split 함수는 원본 문자열의 수정 없이 나뉘어진 단어들을 처리하고 출력할 수 있었습니다. 그렇다면 C에서는 이런 방식이 불가능할까요? 아닙니다. 가능합니다. 하지만 방식이 조금 다를 뿐이죠.
C언어는 우리가 알고있듯이 절차지향 언어입니다. 위에서 정의되어있지 않으면 아래에서 사용할 수 없습니다. 순서가 너무 중요하기때문에 객체 지향언어 처럼 쉽게 객체를 생성하고 가공하고 처리할 수 없습니다. 우리는 C언어에서 최대한 Python의 Split 함수와 비슷하게 사용할 수 있는 방법을 알아보겠습니다.
함수 구조 설명
이번 포스팅에선 기본 라이브러리 API가 아닌 우리가 직접 함수들을 구성해보겠습니다. 기존의 원본 문자열을 수정하지 않기 위해 특별한 구조체를 생성하겠습니다.
typedef struct splited_ctx {
char **strs;
int cnt;
int max;
} split_ctx_t;
# 타입 소개
split_ctx_t 타입
- 나뉘어진 단어들을 저장하고 사용할 수 있는 구조체이다.
# split_ctx_t 타입의 멤버변수 소개
char *sttrs
- 나뉘어진 단어들이 저장될 char형 포인터 변수의 배열 변수이다.(더블포인터)
int cnt
- 나뉘어진 단어들의 총 개수를 저장할 정수형 변수
int max
- 실시간으로 단어를 나누면서 strs 변수의 저장할 수 있는 크기보다 더 많아지면 실시간으로 크기를 늘리기 위한 크기 값
split_ctx_t *split(const char *src, char *token)
# 인자
const char *src
- 특정 문자로 잘려질 문자열 포인터
char *token
- 자를 기준이되는 특정 문자열 포인터
# 반환
split_ctx_t *
- 특정 문자 기준으로 나뉘어진 단어들을 저장한 split_ctx_t 타입 변수포인터를 반환한다. malloc을 사용하여 동적으로 할당한 변수이기 때문에 다 사용하면 반드시 destroy_split_ctx() 함수에 전달하여 메모리 리소스 해제를 해야한다. 그렇지 않을 경우 메모리 누수가 발생할 수 있다.
예제 목표
이번 포스팅의 목표는 strtok을 사용하여 문자열을 특정 문자로 자르는 원리를 확실히 파악하는 것에 있습니다. strtok은 원본 문자열을 수정하기 때문에 우리가 구현할 split 함수에서 strdup API를 사용하여 원본 문자열을 다른 변수에 복사하고 있습니다. 이 복사한 값을 사용하여 원본 문자열이 수정될 이유가 없습니다.
또한 malloc API를 사용하여 동적으로 저장 변수를 할당하고 있으며 만약 나뉘어진 단어들의 개수가 이 크기보다 커질 경우 realloc API를 사용하여 메모리를 더 크게 재할당 하고 있습니다.
동적 할당 메모리는 사용후 반드시 free를 사용하여 리소스 해제를 해야합니다. 메모리 누수를 방지하기 위해서죠.
코드 작성
$ cd /tmp
$ mkdir c_split_example; cd c_split_example
$ vim c_split_test.c
#include <stdio.h>
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#define ARRAY_MAX 10
typedef struct splited_ctx {
char **strs;
int cnt;
int max;
} split_ctx_t;
void destroy_split_ctx(split_ctx_t *ctx) {
int index = 0;
for (index = 0; index < ctx->cnt; index++) {
if (ctx->strs[index]) free(ctx->strs[index]);
}
if (ctx->strs) free(ctx->strs);
}
split_ctx_t *split(const char *src, char *token) {
split_ctx_t *split_ctx = NULL;
char *copy_str = NULL, *copy_str_head = NULL;
char *ptr = NULL;
int index = 0;
// splited_ctx 초기화
split_ctx = (split_ctx_t *)malloc(sizeof(split_ctx_t));
if (!split_ctx) return NULL;
memset(split_ctx, 0, sizeof(split_ctx_t));
split_ctx->strs = (char **)malloc(sizeof(char **) * ARRAY_MAX);
if (!split_ctx->strs) return NULL;
memset(split_ctx->strs, 0, sizeof(char **) * ARRAY_MAX);
split_ctx->max = ARRAY_MAX;
// src 문자열 복사
copy_str = (char *)strdup(src);
if (!copy_str) return NULL;
copy_str_head = copy_str;
// 문자열 나누기 시작
ptr = strtok(copy_str, token);
while (ptr) {
// 만약 메모리에 할당 크기보다 더 커질 경우 더 큰 공간을 재할당
if (split_ctx->max <= split_ctx->cnt) {
split_ctx->strs = (char **)realloc(split_ctx->strs, (sizeof(char **) * split_ctx->max) + (sizeof(char **) * ARRAY_MAX));
if (!split_ctx->strs) return NULL;
split_ctx->max = split_ctx->max + ARRAY_MAX;
}
// 나뉘어진 단어(ptr) 복사
split_ctx->strs[index] = (unsigned int)strdup(ptr);
if (!split_ctx->strs[index]) return NULL;
split_ctx->cnt++;
index++;
ptr = strtok(NULL, token);
}
// 동적 할당한 변수 리소스 반환
if (copy_str_head) free(copy_str_head);
return split_ctx;
}
void main() {
int index = 0;
const char *example_text = "hello world korea yoo hey gogogo my name is hws help me go school!\n";
split_ctx_t *split_ctx = split(example_text, " ");
printf("잘려진 문장들\n");
for (index = 0; index < split_ctx->cnt; index++) {
printf("%s\n", split_ctx->strs[index]);
}
printf("\n");
printf("example_text 문자열: %s\n", example_text);
// 잘려진 문장들을 다 사용했으면 반드시 메모리 헤제할것
destroy_split_ctx(split_ctx);
}
실행
$ cd /tmp/c_split_example
$ gcc -c c_split_test.c -o c_split_test.out
$ gcc -o c_split_test c_split_test.out
$ ./c_split_test
결과
위와 같이 example_text 문장을 공백 문자(" ") 단위로 자르기 위해서 split 함수를 사용하였습니다. split 함수는 split_ctx 타입 변수에 나뉘어진 단어들을 저장하여 반환하고 우린 반복문을 통해 split_ctx->strs 배열에 접근하며 나뉘어진 단어들을 출력할 수 있었습니다.
이처럼 strtok API를 활용하여 원본 문자열을 건드리지 않고 Python의 Split 함수처럼 사용할 수 있는 방법에 대하여 알아보았습니다.