[C/C++] strcpy vs strncpy: 문자열 복사 완벽 마스터하기
안녕하세요! 오늘은 C언어에서 문자열을 다룰 때 빼놓을 수 없는 두 가지 함수, strcpy와 strncpy에 대해 깊이 있게 알아보려고 합니다. 이 함수들은 문자열을 복사하는 기본적인 기능을 제공하지만, 정확히 알고 사용하지 않으면 예기치 않은 문제를 일으킬 수도 있답니다.
1. 기본 다지기: strcpy와 strncpy 알아보기
C언어에서 문자열은 기본적으로 char형 배열이나 포인터로 다루며, 문자열의 끝은 널(NULL) 문자 \0로 표시됩니다. strcpy와 strncpy는 이러한 C 스타일 문자열을 복사하는 데 사용되는 표준 라이브러리 함수입니다.
1.1. strcpy: 전체를 복사하는 해결사
- 헤더 파일:
<string.h>(C++에서는<cstring>) - 함수 원형:
char* strcpy(char* destination, const char* source); - 기능:
source가 가리키는 문자열 (널 문자\0포함)을destination이 가리키는 메모리 공간으로 복사합니다. - 반환 값:
destination포인터를 반환합니다.
strcpy는 이름에서 알 수 있듯(STRing CoPY), source 문자열 전체를 destination으로 옮겨 담습니다. 이때, 문자열의 끝을 알리는 \0까지 함께 복사된다는 점이 중요합니다.
간단 사용 예:
char source_str[] = "Hello C!";
char dest_str[20];
strcpy(dest_str, source_str);
// 이제 dest_str에는 "Hello C!\0"가 복사되어 있습니다.
1.2. strncpy: 원하는 만큼만, 안전하게?
- 헤더 파일:
<string.h>(C++에서는<cstring>) - 함수 원형:
char* strncpy(char* destination, const char* source, size_t num); - 기능:
source가 가리키는 문자열에서 최대num개의 문자를destination으로 복사합니다. - 반환 값:
destination포인터를 반환합니다.
strncpy는 strcpy에 n (number)이 추가된 형태로, 복사할 최대 문자 수를 num 매개변수로 지정할 수 있습니다. 이것만 보면 strcpy보다 안전해 보이지만, 몇 가지 까다로운 특징이 있습니다. (이 부분은 '주의사항'에서 자세히 다룰게요!)
간단 사용 예:
char source_data[] = "BlockDMask";
char dest_data[100];
strncpy(dest_data, source_data, 5); // source_data에서 처음 5개 문자만 복사
// dest_data에는 "Block"이 복사되지만, \0의 존재는 보장되지 않습니다!
// 따라서 수동으로 추가해줘야 할 수 있습니다: dest_data[5] = '\0';
2. 실제 사용법: 예제로 이해하기
백문이 불여일견! 예제를 통해 두 함수가 어떻게 동작하는지 살펴보겠습니다.
2.1. strcpy 사용 예제
#include <stdio.h>
#include <string.h>
int main() {
char origin[] = "TestString"; // 길이 10 (널 문자 제외)
char dest1[20];
char dest2_small[5]; // 복사될 문자열보다 작은 버퍼
char dest3_overwrite[15] = "Old Value+++++";
// Case 1: 충분한 공간으로 복사
strcpy(dest1, origin);
printf("dest1: %s\n", dest1); // 출력: TestString
// Case 2: 작은 버퍼로 복사 (위험! 주석 처리)
// strcpy(dest2_small, origin); // 버퍼 오버플로우 발생 가능성!
// printf("dest2_small: %s\n", dest2_small);
// Case 3: 기존 내용이 있는 더 큰 버퍼로 복사
strcpy(dest3_overwrite, origin);
printf("dest3_overwrite: %s\n", dest3_overwrite); // 출력: TestString (Old Value+++++는 덮어쓰임)
// "TestString\0+++++" 와 같이 메모리에 저장되지만, printf는 \0까지만 읽음.
return 0;
}
결과 분석:
dest1:origin문자열이\0까지 성공적으로 복사됩니다.dest2_small:origin("TestString", 10글자)을 5칸짜리dest2_small에 복사하려 하면 버퍼 오버플로우가 발생하여 프로그램이 비정상 종료되거나 예상치 못한 동작을 할 수 있습니다. 절대 금물!dest3_overwrite:origin이 복사되면서 기존 "Old Value" 부분을 덮어씁니다.origin의\0문자까지 복사되므로,printf는 "TestString"까지만 출력합니다.
2.2. strncpy 사용 예제
#include <stdio.h>
#include <string.h>
int main() {
char source[] = "ExampleStr"; // 길이 10 (널 문자 제외)
char dest_a[15];
char dest_b[15] = "Original Data";
char dest_c[5]; // 복사할 일부 문자열을 담을 버퍼
// Case 1: 원본 문자열 길이만큼 정확히 복사 (널 문자 포함)
strncpy(dest_a, source, strlen(source) + 1); // strlen(source) + 1은 널 문자까지의 길이
// 또는 strncpy(dest_a, source, sizeof(source)); 도 가능
printf("dest_a: %s\n", dest_a); // 출력: ExampleStr
// Case 2: 원본 문자열보다 적은 수의 문자만 복사 (널 문자 미포함 가능성)
strncpy(dest_b, source, 4); // "Exam"만 복사
// dest_b는 "Examle Data\0" 와 같이 앞부분만 바뀜.
// 안전하게 하려면: dest_b[4] = '\0';
printf("dest_b (처음 4개 복사 후): %s\n", dest_b); // 출력: Examle Data
// Case 3: 널 문자를 수동으로 추가해야 하는 경우
strncpy(dest_c, source, 4); // "Exam" 복사
// dest_c[4] = '\0'; // 이 줄이 없다면? -> 쓰레기 값 출력 가능성
printf("dest_c (널 문자 추가 전): %s\n", dest_c); // 쓰레기 값 포함될 수 있음
dest_c[4] = '\0';
printf("dest_c (널 문자 추가 후): %s\n", dest_c); // 출력: Exam
// Case 4: 복사할 개수(num)가 원본 문자열 길이(널 포함)보다 클 때
char dest_d[15];
strncpy(dest_d, "Hi", 10); // "Hi\0" 복사 후 나머지 공간을 \0으로 채움 (총 10개)
printf("dest_d: %s (문자 H 위치: %p)\n", dest_d, &dest_d[0]);
printf("dest_d[5] (널문자): %d (문자 H로부터 5칸 뒤: %p)\n", dest_d[5], &dest_d[5]); // 0 (널 문자)
return 0;
}
결과 분석:
dest_a:strlen(source) + 1만큼 복사하여\0까지 안전하게 복사합니다.dest_b:source의 처음 4글자("Exam")만dest_b의 앞부분에 덮어씁니다.strncpy는 지정된num만큼만 처리하므로,dest_b의 나머지 "ple Data" 부분과\0은 그대로 남아있습니다.dest_c:source의 처음 4글자("Exam")를 복사합니다. 만약dest_c[4] = '\0';라인이 없다면,dest_c는 널로 끝나지 않아printf가 쓰레기 값을 출력할 수 있습니다.strncpy사용 시 가장 주의해야 할 부분입니다!dest_d:num(10)이 복사할 문자열("Hi",\0포함 3)보다 크면, "Hi\0"을 복사한 후destination의 나머지 공간을num에 도달할 때까지\0으로 채웁니다(padding).
3. 핵심 주의사항: 이것만은 꼭! (strcpy & strncpy 함정 피하기)
이제 두 함수의 가장 중요한 차이점과 사용 시 주의사항을 정리해 보겠습니다.
3.1. strcpy의 최대 함정: "버퍼 오버플로우"
strcpy는 destination 버퍼의 크기를 전혀 고려하지 않습니다. 만약 source 문자열의 길이가 destination 버퍼보다 크면, 버퍼의 경계를 넘어 데이터를 쓰게 되어 버퍼 오버플로우(Buffer Overflow)가 발생합니다. 이는 심각한 보안 취약점으로 이어질 수 있으며, 프로그램 오작동의 주범이 됩니다.
→ 해결책: strcpy를 사용하기 전, destination 버퍼가 source 문자열을 담기에 충분한지 반드시 확인해야 합니다. (예: if (strlen(source) < sizeof(dest_buffer))) 하지만 이런 확인은 번거롭고 실수하기 쉽습니다.
3.2. strncpy의 복잡성: \0 (널 문자)와 길이(num)의 딜레마
strncpy는 num이라는 길이 제한 덕분에 strcpy의 무분별한 버퍼 오버플로우를 막아주는 듯 보입니다. 하지만 다음과 같은 특징 때문에 사용이 까다롭습니다.
널 문자 자동 추가의 부재: 만약 복사할 문자열(
source)의 실제 길이(널 문자 제외)가num보다 크거나 같으면,strncpy는destination에 널 문자(\0)를 자동으로 추가하지 않습니다.char src[] = "longstring"; char dest[5]; strncpy(dest, src, 5); // dest에는 "longs"가 복사됨. \0 없음! // printf("%s", dest); // 위험! dest는 널로 끝나지 않은 문자열 dest[4] = '\0'; // 이렇게 수동으로 널 문자를 추가해야 안전 (버퍼 크기가 5라면 dest[4]가 마지막) // 또는 strncpy(dest, src, sizeof(dest)-1); dest[sizeof(dest)-1] = '\0';이것이
strncpy사용 시 가장 흔히 저지르는 실수이며, 예상치 못한 결과(쓰레기 값 출력, 다른 메모리 침범 등)를 초래합니다.널 문자로 채우기(Padding): 만약
source문자열의 길이(널 문자 포함)가num보다 작으면,strncpy는source를 복사한 후destination의 나머지 공간을num개의 문자가 채워질 때까지\0으로 채웁니다.char src[] = "Hi"; // "Hi\0" (3바이트) char dest[10]; strncpy(dest, src, 7); // dest에는 "Hi\0\0\0\0\0" 가 복사됨 (총 7바이트 처리)이는 때로 유용할 수 있지만, 불필요한 널 문자 채우기로 성능 저하를 일으킬 수도 있습니다.
num값 설정의 중요성:num은destination버퍼의 전체 크기를 넘지 않도록 주의해야 합니다. (num <= sizeof(destination))destination에 안전하게 널 문자를 추가하려면,num을sizeof(destination) - 1로 하고, 복사 후destination[sizeof(destination)-1] = '\0';를 명시적으로 해주는 것이 일반적인 안전한 사용법입니다.
4. 어떤 함수를 선택해야 할까? (strcpy vs. strncpy)
strcpy:- 장점: 사용이 간단명료합니다.
- 단점: 버퍼 오버플로우에 매우 취약합니다.
- 언제 사용?: 복사될 문자열의 최대 길이를 명확히 알고 있고, 목적지 버퍼가 항상 충분히 크다는 것이 100% 보장될 때만 제한적으로 사용합니다. (예: 컴파일 타임에 크기가 결정되는 문자열 리터럴 복사)
strncpy:- 장점: 복사할 최대 길이를 제한하여
strcpy보다는 버퍼 오버플로우에 덜 취약합니다. - 단점: 널 문자를 항상 보장하지 않아 수동 관리가 필요하며, 사용이 다소 복잡합니다. 널 패딩으로 인한 비효율이 있을 수 있습니다.
- 언제 사용?: 외부 입력이나 동적으로 길이가 변하는 문자열을 다룰 때, 버퍼 크기를 지정하여 복사해야 할 경우 사용합니다. 단, 널 문자 처리에 각별히 주의해야 합니다.
- 장점: 복사할 최대 길이를 제한하여
현대적인 대안:
사실, C11 표준부터는 strcpy_s나 strncpy_s와 같이 좀 더 안전한 함수들이 제공됩니다. (MSVC 컴파일러 등에서는 이전부터 지원) 이 함수들은 목적지 버퍼의 크기를 명시적으로 인자로 받아 좀 더 안전한 작업을 수행합니다. 가능하다면 이런 보안 강화 함수들을 사용하는 것이 좋습니다. C++ 환경이라면 std::string 클래스를 사용하는 것이 훨씬 안전하고 편리합니다.
5. 마무리하며
오늘은 C언어의 대표적인 문자열 복사 함수 strcpy와 strncpy에 대해 알아보았습니다. strcpy는 간편하지만 위험하고, strncpy는 조금 더 안전하지만 널 문자 처리라는 숨겨진 복잡성이 있다는 것을 알게 되셨을 겁니다.
가장 중요한 것은 목적지 버퍼의 크기를 항상 인지하고, 문자열의 끝을 알리는 \0을 올바르게 처리하는 것입니다. 이 두 가지만 명심한다면 C언어에서도 문자열을 좀 더 안전하게 다룰 수 있을 거예요.