[2023 방통대 C 스터디] 11. [memory.h] 데이터를 복사할 땐,

Cover Image

앞서 알아본 string.h 라이브러리를 사용하면 문자열을 간편하게 복사할 수 있었습니다. 그럼 문자열 이외의 숫자 배열이나 구조체의 배열 등은 어떻게 복사할까요?

일반적인 자료형의 변수부터 배열, 주조체 등 모든 변수들은 메모리에 저장되기 때문에 메모리 속의 내용을 복사해버리면 간단하게 해결됩니다. memory.h 라이브러리 의 주요 함수를 알아보도록 하겠습니다.

데이터 복사 - memcpy() #

memcpy() 함수는 메모리를 복사하는 함수입니다.

c void *memcpy(void *dest, const void *src, size_t n);

  • dest : 복사 대상 메모리 블록을 가리키는 포인터
  • src : 복사할 메모리 블록을 가리키는 포인터
  • n : 복사할 바이트 수
  • 반환값 : 복사 성공 시 *dest

💡 일반적으로 복사할 바이트의 수의 계산에는 sizeof() 연산자를 사용합니다. 예를 들어 char타입의 4칸짜리 배열(또는 길이가 4인 문자열)의 크기는 아래와 같이 알아낼 수 있습니다.

배열의 복사 #

  • 코드

    c
    #include <memory.h>
    #include <stdio.h>
    
    typedef struct {
      int m_id;
      char *m_nm;
      int m_age;
    } member_t;
    
    int main() {
      member_t members[2], members_cpy[2];
      members[0].m_id = 1;
      members[0].m_nm = "qwer";
      members[0].m_age = 10;
    
      members[1].m_id = 2;
      members[1].m_nm = "asdf";
      members[1].m_age = 20;
    
      memcpy(members_cpy, members, sizeof(member_t) * 2);
    
      for (int i = 0; i < 2; i++) {
        printf("m_id: %d\nm_nm: %s\nm_age:%d\n\n", members_cpy[i].m_id, members_cpy[i].m_nm, members_cpy[i].m_age);
      }
    
      return 0;
    }

위 코드는 member_t 형 구조체 배열인 members를 동일한 구조체 배열인 members_cpy에 복사하는 예제 코드입니다. 배열이건 구조체건 간에 메모리에 연속적으로 저장된 데이터이기 때문에 복사 할 데이터의 시작 지점(포인터)과 데이터의 크기만 알면 복사할 수 있습니다. 여기에서는 members의 크기를 알아내기 위해 구조체의 크기를 sizeof() 연산자를 통해 알아낸 뒤 배열의 길이인 2를 곱해 memcpy()함수의 세 번째 인자값으로 전달하였습니다.

출력은 다음과 같습니다.

m_id: 1 m_nm: qwer m_age:10

m_id: 2 m_nm: asdf m_age:20

non-null-terminated 문자열의 복사 #

이전 [[string.h] 파이썬은 되던데…](https://www.notion.so/string-h-5d75fe6fd7924d29a602043703ad580c?pvs=21) 과정에서 문자열의 복사에는 strcpy()함수를 사용한다고 하였습니다. 이 함수 대신 memcpy()함수를 이용해서도 문자열을 복사할 수 있습니다. 가끔 문자열이 non-null-terminated 형태로 길이와 함께 전달되거나 문자열 안에 NULL기호가 들어있어야 한다면 memcpy()를 사용하면 간편하고 효율적입니다.

  • 코드

    c
    #include <memory.h>
    #include <stdio.h>
    
    int main() {
      char string[4] = {'a', 's', 'd', 'f'}, string_cpy[4];
      memcpy(string_cpy, string, sizeof(char) * 4);
      printf("%c%c%c%c\n", string_cpy[0], string_cpy[1], string_cpy[2], string_cpy[3]);
    
      return 0;
    }

위 코드의 출력은 다음과 같습니다.

asdf

null-terminated 문자열의 복사 #

strcpy()를 사용해도 무방하지만 memcpy()strlen()을 사용하여도 동일하게 동작합니다. 다만, strcpy() 함수는 NULL 문자 탐색으로 인해 반복문으로 메모리를 복사하는 반면, memcpy() 함수는 운영체제의 메모리 복사 API를 사용해 strcpy()에 비해 일부 상황에서 효율적일 수 있습니다.

  • 코드

    c
    #include <memory.h>
    #include <stdio.h>
    #include <string.h>
    
    int main() {
      char string[5] = "asdf", string_cpy[5];
      memcpy(string_cpy, string, sizeof(char) * strlen(string) + 1);
      printf("%s\n", string_cpy);
    
      return 0;
    }

위 코드의 출력은 다음과 같습니다.

asdf

메모리 byte 특정 값 설정 - memset() #

memset()함수는 지정한 공간의 메모리 블럭을 특정 값으로 설정하는 함수입니다. 보통 구조체나 배열을 생성할 때 데이터를 0으로 초기화할 때 주로 사용합니다.

void *memset(void *s, int c, size_t n);

  • void *s : 설정할 메모리 블록을 가리키는 포인터
  • int c : 설정할 값
  • size_t n : 설정할 바이트 수
  • 반환값 : 설정이 완료된 메모리 블럭 포인터
  • 코드

    c
    #include <memory.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct student {
      int id;
      char name[20];
      int age;
    } student_t;
    
    int main(void) {
      student_t s1;
    
      memset(&s1, 0, sizeof(student_t));
    
      printf("id: %d\nname: %s\nage: %d", s1.id, s1.name, s1.age);
    
      return 0;
    }

위 예제에서는 student_t 타입 구조체의 멤버 변수를 0으로 초기화하는 방법을 보여줍니다. memset() 함수를 사용하여 구조체의 포인터와 크기를 전달하여 0으로 초기화합니다. 이렇게 초기화하면 구조체의 모든 멤버 변수가 0 또는 NULL로 설정됩니다.

위 코드의 출력은 다음과 같습니다. 이름의 경우 ASCII에서 NULL 문자에 대응하는 0으로 채워져 아무런 문자가 출력되지 않습니다.

id: 0 name: age: 0

💡 여기서 착각하기 쉬운게, 설정할 값으로 1을 넣는다고 해서 변수의 값이 1이 되는 것은 아닙니다. memset() 함수는 전달한 범위 만큼의 메모리 블럭을 특정 수로 설정하는것이며, 이때 4 byte의 저장공간을 가지는 int형 변수의 경우 변수의 데이터를 담고 있는 4개의 각 블럭에 1(0b00000001)이 저장되어 실제로 저장되는 수는 signed int 타입이기 때문에 16843009(0b00000001000000010000000100000001)가 됩니다.

메모리 블록 데이터 비교 - memcmp() #

string.hstrcmp()가 있듯이, memory.h에도 메모리를 비교하는 함수가 있습니다.

c int memcmp(const void *s1, const void *s2, size_t n);

  • const void *s1 : 비교할 첫 번째 메모리 블록을 가리키는 포인터
  • const void *s2 : 비교할 두 번째 메모리 블록을 가리키는 포인터
  • size_t n : 비교할 바이트 수
  • 반환값 : 두 메모리 데이터가 같으면 0, 다르면 음수 또는 양수 반환
  • 코드

    c
    #include <memory.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct {
      int m_id;
      char *m_nm;
      int m_age;
    } member_t;
    
    int main(void) {
      member_t s1, s2;
      s1.m_id = 1;
      s1.m_nm = "qwer";
      s1.m_age = 10;
    
      memcpy(&s2, &s1, sizeof(member_t));
    
      if (memcmp(&s1, &s2, sizeof(member_t)) == 0) {
        printf("두 구조체의 내용은 동일합니다.");
      } else {
        printf("두 구조체의 내용은 다릅니다.");
      }
    
      return 0;
    }

member_t 타입의 구조체 s1에 데이터를 입력하고 이를 memcpy() 함수를 이용해 s2에 복사한 후 s1s2를 비교하는 예시입니다. 당연하게도 내용이 복사되었기 때문에 두 구조체의 데이터는 동일합니다.

위 코드의 출력은 다음과 같습니다.

두 구조체의 내용은 동일합니다.