HOWTO · Arduino
Arduino memcpy 및 memmove
memcpy() 및 memmove() 함수를 사용하여 한 변수에서 다른 변수로 메모리 블록을 복사 할 수 있습니다.
이 페이지의 내용
Arduino 프로그램에서는 표준 C 라이브러리 함수인 memcpy()와 memmove()를 사용해 메모리 위치 사이의 바이트를 복사할 수 있습니다. 원본 영역과 대상 영역이 겹치지 않는다면 memcpy()를, 겹칠 가능성이 있다면 memmove()를 선택하세요. 두 함수 모두 호출할 때 지정한 바이트 수만큼 복사합니다. 어느 함수도 대상 버퍼의 크기를 알지 못하므로, 대상에 충분한 공간이 있는지 프로그램에서 확인해야 합니다.
바이트를 복사하는 memcpy()
함수 원형은 void *memcpy(void *dest, const void *src, size_t n);입니다. dest는 쓸 첫 번째 바이트를 가리키고, src는 읽을 첫 번째 바이트를 가리킵니다. n은 배열 요소의 개수가 아니라 바이트 수입니다. 배열이 범위 안에 있을 때 sizeof(array)는 배열 전체의 크기를 바이트 단위로 반환합니다. 포인터에 sizeof(pointer)를 적용하면 배열 크기가 아니라 포인터 자체의 크기를 반환합니다.
원본 영역과 대상 영역은 겹치면 안 됩니다. 두 영역에 단 한 바이트라도 함께 포함되면 memcpy() 호출의 동작은 정의되지 않습니다. C와 C++ 언어 규칙은 이 경우 특정한 결과를 보장하지 않습니다. 호출하면 항상 오버플로가 발생하거나 데이터가 특정한 방식으로 손상된다고 예측하는 것도 정확하지 않습니다. 일반적인 복사에는 별도의 대상 버퍼를 사용하고, 그 버퍼에 최소 n바이트를 쓸 수 있는지 확인하세요. 이 함수는 메모리를 할당하거나 대상 용량을 확인하지 않습니다.
영역이 겹칠 때 사용하는 memmove()
void *memmove(void *dest, const void *src, size_t n);은 같은 인수를 받아 같은 수의 바이트를 복사합니다. memcpy()와 달리 원본 영역과 대상 영역이 겹쳐도 동작이 정의됩니다. 원본 바이트를 먼저 보존한 뒤 대상에 기록한 것과 같은 결과를 내므로, 배열의 일부를 같은 배열 안에서 이동할 때 적합합니다.
이 보장은 영역의 겹침만 다룹니다. memmove()도 dest부터 n바이트를 기록합니다. 포인터가 가리키는 객체의 크기를 알아내거나 범위를 벗어난 쓰기를 방지하지는 않습니다. 호출하기 전에 대상에 쓸 수 있는 공간을 확인하고, 원본 영역도 n바이트만큼 유효한지 확인하세요.
memcpy()와 memmove() 중 선택하기
두 영역이 분리되어 있다고 확실히 알 때는 memcpy()를 사용하세요. 이 함수는 겹치지 않는 복사라는 단순한 경우를 직접 표현하며, 구현에 따라 최적화된 비중첩 복사를 사용할 수도 있습니다. 영역이 겹칠 수 있거나 두 영역의 관계가 확실하지 않다면 memmove()를 사용하세요. 특정 플랫폼에서 겹치는 memcpy() 호출이 우연히 작동하는 것처럼 보이더라도 이에 의존하지 마세요. 컴파일러 최적화로 인해 정의되지 않은 동작은 특히 예측하기 어려울 수 있습니다.
두 함수 모두 먼저 복사할 바이트 수와 대상의 용량을 확인해야 합니다. 배열 전체를 복사한다면 배열이 포인터로 변환되기 전에 sizeof 값을 사용하고, 대상 공간을 그 바이트 수 이상으로 확보하세요. char, unsigned char, std::byte 이외의 형식으로 된 요소를 복사하려면 요소 개수에 sizeof(element)를 곱하고, 해당 형식에 이 연산이 적절한지도 확인하세요. 이 함수들은 객체의 메모리 표현을 복사할 뿐, 요소별 변환이나 대입을 수행하지 않습니다.
Arduino 예제: 배열 복사하기
다음 독립 실행형 C++ 프로그램은 별도의 버퍼에 배열을 복사하는 방법과 하나의 버퍼 안에서 데이터를 이동하는 방법을 보여 줍니다. Arduino C++ 코드에서도 같은 표준 라이브러리 함수를 사용할 수 있습니다. 보드에서는 복사 코드를 setup()에 넣고 Serial.println()으로 대상 내용을 시리얼 모니터에 출력하세요. 문자열을 C 문자열로 출력하려면 끝의 널 문자를 포함해 복사할 바이트를 모두 담을 수 있도록 대상 공간을 확보해야 합니다.
표준 C++ 컴파일러로 이 전체 C++ 예제를 실행하세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
#include <cstdio>
#include <cstring>
int main() {
const char source[] = "LED";
char destination[sizeof(source)] = {};
std::memcpy(destination, source, sizeof(source));
std::printf("%s\n", destination);
char buffer[] = "ABCDE";
std::memmove(buffer + 1, buffer, 4);
std::printf("%s\n", buffer);
return 0;
}
LED
AABCD
여기서 destination은 널 종료 문자를 포함한 원본 배열 전체를 담기에 정확한 크기입니다. 두 번째 복사에서는 buffer 안의 네 바이트를 오른쪽으로 한 위치 이동합니다. 원본 영역과 대상 영역이 겹치므로 memmove()가 적절합니다. Arduino 스케치에서는 평소처럼 Arduino.h를 포함하고 Serial.begin(...)을 호출한 뒤 데스크톱의 std::printf() 호출을 Serial.println()으로 바꾸세요. 메모리 복사 호출과 크기 확인 요건은 그대로입니다.