memset은 <cstring> 헤더에 들어있습니다.

 

배열 초기화할 때 memset을 사용하는 분들이 많을 텐데요, fill_n은 iteration으로 값을 초기화하기 때문에 느릴 것 같아서, fill은 그냥 memset보다 느릴 것 같아서(?) memset을 쓰는 분이 많을 거라 생각합니다.

아니면 그냥 memset이 쓰기 편해서 쓸 수도 있고요.

 

어떤 이유건간에 memset을 배열을 특정값으로 초기화 하려면 각별히 주의하셔야 합니다.

배열의 값을 0, -1 이외의 값으로 초기화 할 때는 사용하면 안됩니다.

 

void* memset( void* dest, int ch, std::size_t count );

memset의 함수 원형입니다.

  • dest: 값을 채울 오브젝트의 포인터
  • ch: 채울 값
  • count: 채울 byte 수

여기서 함정이 있는데, ch가 int 변수지만 unsigned char, 즉 1바이트 값으로 변환되어 count 만큼의 바이트만큼 채워지게 됩니다.

 

배열의 값을 3으로 초기화 하려고 memset 파라미터로 3을 넣으면 어떻게 될까요?

#include <iostream>
#include <cstring>
using namespace std;

int main() {
	int arr[10];
	memset(arr, 3, sizeof(arr));

	for (auto& c : arr)
        cout << c << ' ';	// 50529027

	return 0;
}

3이 아니라 50529027이라는 값이 나오네요. 왜 그럴까요?

arr[i] = 00000011000000110000001100000011

배열 크기만큼의 모든 바이트에 3(0b00000011)을 대입했기 때문에 이렇게 이상한 값으로 초기화 되는 겁니다.

 

0과 -1만 memset으로 잘 초기화 되는 이유는 0은 00000000, -1은 11111111의 값으로 캐스팅 되기 때문에 배열의 초기화랑 잘 맞아떨어져서 되는 겁니다.

 

그러면 0, -1이 아닌 값으로 배열 초기화를 어떻게 할까요? 그건 fill, fill_n을 써야 합니다.

fill, fill_n은 무조건 반복문으로 초기화를 해서 느릴까요? 무조건 그런건 아닙니다.

내부적으로 memset을 쓸 수 있으면 쓰고, 아니면 반복문으로 초기화 하는 식으로 구현되어 있기 때문에 동일합니다.

 

Fig 1. MSVC 14.26.28801 xutility

MSVC의 STL 소스를 보면 fill이 호출됐을 때 _Fill_memset_is_safe.... 가 뭔진 모르겠지만 이 값을 확인해서 memset이 가능한 경우 memset으로 메모리에 때려넣고, 아니면 밑에 반복문으로 초기화 시키는 것으로 보입니다.

 

Fig 2. gcc stl_algobase.h

gcc도 확인해봤는데 비슷합니다. fill 함수가 __fill_a 셋중에 하나를 골라 호출하는 방식이네요. 셋 중 하나는 __builtin_memset을 사용하는 걸 알 수 있습니다.

 

그런데! 비주얼 스튜디오에서 이것저것 fill을 사용해봤는데 디버깅해서 찍어보니까 전부 반복문으로 돌아갑니다;;; 왜죠??

대체 언제 fill을 호출했을 때 memset이 되는건지;;; 잘 모르겠습니다. 아시는 분은 댓글 부탁합니다

반응형