memmove 함수 구현하기

윤효준·2024년 7월 25일

42 Libft 복습

목록 보기
10/28

memmove 함수의 manual은 다음과 같다!

Synopsis

#include <string.h>

void	*memmove(void *dst, const void *src, size_t len);

Description

  • memmove함수는 len 바이트를 src문자열에서 dst문자열로 복사한다.

    memcpy에서는 src와 dst를 메모리 영역이라고 칭했는데 왜 여기서는 문자열이라고 했는지 잘 모르겠다. 다만 일반적인 사용 사례 중 하나이므로 string을 쓴 것이 아닐까 생각이 든다.

  • 두 문자열은 겹칠 수 있으며 원본 데이터를 손상시키지 않고 안전하게 복사한다.

    memcpy와의 차이점이 여기서 나타난다!

Return Values

  • memmove함수는 dst의 값을 반환한다.

구현

구현 시 가장 중요한 부분은 두 문자열이 겹쳤을 때 원본 데이터를 손상시키지 않아야 한다는 것이다.

예를 들어 memcpy 함수 구현에서 들었던 예시를 가지고 오겠다.

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

int main(void)
{
	int	arr[5] = {0, 1, 2, 3, 4};
    memcpy(arr + 2, arr, 12);
    //memcpy는 바이트 단위로 복사하기에 정수 3개 복사하기에는 12바이트가 필요하다.
    //정수를 3개만 복사하는 이유는 4개 이상을 복사하면 arr 영역을 벗어나 오버플로우가 발생하기 때문이다.
    for (int i = 0; i < 5; i++)
    	printf("%d ", arr[i]);
    return (0);
}

여기서 memcpy함수 대신 memmove함수를 이용하여 배열 {0, 1, 2, 3, 4}를 {0, 1, 0, 1, 2}로 만들고 싶은 것이다.

여기서 arr를 arr + 2로 이동시킬 때 앞에서부터 옮긴다고 하면 아래와 같이 된다.

  1. arr[2]에 arr[0]인 0이 저장된다. >> arr[2]가 오염이 되어 나중에 문제를 일으킨다.
  2. arr[3]에 arr[1]인 1이 저장된다.
  3. arr[4]에 arr[2]인 0이 저장된다. >> 오염된 arr[2]를 사용하여 우리의 의도와 다르게 된다.

위와 같은 방법을 적용시켰을 때는 데이터가 오염되어 문제가 발생한다.
이때 어떻게 하면 데이터 오염을 방지할 수 있을까??

바로 마지막부터 이동을 시키는 것이다.

arr + 2와 arr를 12 바이트씩 이동시켜 arr[2]를 arr[4]로 먼저 이동시키는 것이다.
1. arr[4]에 arr[2]인 2가 저장된다.
2. arr[3]에 arr[1]인 1이 저장된다.
3. arr[2]에 arr[0]인 0이 저장된다.

따라서 arr = {0, 1, 0, 1, 2}가 저장된다.
위와 같은 방법을 통해 우리는 이동시키는 데이터를 오염없이 이동시킬 수 있다.

그럼 항상 뒤에서부터 이동을 시작하면 될까???
그건 아니다.

memmove(arr, arr + 2, 12)를 생각해보자.
아래는 arr + 2를 arr로 이동시킬 때 마지막부터 이동을 단계별로 설명한 것이다.

arr와 arr + 2를 12 바이트씩 이동시켜 arr[4]를 arr[2]로 먼저 이동시키는 것이다.
1. arr[2]에 arr[4]인 4가 저장된다.
2. arr[1]에 arr[3]인 3이 저장된다.
3. arr[0]에 arr[2]인 4가 저장된다. >> arr[2]는 1번에서 오영되었다.

데이터가 오염이 되어 의도한 동작이 나오지 않았다.
이 경우는 memcpy함수를 구현했던 방식대로 앞에서부터 이동시켜야 오염이 없다.
따라서srcdst의 위치에 따라 이동 방식을 달리해야 한다.

static void	copy_forward(unsigned char *d, const unsigned char *s, size_t len)
{
	while (len-- > 0)
    	*d++ = *s++;
}

static void	copy_backward(unsigned char *d, const unsigned char *s, size_t len)
{
	while (len-- > 0)
    	*d-- = *s--;
}

void	*ft_memmove(void *dst, const void *src, size_t len)
{
	unsigned char		*d;
	const unsigned char	*s;

	// 널 포인터 검사
	if (dst == NULL || src == NULL)
		return (NULL);
	d = (unsigned char *)dst;
	s = (const unsigned char *)src;
	//d와 s의 대소 비교
	if (d < s)
		copy_forward(d, s, len);
	else if (d > s)
	{
		d += len - 1;
		s += len - 1;
		copy_backward(d, s, len);
	}

	return (dst);
}

static이란?

  • static 키워드를 사용하여 선언된 함수는 해당 파일 내에서만 접근할 수 있어 동일한 프로젝트 내에서 다른 파일의 동일한 이름의 함수와 충돌을 방지한다.
  • 또한 모듈의 내부 구현을 외부에 노출하지 않도록 하기 위해 사용한다.

위의 내용은 함수 선언에 사용하는 static 키워드이다.
함수 선언 외에도 정적 변수, 정적 전역 변수, 클래스 정적 멤버를 선언할 때 사용하는데 앞의 3개에 대해서는 이후 과제에서 나오기에 그때 설명하겠다!

profile
작은 문제를 하나하나 해결하며, 누군가의 하루에 선물이 되는 코드를 작성해 갑니다.

0개의 댓글