이번에는 memcpy() 라는 녀석에 대해서 알아보겠습니다.
memcpy()는 특정 주소의 메모리 일정부분을 다른 주소로 복사하는 기능을 가진 함수입니다. C를 배우는 기초단계에서는 쓸일이 많지는 않은 것이 사실입니다.
숫자는 그냥 대입연산자 '=' 를 쓰면 되고, 문자열은 strcpy()를 쓰면 되니까요. 그런데 그러한 부분에 익숙해져있다가 막상 memcpy()를 사용하기 시작할 무렵에 코딩 오류를 범하는 경우를 종종 보곤 합니다.

아래는 MSDN에 명시되어 있는 함수 원형입니다.

void *memcpy( void *dest, const void *src, size_t count );

그럼, memcpy()의 기능, 특징 등을 한번 살펴 보도록 하죠.

byte 단위의 메모리 복사

memcpy()의 역할을 최대한 간결하게 설명하는 말이라 생각하고 적어보았습니다. memcpy()는 어떤 데이터든 상관없습니다. 왜냐면, 메모리의 값을 복사하는 것이니까요. 'A'가 메모리에 저장된 것과 65 라는 값이 메모리에 저장된 것을 구분할 수 있을까요?
정답은 '구분할 수 없습니다' 입니다. 왜냐면 메모리에 저장되는 값은 둘다 동일하게 65 이기 때문입니다. 그것을 프로그램이 해석할때 char로 해석하면 'A'이고, 숫자로 인식하면 65가 될 뿐이죠.
그래서 memcpy()는 메모리내용을 복사하기 때문에 복사될 내용의 데이터형은 상관없습니다. 또한 두 가지 파라메터의 데이터형이 달라도 상관없습니다. 중요한 것은 주소가 몇 번지인지, 그리고 몇 바이트를 복사할지에 대한 것 뿐입니다.

int value[3] = { 0x0, 0x0, 0x0 };
char key = 'X'; // 0x58
memcpy( &value[1], &key, sizeof(char));
printf( "0x%08X, 0x%08X, 0x%08X\n", value[0], value[1], value[2] );
int i;
char *p = (char*)value;
for( i=0; i< sizeof(int)*3; i++ )
    printf( "%02X ", (int)(*p++) );

실행결과>
0xFFFFFFFF, 0xFFFFFF58, 0xFFFFFFFF
FF FF FF FF 58 FF FF FF FF FF FF FF

 

NULL까지도 복사하는 능력

문자열을 복사하는데에도 strpcy() 대신 memcpy()를 써야 하는 경우가 있습니다. strcpy()는 NULL 문자를 만날때까지의 값들을 복사하지만, memcpy()는 값은 상관없이 입력한 길이만큼 복사하게 되죠. 바로 그 차이 때문입니다.

char string[100][100] = 
{ "This is One",
  "This is Two",
  "This is Three",
  ....
  "This is Hundred" }

이러한 데이터가 있다고 가정했을 때, 다른 곳으로 복사하려면 어떻게 해야할까요? 여러분이라면 어떻게 하시겠습니까? strcpy() 1번만에 복사가 가능할까요? 각 문자열은 NULL로 끝나기 때문에 아무리 노력해도 한번에 한개의 문자열밖에 복사할 수 없습니다.
그러면, strcpy()를 100번 실행하면 된다구요? 네, 가능합니다. 하지만 한번에 해결할 수 있다면 그게 더 좋지 않을까요? memcpy()는 데이터형에 상관없이 복사가 가능하기 때문에 단번에 복사할 수 있습니다.

char dest[100][100];
memcpy( &dest[0], &string[0], sizeof(char)*100*100 );
혹은
memcpy( (char*)dest, (char*)string, sizeof(char)*100*100 );

한번을 실행하더라도 strcpy()가 memcpy()보다 느립니다. 그런데 그걸 100배만큼 실행한다는 건 바람직하지 않겠지요. memcpy() 한줄로 끝나는 작업을 말이죠.

문자열인 경우에는 strcpy() 라는 함수가 있음에도 활용이 가능하다는 장점이 있습니다. 그러면 그외의 데이터는 두말할 것도 없이 써야겠지요. 메모리에 로드한 데이터들의 특정 부분을 가져온다던지 할때에 주력으로 쓰는 함수입니다.

NULL 상관없이 복사하는 능력

위의 'NULL까지도 복사하는 능력'과 조금 유사합니다만 다른 이야기를 하고자 합니다. 위에서는 NULL을 포함하는 문자열(혹은 문자열 집합)을 복사할때에는 strcpy()이 아닌 memcpy()를 써야한다고 했습니다.
그런데, NULL을 포함하지 않는 경우도 strcpy()보다 memcpy()가 우월하다는 점도 아셔야 합니다. 즉, NULL이 없는 경우라도 strcpy()보다 정확한 복사가 가능하다는 것입니다.

char str1[2][10] = {
    "This is a",
    "message"
};
char str2[10] = "";
char ch;
int index;
scanf("%d", &index );
str1[1][index] = '*';
strcpy( str2, str1[0] );
printf( "%s\n", str2 );

실행결과>
-1
This is a*message

scanf()로 인덱스를 받아서, str1[1]의 'message' 중 해당하는 위치의 글자를 '*'로 바꾸기 위한 코드가 들어있습니다. 그런데 실수로 -1을 입력한 경우입니다. 그러면, str1[1][-1] 위치에 '*'를 넣게 되겠지요. 위의 경우에는 str[0]의 마지막 위치에 해당합니다. 즉, "This is a"의 끝을 알리는 NULL 위치입니다.
그런데 오동작으로 인해서 NULL이 사라지고, '*'이 들어가게 된 경우이죠.

before>
'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', '\0', 'm', 'e', 's', 's', 'a', 'g', 'e', '\0'

after>
'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', '*', 'm', 'e', 's', 's', 'a', 'g', 'e', '\0'

그래서, strcpy()를 하게 되면, "This is a"만 복사되는게 아니라 NULL을 만날때까지인 "This is a*message"가 str2로 복사되는 것입니다. memcpy()는 복사할 최대 길이를 지정할 수 있으므로 그런 위험이 없습니다. NULL이 없더라도 예상한 길이만큼만 복사되게 할 수 있죠. 다만, 위와 같은 경우를 대비해서 NULL을 입력해주는 코드를 추가하긴 해야 합니다.

memcpy( str2, str1[0], sizeof(char)*10 );
str2[9] = '\0';


여기서 두가지 질문이 예상됩니다.

첫째는, strncpy() 함수를 이용하면 원하는 길이만큼 복사가 가능하다는 것입니다.
예 맞습니다. strncpy()는 strcpy() 함수에 복사할 문자열 길이를 추가하여, 원하는 길이만큼까지만 복사가능합니다. 그리고 끝에 NULL을 붙여주기까지 하죠. strcpy() 대신 strncpy()를 쓰신다면, 오류방지를 위한 상당한 발전이라고 보여집니다.
하지만, strncpy()는 컴파일러에 따라서 지원되지 않는 경우도 있다는 사실 정도는 기억해두세요. 그리고 문자열 중간에 NULL이 포함된 경우는 strcpy()와 마찬가지로 NULL 이전까지만 복사된다는 단점은 그대로 유효합니다. 그리고 memcpy()보다 속도가 느립니다.
자주 쓰는게 아니라면, strncpy()도 좋은 선택입니다.

둘째는, strcpy()도 NULL을 강제로 넣도록 해주면 정상적인 문자열을 출력된다는 것입니다.
하지만 이것은 이미 다른 변수의 영역을 덮어쓴 이후에 조치하는 것이므로, 그것 자체가 이미 오류입니다.

memcpy()를 택할 수 밖에 없는 경우

문자열이 아닌, 일반 데이터를 복사할때는 당연히 memcpy() 입니다. 이것은 진리죠.
그런데, 문자열을 복사하는데에도 strpcy() 대신 memcpy()를 써야 하는 경우가 있습니다. 위에서도 언급을 했었는데 strcpy()는 NULL 문자를 만날때까지의 값들을 복사하지만, memcpy()는 값은 상관없이 입력한 길이만큼 복사하게 되죠. 바로 그 차이 때문입니다.

char string[100][100] = 
{ "This is One",
  "This is Two",
  "This is Three",
  ....
  "This is Hundred" }


이러한 데이터가 있다고 가정했을 때, 다른 곳으로 복사하려면 어떻게 해야할까요? 여러분이라면 어떻게 하시겠습니까? strcpy() 1번만에 복사가 가능할까요? 각 문자열은 NULL로 끝나기 때문에 아무리 노력해도 한번에 한개의 문자열밖에 복사할 수 없습니다.
그러면, strcpy()를 100번 실행하면 된다구요? 네, 가능합니다. 하지만 한번에 해결할 수 있다면 그게 더 좋지 않을까요? memcpy()는 데이터형에 상관없이 복사가 가능하기 때문에 단번에 복사할 수 있습니다.

char dest[100][100];
memcpy( &dest[0], &string[0], sizeof(char)*100*100 );
혹은
memcpy( (char*)dest, (char*)string, sizeof(char)*100*100 );


한번을 실행하더라도 strcpy()가 memcpy()보다 느립니다. 그런데 그걸 100배만큼 실행한다는 건 바람직하지 않겠지요. memcpy() 한줄로 끝나는 작업을 말이죠.

어라? 컴파일 에러?

strcpy()는 문자열을 복사하기 위한 함수라고 외우신 분들께서 memcpy()는 메모리값을 복사하기 때문에 값을 복사하는데 사용하실때 자주 실수하시는 것 같습니다.

'memcpy' : cannot convert parameter 1 from 'char' to 'void *'

보통 이러한 에러가 날텐데요. 파라메터들을 포인터로 넘기지 않아서 발생하는 것입니다.

char str[] = "Hello, World";
char dest_str[100] = "";

strcpy( dest_str, src_str );

주로 이렇게 사용하셨을 겁니다.
그렇다보니 memcpy()를 쓸때 아래와 같이 쓰는 경우가 더러 있습니다.

int value = 100;
int wallet = 0;

memcpy( wallet, value, sizeof(value) );

물론 아주 초보적인 실수입니다만, 이것이 구조체 형태로 넘어가면서 컴파일 에러를 찾는데 시간을 적잖게 허비하게 되기도 합니다.

typedef struct {
    int value;
    int wallet;
} NODE;
NODE node = { 100, 0 }, blank;

memcpy( blank, node, sizeof(node) );

위의 구조체는 포인터 형태가 아니고 변수 형태이므로, 아래와 같이 '&'를 붙여서 주소값을 넘겨주도록 해야 합니다.

memcpy( &blank, &node, sizeof(node) );

프로그램이 좀 복잡해지면, 구조체를 #define으로 이름을 바꾸어 사용하고 하다보면 헷갈릴때가 종종 있어서, 의외로 헷갈릴 때가 있답니다.
 

어라? 이것밖에 복사가 안되네?

특히 2 bytes 문자를 사용하는 경우에 이런 증상을 겪는 일이 자주 있습니다.

#define LEN     10
typedef MCHAR unsigned int
MCHAR string[LEN], dest[LEN];
......
memcpy( dest, string, LEN );

전체 데이터의 크기는 20 bytes 입니다. 그러나, 이렇게 복사하게 되면 문자열의 절반 정도만 복사가 되겠지요? 이런 경우처럼 글자단위당 크기와 글자갯수를 혼동하는 경우에는 예상한 양보다 많거나 혹은 적게 복사되는 경우가 발생합니다.

memcpy( dest, string, sizeof(MCHAR) * LEN );

권장하는 방법은 위와 같이, 데이터단위의 크기를 sizeof()로 구한 다음에 길이만큼 곱해주는 방식입니다. 이렇게 코딩하시는 것이 차후에도 알아보기 편하고 정확하답니다.
 

마치면서...

이쯤하면, memcpy()에 대해서 충분히 알아본 것 같은데요, 필요에 따라서, 차후에 업데이트 하도록 하겠습니다.

'develop' 카테고리의 다른 글

int의 크기  (2) 2010.08.24
enum과 typedef enum  (12) 2010.08.24
memcpy()  (0) 2010.08.23
삼성 오픈소스 사이트 소개  (0) 2010.08.19
C의 비트연산 겉핥기  (4) 2010.08.18
ASCII Character Code Table  (0) 2010.08.12
Posted by 찬이

댓글을 달아 주세요