본문 바로가기

별걸다하는 IT/프로그래밍언어

[C언어]문자열 복사 함수 strcpy, strcnpy와 주의사항, 널문자 (null) - string.h 라이브러리 파헤치기

반응형

[C언어, C++언어 완전정복 목차 링크!]

안녕하세요~!

저번 시간 strlen, strtok 함수에 이어서 string.h 라이브러리에 속한 또 다른 함수 중 하나를 살펴보려고 합니다.

오늘은 매우매우 보편적이고 기본적이고 자주사용하는만큼 유의해야할, 문자열 복사함수 strcpy 요놈이 주인공이 되겠습니다~!

문자열 대상

C언어 기준, '\0'으로 끝나는 C형식 문자열 방식에서 사용되는 ,

string.h 라이브러리에 정의되어 있는 문자열 함수입니다. 따라서 #include <string> 라이브러리에 정의되어 있는 string 타입을 사용하시면 안됩니다~

 

문자열 길이 구하는 함수

strcpy, strncpy

strcpy: 문자열을 복사한다.

strncpy: 문자열을 일정 길이만큼만 복사한다.

 

헤더파일

#include <string.h>

 

함수 프로토타입

char* strcpy (char* destination, const char* source);
char* strncpy (char* destination, const char* source, size_t num);

인자:

char * destination :

C style의 문자열로, 여기다가 복사할 것이다. 그니까 문자열을 복사해서 저장할 목적지, 문자열의 포인터이다.

const char* source:

source에 저장되어 있는 문자열을 destination에다가 붙여넣기 할거다. 즉 내가 복사할 목적지에 넣고 싶은 문자열을 가지고 있는 포인터이다.

source를 destination에 복사하는 거!

size_t num (strncpy의 경우):

source에서 복사할 문자열의 길이이다.

리턴 값:

destination을 리턴한다.

역할:

source에 있는 문자열을 destination에 복사하는 기능을 한다. 전체 복사하지 않고 일부분의 길이만 복사하고 싶을 때는 길이정보를 추가적으로 더 받는 strncpy를 사용한다. 

str은 문자열을 의미하는 string의 약자, cpy는 참고로 copy 카피의 약자!

 

strcpy, strncpy 사용 예시

기본적인 사용법:

char destination[20];
strcpy(destination, "copy string"); //destination 문자열에 "copy string"을 복사한다.

<상황1>

문자열을 복사해서 저장할 대상이 되는 목적지 문자열은 비어있습니다.

목적지의 공간은 넉넉합니다 (공간 크기: source(10) < dest(13) )

#include <stdio.h>
#include <string.h>
int main()
{
	char dest1[13] = "", dest2[13] = "";
	char source[10] = "computer";
	printf("strcpy return: %s\n", strcpy(dest1, source));
	printf("dest1: %s\n", dest1);
	printf("source: %s\n\n", source);
	printf("strncpy return: %s\n", strncpy(dest2, source, 3));
	printf("dest2: %s\n", dest2);
	printf("source: %s\n", source);
	return 0;
}

 

결과창
strcpy가 수행되는 과정  
strncpy가 진행되는 경우

<상황2>

문자열을 복사해서 저장할 대상이 되는 목적지 문자열은 비어있지 않습니다("helloworld").

목적지의 공간은 넉넉합니다 (공간 크기: source(10) < dest(13) )

#include <stdio.h>
#include <string.h>
int main()
{
	char dest1[13] = "helloworld";
	char source[10] = "hey";
	printf("length of word, helloworld: %d\n", strlen(dest1)); //문자열길이
	printf("length of word, hey: %d\n", strlen(source));
	printf("strcpy return: %s\n", strcpy(dest1, source));
	printf("dest1: %s\n", dest1);
	printf("source: %s\n", source);
	printf("dest1[7]: %c", dest1[7]);
	return 0;
}

기존 목적지에 있는 문자열이 만약 복사하고자 하는 문자열의 크기보다 길면, 복사하고 남은 공간에는 글자들이 그대로 남아있게 됩니다. 7번째 인덱스 문자를 출력했을 때 r이 출력되는 이유입니다. 

 

<상황3>

만약 dest1의 크기가 source보다 작으면 오버플로우 에러가 납니다.

#include <stdio.h>
#include <string.h>
int main()
{
	char dest1[5] = "hey";
	char source[10] = "hello";
	printf("length of hey, helloworld: %d\n", strlen(dest1)); //문자열길이
	printf("length of hello, hey: %d\n", strlen(source));
	printf("strcpy return: %s\n", strcpy(dest1, source));
	printf("dest1: %s\n", dest1);
	printf("source: %s\n", source);
	return 0;
}

 

stncpy 잘못된 사용예:

<상황4> strncpy는 널문자를 붙여주지 않을 수 있다.

문자열을 복사해서 저장할 대상이 되는 목적지 문자열은 비어있지 않습니다. ("hello")

목적지의 공간은 복사하려는 문자열을 가지고 있는 배열의 크기보다 작지만, 문자열을 복사하기에 공간이 부족하지는 않습니다. (len(3) < dst(6) < source(10))

복사할 문자열의 길이(3)는 dst(6)과 source(10)보다 작습니다.

#include <stdio.h>
#include <string.h>
int main()
{
	char dst[6] = "hello";
	char source[10] = "computer";

	printf("strncpy return: %s\n", strncpy(dst, source, 3));
	printf("dst: %s\n", dst);
	printf("source: %s\n", source);
	return 0;
}

만약 strcpy처럼 strncpy가 마지막에 널문자를 붙여준다면 반환된 문자열을 출력했을 때 결과가 "com"이 나와야겠죠! 근데 "comlo"로 나왔다는 것은 문자열 끝을 알리는 널문자가 3문자를 복사하고 난뒤 붙여지지 않았다는 것이죠 ㅎㅎ <상황2>와 유사한 상황인데 <상황2>에서는 널문자까지 복사해줬다면, 여기서는 중간에 짤라진 길이만큼 복사하는거니까 널이 포함되지 않습니다.

<상황4> 해결

#include <stdio.h>
#include <string.h>
int main()
{
	char dst[6] = "hello";
	char source[10] = "computer";

	printf("strncpy return: %s\n", strncpy(dst, source, 3));
	dst[3] = 0; //널문자 붙이기 
	printf("널문자 붙고 난 후 dst: %s\n", dst);
	printf("source: %s\n", source);
	return 0;
}

strncpy를 해주고 난 뒤에 널문자를 붙이는 습관을 들이도록 합시다! 널문자 붙이고 난 후에는 "com"으로 제대로 3개만 출력한 것을 볼 수 있어요 

 

<상황5> 쓰레기값

목적지의 공간은 부족합니다 (공간 크기: dst(5)<len(7)< source(10) )

복사할 문자열의 길이(7)는 dst(5)보다 많고, source(10)보다 작습니다.

#include <stdio.h>
#include <string.h>
int main()
{
	char dst[5] = "hi";
	char source[10] = "computer";

	printf("strncpy return: %s\n", strncpy(dst, source, 7));
	printf("dst: %s\n", dst);
	printf("source: %s\n", source);
	return 0;
}

오버플로우가 발생해버려요 ㅎㅎ 공간은 5개인데 7글자를 어거지로 넣으려고 하니까요~

무엇이 들어있을지 모를 쓰레기값은 물음표(?)로 표기해뒀습니다. ㅎㅎ 출력할 때 문자열 끝을 알리는 널문자가 나올때까지 읽으려고 하다보니까, 이상하게 값이 출력되는 겁니다. ㅎㅎ

 

주의할 점 or 같이 알아두면 좋을 점

▶C언어 문자열에서 사용해야 한다. string 클래스 형태의 문자열에서는 사용 불가!

▶strcpy은 문자열을 복사할 때, 끝에 널문자도 같이 포함해서 복사해줍니다.

▶strncpy는 널문자를 마지막에 포함시키는 것을 보장하지 않습니다.

▶즉 strncpy는 strcpy보다 안전하게 정의된 것은 아니므로 사용할 때 주의해야 합니다.

▶strncpy의 경우, 문자열을 복사하고 나서도 문자열 공간에 자리가 남았다면, 널문자로 나머지 공간을 채웁니다. 즉 10개의 문자가 들어갈 수 있는 공간에, hello 5글자를 넣었다면 나머지 5공간은 널문자로 채워집니다.

▶소스코드에서 변수의 선언이나 정의가 (구조체일 경우도 포함) 다른 파일에 정의되어 있을 경우, 문자열이 차지하고 있는 메모리 크기를 코드에서 바로 식별할 수 없을 경우가 많습니다. 크기에 대한 데이터가 중요하기 때문에 sizeof 함수랑 짝자꿍으로 많이 사용됩니다.

▶마찬가지로 source 길이에 대한 데이터가 중요하기 때문에 길이 구하는 함수 strlen이랑 많이 사용됩니다.

▶따라서 복사함수를 사용할 때는 복사를 수행하고자 하는 문자열의 크기와 집어넣으려는 공간의 크기를 확인해주는 습관이 중요합니다. 컴파일 에러가 아닐 수 있어서 알아차리기 어려워, 시스템에서 장애를 야기할 수 있으니 주의해야 합니다.

▶오버플로우를 방지하기 위해서 문자열을 복사해 저장하려는 목적지 배열의 크기는 반드시 복사하려는 문자열의 크기보다 같거나 커야합니다. 또한 문자열을 저장하고 있는 문자열배열과(from) 문자열을 복사하려는 목적지 배열(to) 영역이 겹치면 안됩니다.

▶오버플로우 문제 없이 보다 안전하게 이용하고 싶다면 C11부터 지원하는 strcpy_s 함수를 사용하면 됩니다.

▶strcpy_s 함수는 만일 source의 크기가 destination보다 크다면 복사를 수행하지 않고, 0이 아닌 값을 리턴합니다. 호출자가 리턴값을 무시했을 때를 대비해 버퍼를 비워버리는 기능을 수행합니다.

 

함수 원형 코드

궁금하신 분들이 있을 것 같아서 함수 원형 코드를 추가했어요. 참고정도만 하기~

BSD계열의 경우 strcpy:

#include <string.h>

#if defined(APIWARN)
__warn_references(stpcpy, "stpcpy() is dangerous; do not use it");
#endif

char *
stpcpy(char *to, const char *from)
{
	for (; (*to = *from) != '\0'; ++from, ++to);
	return(to);
}

여기서는 문자열을 저장하고 있는 source를 '~로부터'를 의미하는 from으로, 목적지 destination을 '~로'를 의미하는 to로 정의했네요 ㅎㅎ 

코드는 간단합니다. 일단 from 시작 문자부터 to로 복사하고, 옮겨진 문자가 널인지 아닌지 확인하는 겁니다.

복사한 후 비교작업이 들어가기 때문에, 널이 복사하는 대상에 같이 포함되어요~!

만약 함수 정의가 비교 후 복사였으면, 복사 대상에는 널문자가 포함되지 않았겠죠? ㅎㅎ

 

BSD계열의 경우 strncpy:

#include <string.h>
/*
 * Copy src to dst, truncating or null-padding to always copy n bytes.
 * Return dst.
 */
char *
strncpy(char *dst, const char *src, size_t n)
{
	if (n != 0) {
		char *d = dst;
		const char *s = src;

		do {
			if ((*d++ = *s++) == 0) {
				/* NUL pad the remaining n-1 bytes */
				while (--n != 0)
					*d++ = 0;
				break;
			}
		} while (--n != 0);
	}
	return (dst);
}
DEF_STRONG(strncpy);

코드를 보면 strncpy의 경우 만약 복사하고자 하는 길이를 0으로 집어넣으면, 그냥 목적지 문자열을 반환해줘요 ㅎㅎ 지정한 문자 개수만큼만 복사하는 거니까, 한글자 한글자 복사하는 작업을 문자 개수만큼만 반복하네요 ㅎㅎ

여기도 마찬가지로 복사를 한 후에 널문자인지 비교 작업이 들어가는데, 내가 복사하려는 문자 개수 전에 널문자가 발견되면 남은 길이가 끝날때까지 나머지 공간을 널로 채웁니다.

그런데 내가 3개를 복사하려고 하는데 3개동안 널문자가 없으면 그냥 3개만 복사하고 끝내요 ㅎ 즉 널문자를 마지막에 얘네가 임의적으로 넣어주진 않는다는 것을 알 수 있습니다.

 

string.h 또다른 함수 보기

문자열 길이 함수 strlen

 

[C언어][string.h]문자열 관련 함수- strlen(길이)함수의 모든 것, 함수 코드, 사용법 및 예제, 주의사항 등

[C, C++완전정복 목차] 안녕하세요~!! 오랜만이예요 ㅎㅎ 그동안 할일이 초큼 쌓여있는걸 처리하느라..ㅎㅎㅎ 오늘은 무진장~~ 많이 쓰이는, 안사용할 수가 없는 문자열 관련 함수들을 정리해보려고 해요 ㅎㅎ st..

jhnyang.tistory.com

문자열 파싱, 자르기 함수 strtok

 

오늘은 문자열 복사 함수 strcpy와 strncpy 함수를 알아보았어요~! ㅎㅎ

공감, 댓글, 광고보답은 정보공유에 힘이됩니다~! 다음에 또 정보가 필요할때 방문해주세요 :)

 

반응형