C++ sizeof 그리고 random
sizeof는 C부터 사용했던 연산자로 malloc과 같이 메모리를 할당할 때 크기를 나타내기 위해 주로 사용했습니다. C++에서도 sizeof 연산자를 사용할수 있으며 간단한 사용법을 알아보려고 합니다.
sizeof
주로 배열의 크기를 계산하기 위해 사용되며 해당 변수의 값에 상관없이 메모리의 크기를 알 수 있습니다.
std::cout << sizeof(1) << std::endl; // print: 4
std::cout << sizeof(5.12) << std::endl; // print: 8
std::cout << sizeof(10.0f) << std::endl; // print: 4
std::cout << sizeof(char) << std::endl; // print: 1
std::cout << sizeof(1 + 5.12) << std::endl; // print: 8
리터럴값도 사용가능하며, char와 같이 자료형이 들어갈 수 도 있습니다. 마지막은 int와 double의 산술연산은 int가 double로 암시적 형변환을 통해 double과 double의 합으로 계산됩니다. 그래서 double의 크기인 8바이트가 출력된 것을 확인할 수 있습니다.
sizeof 연산자는 위 예시들과 같이 사용하면 컴파일 타임에 처리됩니다. 즉 실행전에 이미 계산이 됐다고 생각할 수 있습니다. 반대로 런타임에 처리되는 경우도 간혹 있습니다.
class Base {
public:
virtual void func() {}
};
int main() {
Base* ptr = new Base();
std::cout << sizeof(*ptr) << std::endl;
delete ptr;
return 0;
}
런타임에 결정되는 요소를 포함한 sizeof 연산자는 런타임에 실행될 수도 있습니다. 위 예시는 객체가 생성된 후 확인할 수 있게 가상 테이블을 생성하여 sizeof 연산자를 런타임에 실행할 수 있게 작성되었습니다. 하지만 위 예시가 반드시 런타임에 실행되는 것은 아님니다. 적시 컴파일 환경에서는 객체 타입을 동적으로 판단하기에 앞에서 말한 런타임 때 실행될 확률이 높습니다. 하지만 대부분의 C++ 컴파일러는 최적화를 위해 컴파일 타임에 실행되기 때문에 런타임에 실행될 확률은 낮습니다.
rand, srand
이 함수들은 C의 stdlib 라이브러리에 존재하며 헤더를 확인하면 아래와 같은 형식으로 프로토타입이 선언되어 있습니다.
#define RAND_MAX 0x7fff
_ACRTIMP void __cdecl srand(_In_ unsigned int _Seed);
_Check_return_ _ACRTIMP int __cdecl rand(void);
가장 위에 선언된 매크로 RAND_MAX 값은 rand 함수에서 반활 될 수 있는 가장 큰 값입니다. rand는 컴파일 타임때 고정된 시드값을 사용하기 때문에 실행시 같은 패턴의 난수가 생성됩니다. 그래서 srand 함수를 통해 시드값을 변경하여 런타임때 다른 패턴의 난수가 실행될 수 있게 합니다. 그래서 주로 아래와 같은 형식으로 사용됩니다.
#include <iostream>
#include <cstdlib>
int main() {
srand(time(0));
std::cout << rand() << std::endl;
}
그리고 srand의 상수값 혹은 리터럴값을 넣을 경우 동일하게 같은 패턴의 난수가 생성되기 때문에, 시간과 같은 동적인 값을 넣어주는 경우가 대부분입니다. 난수를 간단히 생성할 수 있다는 장점이 있습니다. 하지만 큰 단점들이 존재합니다. 우선 rand() 함수의 방식은 선형 합동 생성기 를 기반으로 동작합니다. 선형 합동 생성기는 재귀로 정의된 순열로 시드를 임의의 횟수만큼 연산을 반복하여 난수를 생성합니다. 그래서 반복되는 패턴을 토대로 샘플을 수집하면 예측이 가능하기 때문에 보안에 큰 위험이 있습니다.
그리고 rand는 마지막 난수를 다음 난수로 생성하기 위한 시드로, 전역상태로 공유하기 때문에 멀티스레드 환경에서 안전하지 않습니다. 그렇다면 C++에서는 난수를 생성하기 위해 어떤 방식을 사용할까요?
std::random
모던 C++ 에서는 rand를 대신하여 더 강력한 난수 라이브러리 random을 제공합니다. 사용방법은 아래와 같습니다.
#include <iostream>
#include <random>
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(0, 99);
std::cout << dist(gen) << std::endl;
}
위 예시는 0 ~ 99까지의 범위의 난수를 생성하는 방법입니다. std::random_device는 srand에 들어가는 시드 time(0)과 같은 시드값을 제공하는 객체입니다. 이 시드값을 std::mt19937 난수를 생성하는 엔진에 넣어 시드를 초기화 합니다. std::mt19937은 메르센 트위스터 라는 알고리즘을 사용하여 난수열을 생성하는 객체로, 생성되는 난수들 간 상관관계가 매우 작기에 더 양질의 난수를 얻을 수 있습니다. 그리고 난수의 범위와 타입에 따른 분포를 정의합니다. 위 예시에서는 std::uniform_int_distribution 균등 분포 객체를 사용하여 0 부터 99까지의 범위를 가진 균등 분포를 생성하였습니다. 생성한 균등 분포안에 엔진을 전달함으로써 무작위 난수를 얻을 수 있습니다.