문자열 길이 구하기
size_t strlen(const char* str);
문자열의 길이를 구하는 표준 함수이며, 널 문자가 나타날 때까지 문자의 개수를 세는 함수이다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str[] = "Hello";
/* 문자열 길이 출력 */
printf("문자열 길이: %zu\n", strlen(str)); /* 널 문자는 포함되지 않음 */
return 0;
}
================ 실행 결과 ================
문자열 길이: 5
기억해야 할 것은 널 문자는 문자열의 끝을 나타낼 뿐 문자열의 길이에는 포함되지 않는다
즉, str[] 배열의 크기는 6이지만 strlen으로 찍히는 길이는 5라는 의미이다
문자열 비교 함수
C에서는 문자열을 비교할 때 strcmp()와 strncmp()를 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str1[] = "apple";
char str2[] = "banana";
/* 문자열 비교 */
int result = strcmp(str1, str2);
/* 비교 결과 출력 */
printf("strcmp 결과: %d\n", result);
return 0;
}
================ 실행 결과 ================
strcmp 결과: -1
strcmp()의 경우 두 문자열을 처음부터 비교하며, 문자마다 아스키 값을 비교하여 첫 번째 차이가 나는 문자의 차이를 반환한다
첫 번째 문자열이 두 번째 문자열보다 작으면 음수, 크면 양수, 같으면 0을 반환하는데, 위의 경우 apple의 0번 인덱스, banana의 0번 인덱스를 비교했을 때 a가 더 작기 때문에 -1이 반환되는 것이다
하지만 이런 strcmp()에는 안전성 문제가 있다. 두 문자열이 언제 끝나는지 알기 위해 널 문자를 찾을 때까지 계속 탐색한다. 만약 널 문자가 없거나 유효한 메모리 범위를 벗어나면 예측 불가능한 동작이 발생할 수 있다
또한 사용하는 상황에 따라 부적절할 수 있다. 예를들어 파일 확장자 비교처럼, 특정 개수의 문자만 비교하고 싶다면 적절하지 않다
정리하자면 strcmp()는 전체 문자열이 정확히 같은지, 그리고 비교할 두 문자열의 메모리가 안전하다는 가정 하에 사용하는 것이 바람직하다.
만약 안전성 문제가 일어나지 않게 보장하고 싶다면 그리고 특정 개수만 비교하고 싶다면 strncmp()라는 메서드를 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str1[] = "apple";
char str2[] = "apricot";
/* 문자열 비교 (앞 3글자만 비교) */
int result = strncmp(str1, str2, 3);
/* 비교 결과 출력 */
printf("strncmp 결과: %d\n", result);
return 0;
}
================ 실행 결과 ================
strncmp 결과: 0
strncmp(str1, str2, n)은 최대 n개의 문자까지만 비교하고, 문자열이 n개보다 짧으면 널 문자를 만날 때까지 비교한다. 그렇기에 불필요한 비교를 줄이고 더 안전하게 사용할 수 있다
위와 같은 특징 때문에 파일 확장자 비교, 접두사 비교, 비교할 문자열을 모를 경우 버퍼 오버플로우를 방지하기 위해 최대 비교 길이를 제한하여 예상치 못한 오류를 방지한다
하지만 단 한가지 조심할 점이, 비교 길이 안에 쓰레기 값이 남아있다면 그대로 출력될 여지가 있기 때문에 주의하여야 한다. 따라서 문자열을 안전하게 사용하기 위하여 문자열 관련 데이터를 다룰 때는, 마지막 인덱스에 널 문자를 명시적으로 삽입해주는 것이 좋다
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "apple";
char str2[10];
strncpy(str2, str1, 5);
str2[5] = '\0'; /* 강제로 널 문자 삽입 */
printf("str2: %s\n", str2); /* 정상 출력 */
return 0;
}
================ 실행 결과 ================
str2: apple
문자열 복사 함수
C에서는 문자열을 복사할 때 strcpy()와 strncpy()를 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char source[] = "Hello, World!";
char destination[20];
/* 문자열 복사 */
strcpy(destination, source);
/* 복사된 문자열 출력 */
printf("복사된 문자열: %s\n", destination);
return 0;
}
================ 실행 결과 ================
복사된 문자열: Hello, World!
char* strcpy(char* destination, const char* source) 는 문자열을 복사하지만, destination 크기가 source보다 작을 경우 버퍼 크기를 초과하는 문자열이 복사되면서 버퍼 오버플로우가 발생한다는 문제가 있다
따라서 보다 안전한 문자열 복사 방법이 필요한데, 그래서 strncpy()를 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char source[] = "Hello, World!";
char destination[6];
/* 문자열 복사 (최대 5개 문자) */
strncpy(destination, source, 5);
destination[5] = '\0'; /* 널 문자 추가 */
/* 복사된 문자열 출력 */
printf("복사된 문자열: %s\n", destination);
return 0;
}
================ 실행 결과 ================
복사된 문자열: Hello
char* strncpy(char* destination, const char* source, size_t num) 는 num 만큼의 문자만 복사한다
하지만 널 문자를 자동으로 추가하지 않기 때문에, 명시적으로 널 문자를 마지막 인덱스에 삽입해야 한다
즉, destination이 source보다 작을 경우 널 문자를 추가하지 않으면, destination을 문자열로 안전하게 사용할 수 없게 된다
만약 제대로 널 문자를 추가하지 않을 경우 쓰레기 값이 찍힐 수 있다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char source[] = "Hello, World!";
char destination[6]; /* destination 크기가 작음 */
/* 문자열 복사 (최대 5개 문자) */
strncpy(destination, source, 5);
/* 널 문자 추가하지 않음 */
/* 복사된 문자열 출력 */
printf("복사된 문자열: %s\n", destination); /* 예상치 못한 동작 발생 가능 */
return 0;
}
그리고, strncpy()는 복사할 문자열이 num보다 짧으면 남은 공간을 널 문자로 채운다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 충분한 크기의 destination */
char source[] = "Hello";
char destination[10];
/* destination을 0xAA로 초기화 (눈에 보이도록) */
memset(destination, 0xAA, sizeof(destination));
/* 문자열 복사 (num이 source보다 큼) */
strncpy(destination, source, 10);
/* 복사된 문자열 출력 */
printf("복사된 문자열: %s\n", destination);
/* 메모리 상태 출력 */
printf("메모리 상태: ");
for (size_t i = 0; i < sizeof(destination); i++) {
printf("%02X ", (unsigned char)destination[i]);
}
printf("\n");
return 0;
}
================ 실행 결과 ================
복사된 문자열: Hello
메모리 상태: 48 65 6C 6C 6F 00 00 00 00 00
문자열 포매팅
C에서는 문자열을 연결할 때 strcat()와 strncat()을 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str1[20] = "Hello, ";
char str2[] = "World!";
/* 문자열 연결 */
strcat(str1, str2);
/* 연결된 문자열 출력 */
printf("연결된 문자열: %s\n", str1);
return 0;
}
================ 실행 결과 ================
연결된 문자열: Hello, World!
strcat()은 destination의 기존 문자열 끝에 source를 이어붙이지만, 버퍼 크기를 확인하지 않으면 버퍼 오버플로우 발생 위험이 있다
따라서 위 함수도 마찬가지로 안전성을 보장하기 위해 strncat()을 사용할 수 있다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str1[20] = "Hello, ";
char str2[] = "World!";
/* 문자열 연결 (최대 3글자만) */
strncat(str1, str2, 3);
/* 연결된 문자열 출력 */
printf("연결된 문자열: %s\n", str1);
return 0;
}
================ 실행 결과 ================
연결된 문자열: Hello, Wor
strncat()은 num 개수만큼 destination에 추가하므로, destination의 크기를 초과하는 문제를 방지할 수 있다. 하지만 여전히 주의해야 할 점이 있다
strncat()은 destination의 남은 공간을 확인하지 않고 문자열을 추가하기 때문에, 안전하게 사용하려면 남은 공간을 확인하는 것이 필수적이다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str1[10] = "Hi";
char str2[] = " there!";
/* strncat을 사용할 때 남은 공간 확인 */
size_t remaining_space = sizeof(str1) - strlen(str1) - 1;
strncat(str1, str2, remaining_space); /* 남은 공간만큼만 추가 */
/* 연결된 문자열 출력 */
printf("연결된 문자열: %s\n", str1);
return 0;
}
================ 실행 결과 ================
연결된 문자열: Hi there
위와 같이 남은 공간을 sizeof(destination) - strlen(destination) - 1로 계산하여 안전하게 문자열을 추가하는 것이 중요하다
문자열 검색
문자열에서 특정 문자열을 찾으려면 strstr()을 사용한다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str[] = "Hello, World!";
char *substr = strstr(str, "World");
/* 검색 결과 출력 */
if (substr) {
printf("찾은 문자열: %s\n", substr);
} else {
printf("문자열을 찾을 수 없음\n");
}
return 0;
}
================ 실행 결과 ================
찾은 문자열: World!
찾은 문자열의 시작 주소를 반환하고, 찾을 수 없다면 널 포인터를 반환한다
널 포인터를 반환하기 때문에, 널이 반드시 발생 안하는지 확인하고 발생한다면 어떻게 처리해야할 지 생각해두어야 한다. Segmentation Fault가 발생할 수 있기 때문이다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str[] = "Hello, World!";
char *substr = strstr(str, "Moon"); /* 존재하지 않는 문자열 */
/* 검색 결과 출력 */
printf("찾은 문자열: %s\n", substr); /* substr이 NULL이면 오류 발생 가능 */
return 0;
}
================ 실행 결과 ================
찾은 문자열: (null)
문자열 토큰화
strtok()는 문자열의 특정 구분자를 나누는 함수다
#include <stdio.h>
#include <string.h>
int main(void)
{
/* 문자열 선언 */
char str[] = "apple,banana,grape";
char *token = strtok(str, ",");
/* 문자열 토큰화 */
while (token != NULL) {
printf("토큰: %s\n", token);
token = strtok(NULL, ",");
}
return 0;
}
================ 실행 결과 ================
토큰: apple
토큰: banana
토큰: grape
첫 번째 호출시 문자열을 전달하고, 이후 호출부터는 NULL을 전달해야 한다. 또, 원본 문자열이 변경되므로 주의해야 한다
또한 내부적으로 정적 변수를 사용하여 문자열 상태를 유지한다. 그렇기 때문에 멀티스레드 환경에서는 데이터 충돌이 발생할 수 있다
Reference
'프로그래밍 > C, C++' 카테고리의 다른 글
C 서식 문자열 (0) | 2025.04.09 |
---|---|
C 언어 strtok() 함수의 작동 방식과 주의할 점 (0) | 2025.04.09 |
C 언어 문자열 (0) | 2025.04.09 |
C 언어의 배열과 포인터, 그리고 함수 포인터 (0) | 2025.04.09 |
C 빌드 과정 (0) | 2025.04.09 |