C++ memset() 함수 동작 원리와 실전 활용법

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 결과용도
000 00 00 000초기화
-1 (0xFF)FF FF FF FF-1음수 초기화
0x3F3F 3F 3F 3F1061109567큰 수(INF)
0x7F7F 7F 7F 7F2139062143최대값 근사
0x8080 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을 사용할 때는 반드시 대상 자료형의 바이트 크기와 패딩을 고려해야 한다.

태그: memset C++ 메모리초기화 알고리즘 std::fill

7월 2일 16:32에 게시됨