문자열
문자열은 C언어와 C++을 다루면서 중요한 부분이라고 생각한다. 게임을 플레이하면서 문자 하나 출력되지 않는다고 생각해 보자 어우... 바로 접어버릴 것만 같다. 또 문자열을 다루는 문법은 여태껏 배운 다양한 기술이 필요하기도 하니 복습하는 느낌으로 다시 천천히 살펴보도록 하자. 일단 우리는 char자료형에 대해서 알고 있다 이는 1바이트 크기의 저장공간을 갖고 있으며 사실 int 자료형과 같이 정수자료를 저장하는 자료형이다만, 사실 char 자료형은 문자 하나를 나타내는 아스키코드(==정수)를 담아내기에 최적화 되어있어 정수보다는 문자를 저장하기에 특화되어 있다.
그럼 문자열은 무엇일까? 간단하게 생각해서 Array of character 즉 문자들의 배열 내가 공부할 때 이해하기로는 char 자료형의 배열이라고 생각하면 좀더 쉽게 와닿을 것이다.
C Style String
C Style String은 문자열 중에서도 C언어에서 사용되며 마지막에 \0(NULL Character)가 저장되어 있는 문자열이다. 컴퓨터는 연역적인 사고가 불가능하기 때문에 시작이 주어지면 끝도 반드시 주어져야 한다 끝이 없다면 컴퓨터는 문자열이 끝이 어딘지 모르고 해당 메모리의 쓰레기 값도 모두 읽어버릴 것이다. 이런 면에서 C Style String은 따로 길이에 대한 데이터를 문자열에 저장하지 않기 때문에 가장 최소한의 메모리로 문자열과 그 길이의 표현이 가능하다 그러나 길이를 알려고 시도한다면 문자 배열의 끝까지 가야 알 수 있다는 단점이 있다.
#include <stdio.h>
int main(void)
{
char str1[10] = "Hello,\0";
char str2[10] = {'w', 'o', 'r', 'l', 'd', '\0'};
printf("%s %s", str1, str2);
return 0;
}
char 자료형의 크기는 10인 배열을 선언해주고 그 값으로 "Hello, \0"의 문장을 하나는 w, o, r, l, d, \0 값을 하나씩 각 첨수마다 대입해 주었다. 둘의 결과는 어떨까?

즉 문자열을 만들기 위해서는 두 방법 모두 유효한 방법이라는 것을 알 수 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define MAX (100)
int main(void)
{
char cstr[MAX] = { 0, };
scanf("%s", cstr);
printf("%s", cstr);
return 0;
}
이 코드는 어떤 결과가 나올까? 문자열의 크기는 미리 정의된 MAX의 값을 따라 100으로 정의되었고 scanf()에 의해서 사용자가 입력하는 값이 문자열 cstr에 기록될 것이다. 한번 Hello, world! 를 입력해 보도록 하겠다.

Hello, 만 출력된 모습이다. 이번에는 Hello만 입력해 보자

원인은 간단하다 scanf()는 입력 중간에 공백이 있다면 읽는 것을 멈춘다 즉 Hello, 이후 공백을 만나 그 전의 문자까지만 읽어 들이고 그 부분만 출력하는 것이다. 공백이 일종의 '\0'의 작용을 하는 것이다. 다음 예제도 보자
#include <stdio.h>
#define MAX (21)
int main(void)
{
char cstr[MAX] = "Hello, world!";
printf("%s", cstr);
printf("%s", cstr + 1);
return 0;
}
char자료형의 cstr문자열을 선언 크기는 앞서 정의한 MAX값인 21을 갖으며 그 내용으로는 "Hello, world"를 갖고 있다. 첫 번째 출력에서는 cstr을 그대로 출력하고 두 번째에는 cstr에 1은 더한 값을 출력하게 되어있다. 우리가 앞서 포인터를 배울때 배운것으로는 배열의 이름은 그 배열의 첫번째 원소 즉 cstr은 cstr[0]의 주소를 가리키는 포인터라고 짚고 넘어간 바 있다. 그럼 두번째 출력은 cstr [1]부터 문자열이 출력되어 결과는 다음과 같을 것이다. Hello, world!(첫 번째 출력) ello, world!(두 번째 출력)

다음 예제도 바로 보자
#include <stdio.h>
#include <string.h>
int main(void)
{
char cstr[] = "Hello, world!";
int i;
for (i = 0; ; ++i)
{
if (' ' == cstr[i])
{
break;
}
printf("%c", cstr[i]);
}
return 0;
}
char자료형의 크기는 따로 지정하지 않은 cstr 문자열을 선언하고 그 값으로 "Hello, world!"를 받고 있고 int 자료형의 변수 i를 선언해 주었다. for문을 이용한 반복문으로 cstr의 각 i 번째 원소들을 출력하는 것을 반복하게 하고 있고 만약 cstr [i]가 ' '값을 가리키고 있다면 반복문을 break 하도록 해 두었다. 그러면 그냥 Hello, world! 가 모두 출력되지 않을까? 한번 봐보자.

앗차차 Hello, world! 사이의 공백에서 break 되어버리고 말았다.
문자열의 길이 strlen() 함수
#include <stdio.h>
#include <string.h>
#define LENGTH (12)
int main(void)
{
char string1[] = "Hello world!";
char string2[LENGTH] = "Goodbye~!";
/* Q. 아래 출력의 결과를 예상해 보자. */
printf("string1 size: %zu\n", sizeof(string1));
printf("string1 length: %zu\n", strlen(string1));
printf("string2 size: %zu\n", sizeof(string2));
printf("string2 length: %zu\n", strlen(string2));
return 0;
}
하나의 문자열에는 크기를 정해주지 않고 값을 Hello world! 를 넣었고 다른 하나의 문자열에는 LENGTH값 12를 크기로 정해주고 Goodbye~! 를 넣어주었다. 첫 번째 출력에서는 string1의 size of 값을, 두 번째 출력에서는 string1의 strlen을 출력하게 되어 있으며 세 번째 출력에서는 string2의 size of 값과 네 번째 출력에서는 strlen 값을 출력하게 되어있다. 우리는 string1의 크기를 따로 지정하지 않았기에 배열의 특성에 따라 입력받은 값의 크기만큼 지정되어 있을 것이다. H, e, l, l, o, ' ', w, o, r, l, d,! 총 12글자의 크기를 갖고 있을 것이다. string2는 어떨까? 이미 12만큼의 크기를 받았으니 12가 출력될 것이다. 결과를 확인해 보자

여기서 알 수 있는 것이 size of와 strlen의 차이이다 size of는 C Style String의 문자열 끝에 포함된 \0까지 읽어들이기에 13이 출력되고 strlen은 \0 전까지만 읽기에 12가 출력된다. 마찬가지로 string2는 애초에 할당받은 크기가 12이기에 12로 출력되고 실제 문자는 9글자+ \0 까지 10글자지만 \0까지만 읽는 strlen이기에 9로 출력되는 것이다.
#include <stdio.h>
#include <string.h>
#define LENGTH (3)
int main(void)
{
size_t length; //typedef unsigned int size_t; size_t는 양수 정수 자료형이다.
char str1[] = { 'a', 'b', 'c' };
char str2[LENGTH];
str2[0] = 'd';
str2[1] = 'e';
str2[2] = 'f';
length = strlen(str1);
printf("str1 length : %zu\n", length);
length = strlen(str2);
printf("str2 length : %zu\n", length);
return 0;
}
자 우리는 strlen이 \0 값의 전까지 읽는다고 했다. 근데 다음 코드처럼 \0이 없다면? str1은 값이 a, b, c만 입력된 상태이고 str2는 크기가 3인데 d, e, f 값이 꽉 채워서 들어간 상태이다 두 문자열 모두 끝에 \0이 없는 상황에서 strlen을 돌린다면 어떻게 될까? 문자열이 저장된 메모리를 쭈욱 읽으며 \0 값이 나올 때까지 계속 읽을 것이다. 운 좋게 \0이 나온다면 말이다. 이 코드의 출력 결과는 사람마다 다르기 때문에 한번 예상해보고 str1에 \0 원소를 하나 추가해서 다시 컴파일해서 그 결괏값을 비교해 보도록 하자
두 문자열의 비교 strcmp() 함수
문자열은 기본적으로 사전식 순서를 기반으로 두 문자열을 비교한다 ABCD는 ABCE보다 앞에 있고 ABC는 abc보다 앞에 있다 또 ABC는 ABCD보다 앞에 있는 형식으로 두 문자열의 순서를 결정한다. strcmp()는 두 문자열의 순서를 비교하는 함수이다. 다음 예제를 보자
#include <stdio.h>
#include <string.h> // strcmp() 함수를 사용해보기 위해 인클루드.
int main(void)
{
char str1[] = "Hello";
char str2[] = "yello";
char str3[] = "Hell";
printf("strcmp(str1, str2): %d\n", strcmp(str1, str2));
printf("strcmp(str1, str3): %d\n", strcmp(str1, str3));
printf("strcmp(str2, str3): %d\n", strcmp(str2, str3));
return 0;
}
str1과 str2를 비교하면 H가 y보다 앞에 있으기에 str1이 str2보다 앞에 오게 된다. str1과 str3을 비교하면 뒤에 o가 없는 str3이 str1보다 앞에 오게 된다. str2와 str3을 비교하면 str3이 str2보다 앞에 오게된다 결과를 봐보자.

이를 통해서 strcmp(a, b)의 작동 원리를 살펴보자 a를 기준으로 a가 b보다 앞에 오게 된다면 음수(-1)를 a가 b보다 뒤에 있다면 양수(+1)를 출력한다 여기서 주의해야 할 것은 -1이 나왔다고 해서 1칸 앞에 있다는 것이 아니라는 것이다. 숫자는 신경 쓰지 말고 부호만 신경 써 음수가 나오면 앞에 있다는 것을, 양수가 나오면 뒤에 있다는 것만 기억해 두도록 하자.
문자열의 복사 strcpy() 함수
문자열을 복사하는 함수 strcpy() 말 그대로 문자열을 복사하는 함수이다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
enum { LENGTH = 10};
int main(void)
{
const char str[LENGTH * 2] = "Hello world";
char cloned_str[LENGTH] = { 0, };
strcpy(cloned_str, str);
printf("cloned_str : %s\n", cloned_str);
return 0;
}
strcpy()는 문자열의 끝을 알리는 \0까지 포함해 모든 바이트를 목적지에 검사 없이 복사를 한다. 만일 크기가 10인 문자열을 크기가 4인 버퍼에 복사하면 버퍼 경계를 넘어 메모리가 덮어 써지고 버퍼 오버플로우가 발생한다. 이는 차후에 큰 문제가 발생할 수 있어 크기를 잘 지정해 주는 것이 중요하다.
#include <stdio.h>
enum { LENGTH = 1024 };
void my_strcpy(char* dst, const char* src) // Q. 왜 dst에는 const 키워드가 없을까?
{
while (*src != '\0')
{
*dst++ = *src++;
}
*dst = '\0';
return;
}
int main(void)
{
const char string[LENGTH] = "Hello, world!";
char cloned_string[LENGTH] = { 0, };
printf("string: %s\tcloned_string: %s\n", string, cloned_string);
my_strcpy(cloned_string, string);
printf("string: %s\tcloned_string: %s\n", string, cloned_string);
return 0;
}
위의 예제처럼 나만의 복사함수를 만들어서 사용하는데 조심해야 할 부분이 있다 아까 strlen()을 예시로 생각해 보자 strlen()은 \0이 감지될 때 까지 계속해서 문자열을 읽는다고 했다. 그만큼 C Style String 문자열들은 \0이 중요한 역할을 한다. 근데 mystrcpy()를 잘못사용해서 예컨데 크기가 10인 문자열을 크기가 4밖에 안되는 문자열에 복사한다 생각해보자 전부 복사되지도 않을뿐더러 자료를 받은 문자열의 \0 캐릭터까지 날아가버려 다음에 사용할 함수에 영향이 갈 수도 있는 노릇이다. 그렇기에 사용되는 것이 다음 문법이다.
char* strncpy(char* dst, const char* src, size_t count)
위의 예제의 경우처럼 dst가 더 짧은 경우에 count 변수로 막을 수 있는 좀 더 안전한 함수이다. 만약 dst가 더 짧을 경우에는 count 만큼(dst 길이 만큼만) 복사되며(NULL캐릭터를 호출 후에 붙여줘야 한다) dst가 더 긴 경우에는 남은 요소를 NULL로 채워준다 결과적으로는 strcpy()보다는 느릴수도 있지만 좀더 안전하다는 매리트가 있는 셈이다.
그 외의 string.h 헤더파일에 있는 함수들
char* strcat(char* dst, const char* src)
dst 문자열 뒤에 src 문자열을 덧붙이는 함수(concatenate) dst문자열의 NULL 캐릭터가 있는 위치부터 src 문자열을 추가해 준다. 즉 dst의 크기가 충분하지 않다면 사용하면 위험하다.
char* strstr(const char* str, const char* substr)
str문자열에 substr문자열이 포함되어 있다면 str문자열 속 첫 substr문자열의 시작주소를 반환한다. NULL포이넡가 반환된다면 없다는 뜻이다. 여기서 주의할 것은 매개변수로 받는 str은 const char*이지만 반환 자료형은 char*으로 const가 사라진다 그렇기에 인자로 데이터 섹션의 read only에 위치하는 문자열을 전달하면 안 된다고 한다.(필자도 이게 무슨 소리인지 아직은 모르겠지만 그렇다고 한다.) 다음은 참고하면 좋은 내용이다.

char* strtok(char* str, const char* delims)
str문자열을 구분자 문자열 delimeters로 자르는 함수이다. 잘린 문자열의 시작 주소를 반환한다. str문자열에 const가 없는 것을 보도록 하자 이 함수는 원본 문자열 str을 변경하는 함수이다. 대부분의 C Style String 함수들은 위에서 보다시피 새로운 문자열을 만들어서 반환하지 않으며 되도록이면 매개변수 자료형에 const char*을 사용한다. 원본 문자열을 건드리지 않겠다는 의미이지만 strtok() 함수가 예외인 부분이다. 원본 문자열을 보호하고 싶으면 사본을 만들어서 적용하도록 하자.
'unreal 5기' 카테고리의 다른 글
| 250903 언리얼엔진 본캠프 22일차 파일1 (0) | 2025.09.03 |
|---|---|
| 250901 언리얼엔진 본캠프 20일차 동적할당 (2) | 2025.09.01 |
| 250828 언리얼엔진 본캠프 18일차 디자인패턴 (1) | 2025.08.28 |
| 250826 언리얼엔진 본캠프 16일차 템플릿 (1) | 2025.08.26 |
| 250822 언리얼엔진 본캠프 15일차 클래스와 객체지향적 프로그래밍 (2) | 2025.08.25 |