[C/C++] memcpy 완벽 가이드: 사용법부터 예제, 핵심 주의사항까지 총정리!

안녕하세요! 오늘은 C언어와 C++에서 특정 메모리 블록의 내용을 다른 위치로 복사할 때 매우 유용하게 사용되는 함수, 바로 memcpy에 대해 자세히 알아보겠습니다. 데이터를 원하는 곳으로 정확하고 빠르게 옮기는 방법을 함께 살펴보시죠!

1. memcpy 함수란 무엇인가?

memcpy는 이름에서 알 수 있듯이 "memory copy"의 줄임말입니다. 즉, 메모리의 내용을 특정 바이트 수만큼 그대로 복사하는 기능을 수행합니다. 이 함수는 다양한 데이터 타입(정수, 문자, 구조체 등)의 내용을 가리지 않고, 지정된 크기만큼 바이트 단위로 복사합니다.

헤더 파일:

  • C언어: #include <string.h>
  • C++: #include <cstring>

2. memcpy 함수의 원형 (기본 구조)

memcpy 함수는 다음과 같은 형태로 정의되어 있습니다.

void* memcpy(void* destination, const void* source, size_t num);

각 매개변수의 의미는 다음과 같습니다.

  • void* destination (목적지): 복사된 내용이 저장될 메모리 위치를 가리키는 포인터입니다.
  • const void* source (원본): 복사할 데이터가 있는 메모리 위치를 가리키는 포인터입니다. const 키워드는 원본 데이터가 이 함수 내에서 변경되지 않음을 보장합니다.
  • size_t num (크기): 복사할 데이터의 바이트(byte) 수입니다.

함수는 작업 완료 후 destination 포인터를 반환하지만, 대부분의 경우 반환값은 사용하지 않고 destination을 통해 직접 접근합니다.

간단히 말해, memcpy(복사 받을 곳, 복사할 것, 복사할 크기) 형태로 기억하면 편리합니다.

3. memcpy 사용 시 주의사항

memcpy는 강력한 만큼 주의해서 사용해야 하는 점들이 있습니다.

주의 1: C언어 스타일 문자열과 \0 (널 종료 문자)

C언어에서 문자열은 마지막에 \0(널 종료 문자)를 포함하여 끝을 알립니다.
만약 문자열 전체를 memcpy로 복사하고자 한다면, 이 \0 문자까지 포함하여 길이를 계산해야 합니다.
일반적으로 strlen(문자열) + 1 만큼의 길이를 복사합니다. 그렇지 않으면 복사된 문자열이 제대로 종료되지 않아 예기치 않은 동작을 유발할 수 있습니다.


주의 2: 메모리 영역 겹침 (Overlapping Memory Regions)

memcpysourcedestination 메모리 영역이 겹치는 경우의 동작을 보장하지 않습니다.
즉, 복사할 원본과 복사될 대상의 메모리 주소가 서로 겹쳐있다면, 결과가 예상과 다를 수 있습니다 (Undefined Behavior).

만약 메모리 영역이 겹칠 가능성이 있다면, memmove 함수를 사용하는 것이 안전합니다.
memmove는 겹치는 상황에서도 안전하게 데이터를 복사하도록 설계되었습니다.
(최근의 일부 컴파일러에서는 memcpymemmove처럼 동작하도록 최적화하기도 하지만, 표준적으로는 겹치지 않는 경우에 memcpy를, 겹치는 경우에 memmove를 사용하는 것이 권장됩니다.)

4. memcpy 사용 예제

이제 실제 코드를 통해 memcpy를 어떻게 사용하는지 살펴보겠습니다.

예제 1: 정수 배열 복사

정수형 배열의 내용을 다른 배열로 복사하는 예제입니다.

#include <stdio.h>
#include <string.h> // memcpy를 위해

int main(void) {
    int original_arr[] = {10, 20, 30, 40, 50};
    int copied_arr[5]; // 복사본을 저장할 배열 (크기가 같거나 커야 함)
    int num_elements = sizeof(original_arr) / sizeof(int);

    // original_arr의 내용을 copied_arr로 복사
    // sizeof(original_arr)는 배열 전체의 바이트 크기를 반환합니다.
    memcpy(copied_arr, original_arr, sizeof(original_arr));

    printf("원본 배열: ");
    for (int i = 0; i < num_elements; i++) {
        printf("%d ", original_arr[i]);
    }
    printf("\n");

    printf("복사된 배열: ");
    for (int i = 0; i < num_elements; i++) {
        printf("%d ", copied_arr[i]);
    }
    printf("\n");

    return 0;
}

예상 출력:

원본 배열: 10 20 30 40 50
복사된 배열: 10 20 30 40 50

sizeof(original_arr)original_arr 배열 전체의 바이트 크기를 계산해 주므로, 이를 num 인자로 전달하여 배열 전체를 복사할 수 있습니다.


예제 2: 문자열 일부 복사

문자열의 특정 부분만 다른 문자 배열로 복사하는 예제입니다.

#include <stdio.h>
#include <string.h>

int main(void) {
    const char* source_str = "Hello World!";
    char destination_str[20] = "This is initial."; // 충분한 크기의 목적지 배열

    // source_str에서 앞의 5글자("Hello")만 destination_str로 복사
    memcpy(destination_str, source_str, 5 * sizeof(char)); 
    // sizeof(char)는 항상 1이지만, 명시적으로 표현

    // 중요: 부분 복사 시 널 종료 문자가 자동으로 추가되지 않음
    // 필요하다면 수동으로 추가해야 함
    destination_str[5] = '\0'; 

    printf("원본 문자열: %s\n", source_str);
    printf("복사된 문자열 (일부): %s\n", destination_str);

    return 0;
}

예상 출력:

원본 문자열: Hello World!
복사된 문자열 (일부): Hello

이 경우 "Hello" 5글자만 복사했기 때문에, printf가 문자열의 끝을 인식하도록 수동으로 destination_str[5] = '\0';를 추가했습니다.


예제 3: 문자열 전체 복사 (널 종료 문자 포함)

문자열 전체를 복사하며, 널 종료 문자의 중요성을 보여주는 예제입니다.

#include <stdio.h>
#include <string.h>

int main(void) {
    char source_msg[] = "BlockDMask"; // \0 포함하여 11바이트
    char dest_msg1[20] = "AAAAAAAAAAAAAAAAAAA"; // 충분한 크기
    char dest_msg2[20] = "BBBBBBBBBBBBBBBBB"; // 충분한 크기

    // 경우 1: 널 종료 문자 미포함 (문자열 길이만큼만 복사)
    // strlen은 널 문자를 제외한 길이를 반환
    memcpy(dest_msg1, source_msg, strlen(source_msg) * sizeof(char)); 
    // dest_msg1은 "BlockDMaskAAAAAAAAA" 상태가 됨. 
    // printf는 처음 만나는 \0까지 출력하므로, 뒤의 A가 붙어서 나올 수 있음

    // 경우 2: 널 종료 문자 포함 (문자열 길이 + 1 만큼 복사)
    memcpy(dest_msg2, source_msg, (strlen(source_msg) + 1) * sizeof(char));
    // dest_msg2는 "BlockDMask\0BBBBBBBB" 상태가 됨.
    // printf는 BlockDMask 뒤의 \0을 만나 정상 종료.

    printf("원본: %s (길이: %zu)\n", source_msg, strlen(source_msg));
    printf("복사본1 (널 문자 미포함 시도): %s\n", dest_msg1);
    printf("복사본2 (널 문자 포함): %s\n", dest_msg2);

    return 0;
}

예상 출력:

원본: BlockDMask (길이: 10)
복사본1 (널 문자 미포함 시도): BlockDMaskAAAAAAAAA  // (환경에 따라 다를 수 있음)
복사본2 (널 문자 포함): BlockDMask

dest_msg1의 경우, strlen(source_msg) (즉, 10) 만큼만 복사하여 \0이 복사되지 않았습니다. 따라서 printf로 출력 시, dest_msg1에 원래 있던 'A' 문자들 중 일부가 뒤이어 출력될 수 있습니다. (메모리 상태에 따라 다르게 보일 수 있습니다.) 반면, dest_msg2strlen(source_msg) + 1 만큼 복사하여 \0까지 정확히 복사했기 때문에, 의도한 대로 "BlockDMask"만 깔끔하게 출력됩니다.

5. 마치며

memcpy 함수는 C/C++ 프로그래밍에서 메모리상의 데이터를 효율적으로 복사할 수 있는 강력한 도구입니다. 하지만 복사할 크기를 정확히 지정하고, C 스타일 문자열의 경우 널 종료 문자를 신경 쓰며, 메모리 영역 겹침 문제에는 memmove를 고려하는 등 주의사항을 잘 지켜야 합니다.