본문 바로가기

Programming Language/C

문자열

1. 문자열의 개념

문자열은 문자들의 배열을 뜻합니다. 문자열이 문자들의 배열이라고 했으니 Hello World라는 문자열은 Hello와 World 사이의 띄어쓰기를 포함하여 11개의 공간을 가진 배열일까요? 그렇지 않습니다. 문자열은 컴퓨터 메모리 구조상에서 마지막에 Null값을 포함합니다. Null값은 컴퓨터 상에서 존재하지 않는다는 뜻을 가지고 있고, Hello World 다음에 들어간 null 값은 아무런 의미가 없습니다. 그림으로 Hello World 문자열을 표현한다면 다음과 같습니다.

참고로, null 값은 \0과 같은 형태로 표현되기도 합니다.

이러한 null값은 문자열의 끝을 알리기 위한 목적으로 항상 문자열의 마지막에 삽입됩니다. 따라서 printf() 함수를 실행할 때 컴퓨터는 null을 만날 때까지 한 글자씩 순서대로 배열의 데이터를 출력합니다. 달리 생각해보면 null 값을 만나지 않았을 때는 계속해서 출력하는 것입니다.


2. 문자열과 포인터

포인터 변수에는 문자열 자체를 상수처럼 넣을 수 있습니다. 이 때, 문자열 형태로 포인터를 사용하면 포인터에 특정 문자열의 주소를 넣는 것입니다.

#include <stdio.h>

int main(void) {
    char* a = "Hello World";
    printf("%s\n", a);
    system("pause");
    return 0;
}

문자열은 내부적으로 배열 형태로 처리가 됩니다. 또한 배열은 포인터로 치환이 가능하기 때문에 포인터 자체를 출력하도록 만들면 알아서 null 값을 만날 때까지 출력을 하게 됩니다. 따라서 위 코드의 결과도 Hello World 전체가 출력되는 것입니다.

위 코드와 같이 큰 따옴표 안에 문자열이 들어가는 구조를 문자열 리터럴이라고 합니다. 문자열 리터럴과 같은 구조를 가지면, 컴파일러가 알아서 문자열 자체가 특정한 컴퓨터 메모리 주소에 담길 수 있도록 남아 있는 메모리 공간 중에서 주소를 결정해줍니다. 그리고 만들어진 특정한 문자열의 주소는 포인터가 가지게 됩니다.

"Hello World"와 같은 문자열을 문자열 리터럴 방식으로 선언하게 되면 상수로써 읽기 전용으로 사용이 됩니다. 프로그램이 실행되는 도중에는 변경이 불가능하며, 굳이 문자열을 변경해서 사용하고 싶다면 포인터가 가리키고 있는 문자열을 다른 것으로 변경하면 됩니다.

또한 포인터로 문자열을 선언했다고 하더라도 기존의 배열처럼 처리가 가능합니다.

#include <stdio.h>

int main(void) {
    char* a = "Hello World";
    printf("%c\n", a[0]);
    printf("%c\n", a[1]);
    printf("%c\n", a[2]);
    system("pause");
    return 0;
}

결과:

H
e
l
계속하려면 아무 키나 누르십시오 . . .


3. 문자열 입출력 함수

지금까지 문자열을 입력 받을 때는 scanf() 함수를 주로 사용했습니다. 이번에는 scanf() 함수가 아닌 gets() 함수를 알아보겠습니다. scanf() 함수는 공백을 만날 때까지 입력을 받았지만, gets() 함수는 공백까지 포함하여 한 줄을 입력받습니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
    char a[100];
    gets(a);
    printf("%s\n", a);
    system("pause");
    return 0;
}

결과:

Hello World
Hello World
계속하려면 아무 키나 누르십시오 . . .

공백까지 포함하여 한 줄을 입력받는 특성 덕분에 Hello World를 입력하면 Hello만 출력되는 것이 아니라 Hello와 World 사이의 공백까지 포함하여 전부 출력이 가능합니다.

gets()함수에는 한 가지 문제점이 있는데, 버퍼의 크기를 벗어나도 입력을 받는다는 점입니다. 버퍼의 크기를 벗어나는 문자열을 입력 받으면 프로그램의 다른 부분을 사용자가 임의대로 덮어쓰기를 해버리는 문제가 발생합니다.

gets() 함수의 이러한 취약점을 개선하여 사용되는 것이 gets_s() 함수입니다. gets_s() 함수는 버퍼의 크기를 철저하게 지키는 함수이며, gets_s(a, sizeof(a)) 와 같은 형식을 지킵니다. 여기서 sizeof(a)는 얼마만큼의 크기의 버퍼를 넣어 줄 수 있는지 정해주는 것입니다. 참고로 sizeof(a)는 c언어에서 기본적으로 제공해주는 함수로써 특정한 배열의 전체 크기가 얼마인지를 알려주는 함수입니다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
    char a[20];
    gets_s(a, sizeof(a));
    printf("%s\n", a);
    system("pause");
    return 0;
}

위 코드는 배열의 크기를 20으로 정했는데 만약 입력한 내용이 범위를 넘는다면 바로 런타임(Runtime) 오류가 발생합니다.


4. 문자열 처리를 위한 다양한 함수

아래에 나열된 문자열 함수들은 C언어의 문자열처리를 돕는 기본적인 문자열 함수들로 숙지하고 있는 것이 좋습니다. 또한 C언어에서의 문자열 함수는 <string.h> 라이브러리에 포함되어 있습니다.

문자열 함수 함수의 역할
strlen() string length. 문자열의 길이를 반환합니다.
strcmp() string compare. 문자열 1이 문자열 2보다 사전적으로 앞에 있으면 -1, 뒤에 있으면 1을 반환합니다.
strcpy() string copy. 문자열을 복사합니다.
strcat() string concatenate(사슬같이 잇다, 연쇄시키다). 문자열 1에 문자열 2를 더합니다.
strstr() string string. 문자열 1에 문자열 2가 어떻게 포함되어 있는지를 반환합니다.

4.1. strlen()

#include <stdio.h>

int main(void) {
    char a[20] = "RTX 2070super";
    printf("문자열의 길이: %d\n", strlen(a));
    system("pause");
    return 0;
}

결과:

문자열의 길이: 13
계속하려면 아무 키나 누르십시오 . . .

4.2. strcmp()

#include <stdio.h>

int main(void) {
    char a[20] = "Pen Pineapple";
    char b[20] = "Apple Pen";
    printf("두 배열의 사전 순 비교: %d\n", strcmp(a,b));
    system("pause");
    return 0;
}

결과:

두 배열의 사전 순 비교: 1
계속하려면 아무 키나 누르십시오 . . .

4.3. strcpy()

C언어에서는 기본적으로 a = b와 같은 간단한 방식으로 문자열 복사가 되지 않기 때문에 strcpy() 함수를 사용합니다.
strcpy(a, b) 라고 작성하면 a에 b를 복사 붙여넣기 합니다.

#include <stdio.h>

int main(void) {
    char a[20] = "RTX 2070super";
    char b[20] = "RTX 3080ti";
    strcpy(a, b);
    printf("복사된 문자열: %s\n", a);
    system("pause");
    return 0;
}

결과:

복사된 문자열: RTX 3080ti
계속하려면 아무 키나 누르십시오 . . .

4.4. strcat()

strcat()은 뒤에 있는 문자열을 앞에 있는 문자열에 합치기 때문에 앞에 있는 문자열의 크기를 충분히 크게 만들어 두어야 합니다.

#include <stdio.h>

int main(void) {
    char a[30] = "My GPU is ";
    char b[20] = "RTX 2070super";
    strcat(a, b);
    printf("합쳐진 결과 문자열: %s\n", a);
    system("pause");
    return 0;
}

결과:

합쳐진 결과 문자열: My GPU is RTX 2070super
계속하려면 아무 키나 누르십시오 . . .

4.5. strstr()

#include <stdio.h>

int main(void) {
    char a[30] = "I love you";
    char b[30] = "love";
    printf("찾은 문자열: %s\n", strstr(a, b));
    system("pause");
    return 0;
}

결과:

찾은 문자열: love you
계속하려면 아무 키나 누르십시오 . . .

strstr()은 특정 문자열을 찾으려 하는 문장에서 특정 문자열을 찾아 그 위치를 반환하는데, 특정 문자열을 찾은 주소 값 자체를 반환하기 때문에 단순히 출력하도록 하면, 찾은 이후 모든 문자열이 반환됩니다. 이것은 문자열에서 printf() 함수를 실행하면 컴퓨터 내부적으로 null을 만날 때까지 출력한다는 특징을 가지고 있기 때문에 발생합니다.