memset은 <cstring> 헤더에 선언된 메모리 초기화 함수로, 바이트 단위로 값을 채우는 특성을 이해하지 못하면 예상치 못한 버그를 만들어낼 수 있다. 이 글에서는 memset의 내부 동작을 분석하고, 다양한 자료형에 대한 올바른 사용법을 살펴본다.
핵심 원리: 바이트 단위 복사
memset의 프로토타입은 다음과 같다.
void* memset(void* dest, int ch, size_t count);
첫 번째 인자 dest는 대상 메모리 주소, 두 번째 ch는 채울 값, 세 번째 count는 바이트 수다. 중요한 점은 ch를 1바이트로 해석하여 count바이트만큼 반복한다는 것이다.
이 때문에 4바이트 int 배열에 memset(buf, 1, sizeof(buf))를 호출하면, 각 int는 0x01010101(16843009)이 되어 의도와 다른 결과가 나온다.
int 배열에서 안전하게 사용 가능한 값
| 설정값 | 바이트 패턴 | int 결과 | 용도 |
|---|---|---|---|
| 0 | 00 00 00 00 | 0 | 초기화 |
| -1 (0xFF) | FF FF FF FF | -1 | 음수 초기화 |
| 0x3F | 3F 3F 3F 3F | 1061109567 | 큰 수(INF) |
| 0x7F | 7F 7F 7F 7F | 2139062143 | 최대값 근사 |
| 0x80 | 80 80 80 80 | -2139062144 | 최소값 근사 |
알고리즘에서 무한대 상수 설정
그래프 알고리즘에서 자주 사용되는 무한대(INF) 설정에 memset을 활용할 수 있다. 0x3F3F3F3F는 다음 이유로 최적의 선택이다.
- 10^9 수준으로 일반적인 데이터 범위를 커버
- 덧셈 시 오버플로우 방지:
INF + x가 여전히 큰 양수 INF + INF도 32비트 int 범위 내- 모든 바이트가
0x3F로 동일 →memset(arr, 0x3F, sizeof(arr))가능
#include <cstring>
#include <iostream>
const int INF = 0x3F3F3F3F;
int main() {
int dist[1000];
memset(dist, 0x3F, sizeof(dist)); // 모든 원소를 INF로
std::cout << dist[0] << std::endl; // 1061109567 출력
// 다익스트라 등에서 안전한 연산
int a = dist[0] + 1000000000; // 오버플로우 없음
std::cout << a << std::endl;
return 0;
}
구조체 초기화
#include <cstring>
#include <iostream>
struct Packet {
unsigned short header;
unsigned char payload[64];
unsigned int checksum;
};
int main() {
Packet pkt;
memset(&pkt, 0, sizeof(Packet)); // 전체 영역 0으로
// 또는 특정 패턴으로 채우기
memset(pkt.payload, 0xAA, sizeof(pkt.payload)); // 테스트 패턴
return 0;
}
동적 메모리와의 사용
#include <cstring>
#include <iostream>
int main() {
size_t len = 1024;
char* buffer = new char[len];
// 할당 직후 초기화 (미수행 시 쓰레기값)
std::fill_n(buffer, len, 0); // C++ 스타일
// 또는
memset(buffer, 0, len); // C 스타일
// 사용 후 해제
delete[] buffer;
return 0;
}
흔한 실수와 대안
잘못된 사용:
int scores[50];
memset(scores, 5, sizeof(scores)); // 의도: 모두 5, 실제: 0x05050505
C++에서 권장하는 대안:
#include <algorithm>
int scores[50];
std::fill(std::begin(scores), std::end(scores), 5); // 값 5로 정확히 초기화
// 또는 범위 기반 초기화 (C++11 이후)
int values[10] = {}; // 모든 요소 0
성능 고려사항
memset은 대부분의 컴파일러에서 SIMD 명령어로 최적화되어 매우 빠르다. 그러나 다음 경우 주의가 필요하다.
- 작은 배열: 루프 언롤링이나 std::fill이 더 빠를 수 있음
- 클래스 객체: 비트wise 복사가 안전하지 않은 경우 (가상 함수 테이블 등)
- volatile 메모리: 컴파일러 최적화로 인해 예상치 못한 동작
결론
memset은 바이트 단위 초기화가 필요한 char 배열이나 0/-1/특수 상수로 int를 초기화할 때 유용하다. 그러나 C++에서는 타입 안전성을 위해 std::fill, std::fill_n, 값 초기화 등을 우선 고려하는 것이 바람직하다. memset을 사용할 때는 반드시 대상 자료형의 바이트 크기와 패딩을 고려해야 한다.