본문 바로가기
프로그래밍/C언어

C언어 공부하기 - 문자열 함수 구현하기

by 헬맷쓰다 2020. 11. 1.
반응형

지난 시간에는 문자열과 배열에 대해서 알아봤습니다.

2020/10/28 - [프로그래밍/C언어] - C언어 공부하기 - 문자열과 배열

잠깐 리뷰를 해보면, 문자열은 문자들로 이루어진 널문자('\0', 값 0)로 끝나는 배열입니다. 문자형 배열, 문자형 포인터 2가지로 정의할 수 있는데, 전자는 배열이므로 변경이 가능하고 후자는 포인터이므로 변경이 불가능합니다.
마지막으로 문자열이 길이를 반환하는 함수 예제를 만들어 봤죠.

 

이번 시간에는 문자열 함수 몇 가지를 직접 구현해 보도록 하죠. (참고 : The C Programming Language 2nd E.)

다음과 같이 배열과 포인터로 문자열을 정의했다고 합시다.

char arr_msg[] = "Hello World!";
char *ptr_msg  = "Hello World!";

그러면 배열과 포인터의 차이는 아래 그림과 같은 모습입니다.

지난 시간에 구현한 문자열 길이 함수 my_strlen은 넘어가고 문자열 복사와 비교 함수에 대해 알아보겠습니다.

 

1) 문자열 복사 함수

함수의 원형(Prototype)은 다음과 같습니다.

void my_strcpy(char *s, const char *t);

함수 앞에 void 형으로 붙은 것은 반환하는 값이 없다는 의미로 생각하시면 됩니다. 함수의 파라미터는 두개로 둘다 문자형 포인터입니다. 그런데 두번째 파라미터 앞에는 const라는 지시자가 붙어 있습니다. const는 상수(=변경할 수 없는 것)를 의미한다고 보시면 됩니다. 

그럼 이 함수의 역할은 문자열 t를 s에 복사하는 일을 하게 됩니다. 구현한 코드는 아래와 같습니다.

void my_strcpy (char *s, const char *t)
{
    int i = 0;
    while ((s[i] = t[i]) != '\0')
        i++;
}

while문의 조건을 보면 문자형 배열의 첫번째 요소부터 인덱스 i 를 증가해 가면서 복사(s[i] = t[i])를 했는데요, 탈출조건은 배열의 요소가 널문자('\0')일 때 입니다.

배열의 이름이 배열의 첫번째 요소를 가리키므로 포인터와 동일한 역할을 한다고 말씀을 드렸는데 기억하시죠? 함수의 파라미터는 포인터로 선언했는데 함수의 내용은 배열로 사용해도 같은 역할을 하는 이유입니다. 배열로 표현을 하니까 눈에 확 들어옵니다만 실제 strcpy코드는 위와 같이 작성되지 않고 다음과 같이 될 겁니다.

void my_strcpy (char *s, const char *t)
{
    while ((*s++ = *t++) != '\0')
        ;
}

첫 번째 코드 보다 훨씬 깔끔해졌습니다. while 문 안의 조건이 포인터 연산으로 되어 있습니다. 초기에 t는 문자열의 첫번째 주소, 그러니까 첫번째 문자를 가리킨다고 했죠? 그러면 *t는 첫번째 문자의 내용이 되겠네요. 그리고 증가 연산자 ++이 앞에 붙으면 먼저 값을 변경하고, 뒤에 붙으면 값을 사용한 다음에 증가 시킨다고 했습니다.

그래서 *s++ = *t++ 부분은 t의 첫 번째문자를 s의 첫번째 문자에 복사한 다음에 각각 다음 문자를 가리키도록 ++을 반복해서 사용했습니다. 그런데 while의 조건이 참(0이 아닌 값)인 경우 계속 실행되는데, 널문자('\0')는 값이 0이므로 널문자를 복사한 경우 종료가 됩니다. 따라서 널문자('\0')일 비교하는 내용을 제거하여 코드를 조금 더 단순화 시킬 수 있습니다. 

void my_strcpy (char *s, const char *t)
{
    while (*s++ = *t++)
        ;
}

문자열 복사 함수를 사용할 때 아주 주의할 점이 있습니다. 첫 번째 파라미터에 문자열이 복사 될 만큼의 배열이나 메모리 할당이 되어 있어야 한다는 것입니다. 만일 주소를 가리키는 포인터가 첫번째 파라미터로 들어가면 정상적으로 동작하지 않게 됩니다.

#include <stdio.h>

void my_strcpy (char *s, const char *t)
{
    while (*s++=*t++) ;
}

void main()
{
    char hello_array[20]; 
    char *hello_pointer = "Hello! World!";

    my_strcpy (hello_array, hello_pointer);
    printf("%s\n", hello_array);
    printf("%s\n", hello_pointer);
}

main 함수에서 hello_array 배열에 20바이트로 할당한 예제입니다. 만약 main 함수의 첫번째 줄을 다음과 같이 작성한다면 정상동작하지 않습니다.

char *hello_array;
// char hello_array[];
// char *hello_array[10];

- 메모리 할당이 안된 포인터로 선언한 경우
- 크기가 지정되지 않은 배열로 선언한 경우 초기화가 안되어 있으므로 컴파일 에러
- 복사할 문자열의 크기보다 작은 크기의 배열로 선언한 경우

 

2) 문자열 비교 함수

문자열 비교 함수의 원형은 다음과 같습니다.

int my_strcmp(const char *s, const char *t);

이 함수는 두개의 문자열 상수를 입력받아 문자열을 비교하여 결과 값을 반환합니다. 문자열이 같으면 '0', 사전적 순서가 앞에 것이 빠르면 '음수', 뒤의 것이 빠르면 '양수'를 반환합니다.

코드를 작성하면 다음과 같겠네요.

int my_strcmp (const char *s, const char *t)
{
    for (; *s == *t; s++, t++)
        if (*s == '\0')
            return 0;
    
    return *s - *t;
}

for문의 초기값은 첫번째 문자 부터 비교를 하니 비어 있고, 탈출 조건은 문자가 서로 같을 때는 for문을 계속 수행하고, 수행 후에 증가연산자 ++을 사용하여 다음 문자를 가리키도록 했습니다. for문 안의 if문은 비교하는 두 문자가 같은데 첫번째 파라미터의 문자만 체크해도 되므로 널문자('\0')인 경우는 문자열이 같기 때문에 0을 반환합니다.

비교하는 두 문자가 다른 경우 for문을 빠져 나오고 첫번째 문자에서 두번째 문자를 뺀 값을 반환합니다. 여기서 비교하는 문자가 'A'와 'B'라고 하면 'A'에 해당하는 ASCII값 65에서 'B'에 해당하는 ASCII값 66을 뺀 값인 -1을 반환합니다.

 

3) 문자열 붙이기 함수

마지막으로 문자열 붙이는 함수입니다. 함수 원형(Prototype)은 다음과 같습니다.

void my_strcat(char *s, const char *t);

문자열 복사 함수와 유사한 함수 파라미터를 갖습니다. 문자열 상수 t를 s의 뒤에 붙이는 역할을 합니다. 문자열의 끝을 표시하는 널문자('\0')에서 부터 붙인다는 사실을 기억하시길 바랍니다.

코드를 구현해 보면,,, 간단합니다.

void my_strcat(char *s, const char *t)
{
    while (*s) *s++;
    
    while (*s++=*t++) ;
}

while문을 두개 썼는데요. 첫번째 while문은 널문자('\0')가 나올 때까지 포인터를 증가 시키는 거구요. 두번째 while문은 문자열 복사와 동일한 코드인데, NULL문자 부터 t의 문자열을 복사하는 역할을 합니다.

이 함수를 사용할 때도 주의할 점이 s가 t를 붙일 수 있을 만큼 크기가 할당된 배열이나 메모리여야 한다는 점입니다. 그렇지 않을 경우 정상적인 동작을 하지 않을 수 있습니다.

void main ()
{
    char s[20] = "Hello";
    char *t = " World!";
    
    my_strcat(s, t);
    
    printf ("s = %s\n", s);
}

main 함수 안의 첫번째 코드에 배열 20바이트로 넉넉하게 잡았는데요. 다음과 같이 쓰면 안된다는 겁니다.

char *s = "Hello";
// char s[] = "Hello";
// char s[10] = "Hello";

첫번째 줄은 포인터로 썼는데 문자열 상수이므로 변경할 수 없구요.

두번째 줄은 크기가 정해지지 않은 배열은 초기화 할 때 크기가 정해지고 크기를 변경할 수 없습니다.

세번째 줄은 뒤의 문자열을 붙이기에는 작은 크기의 배열을 선언한 경우입니다.

두번째, 세번째의 경우 제가 테스트 한 환경에서 오류가 나지는 않았지만, 우연치 않게 배열이 할당된 메모리가 비어 있어 오류가 아닌 것처럼 동작한 경우로 생각됩니다. 하지만 이는 명백한 오류이며, 언제 어느 시점에 크게 터질 지 알수 없는 폭탄을 갖고 있는 것과 마찬가지 이므로 더욱 주의해야 합니다.

 

 

이 외에 strncpy, strncat, strncmp등 중간에 n이 들어간 문자열 함수들이 있는데요. 함수의 파라미터로 문자열의 처음 n번째까지 처리하는 함수들입니다. 얘들은 추후에 추가하도록 하죠.

다음 시간에는 구조체와 공용체에 대해 공부해 봅시다. 안녕~

반응형

댓글