C++ random_device | "하드웨어 난수" 가이드
이 글의 핵심
C++ random_device에 대한 실전 가이드입니다.
들어가며
**std::random_device**는 C++11에서 도입된 하드웨어 기반 난수 생성기입니다. 시스템의 엔트로피 소스(예: /dev/urandom)를 사용하여 비결정적 난수를 생성하며, 주로 난수 엔진의 시드 생성이나 암호학적 용도로 사용됩니다.
1. random_device 기본
기본 사용
#include <iostream>
#include <random>
int main() {
std::random_device rd;
// 난수 생성 (32비트 unsigned int)
for (int i = 0; i < 5; ++i) {
std::cout << rd() << std::endl;
}
return 0;
}
출력:
3847291038
1029384756
2938475610
4756102938
1847562903
시드 생성
#include <random>
#include <iostream>
int main() {
// random_device로 시드 생성
std::random_device rd;
// mt19937 엔진 초기화
std::mt19937 gen{rd()};
// 균등 분포
std::uniform_int_distribution<> dist{1, 100};
// 난수 생성 (빠름)
for (int i = 0; i < 10; ++i) {
std::cout << dist(gen) << " ";
}
std::cout << std::endl;
return 0;
}
출력:
42 17 89 3 56 91 28 64 11 73
2. 엔트로피
entropy() 메서드
#include <iostream>
#include <random>
int main() {
std::random_device rd;
// 엔트로피 확인 (비트)
double entropy = rd.entropy();
std::cout << "엔트로피: " << entropy << " bits" << std::endl;
if (entropy == 0.0) {
std::cout << "의사 난수 (결정적)" << std::endl;
} else {
std::cout << "하드웨어 난수 (비결정적)" << std::endl;
}
return 0;
}
출력 (Linux):
엔트로피: 32 bits
하드웨어 난수 (비결정적)
플랫폼별 구현
| 플랫폼 | 엔트로피 소스 | entropy() |
|---|---|---|
| Linux | /dev/urandom | 32 bits |
| Windows | CryptGenRandom | 32 bits |
| macOS | /dev/urandom | 32 bits |
| 일부 임베디드 | 의사 난수 | 0 bits |
3. 시드 시퀀스
단일 시드 vs 여러 시드
#include <random>
#include <array>
#include <algorithm>
int main() {
std::random_device rd;
// ❌ 단일 시드 (32비트만 사용)
std::mt19937 gen1{rd()};
// ✅ 여러 시드 (더 좋은 초기화)
std::array<unsigned int, std::mt19937::state_size> seedData;
std::generate(seedData.begin(), seedData.end(), std::ref(rd));
std::seed_seq seq(seedData.begin(), seedData.end());
std::mt19937 gen2{seq};
return 0;
}
설명:
mt19937의 상태 크기는 624개의 32비트 정수 (19,968비트)- 단일 시드는 32비트만 사용 (나머지는 알고리즘으로 채움)
- 여러 시드는 더 많은 엔트로피 제공
4. 실전 예제
예제 1: 암호학적 난수
#include <random>
#include <vector>
#include <iostream>
#include <iomanip>
std::vector<unsigned char> generateKey(size_t length) {
std::random_device rd;
std::vector<unsigned char> key(length);
for (auto& byte : key) {
byte = static_cast<unsigned char>(rd() % 256);
}
return key;
}
int main() {
// 16바이트 키 생성
auto key = generateKey(16);
std::cout << "암호 키: ";
for (auto byte : key) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(byte);
}
std::cout << std::endl;
return 0;
}
출력:
암호 키: 3f7a9b2c8d1e4f6a5b3c9d2e7f1a4b8c
예제 2: UUID 생성
#include <random>
#include <sstream>
#include <iomanip>
#include <iostream>
std::string generateUUID() {
std::random_device rd;
std::mt19937 gen{rd()};
std::uniform_int_distribution<> dist{0, 15};
std::uniform_int_distribution<> dist2{8, 11};
std::ostringstream oss;
oss << std::hex;
// 8-4-4-4-12 형식
for (int i = 0; i < 8; ++i) oss << dist(gen);
oss << "-";
for (int i = 0; i < 4; ++i) oss << dist(gen);
oss << "-4"; // 버전 4
for (int i = 0; i < 3; ++i) oss << dist(gen);
oss << "-";
oss << dist2(gen); // 변형 (8, 9, a, b)
for (int i = 0; i < 3; ++i) oss << dist(gen);
oss << "-";
for (int i = 0; i < 12; ++i) oss << dist(gen);
return oss.str();
}
int main() {
for (int i = 0; i < 3; ++i) {
std::cout << generateUUID() << std::endl;
}
return 0;
}
출력:
550e8400-e29b-41d4-a716-446655440000
6ba7b810-9dad-11d1-80b4-00c04fd430c8
3d813cbb-47fb-32ba-91df-831e1593ac29
예제 3: 토큰 생성
#include <random>
#include <string>
#include <iostream>
std::string generateToken(size_t length) {
std::random_device rd;
std::mt19937 gen{rd()};
const std::string chars =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
std::uniform_int_distribution<> dist{0, static_cast<int>(chars.size() - 1)};
std::string token;
token.reserve(length);
for (size_t i = 0; i < length; ++i) {
token += chars[dist(gen)];
}
return token;
}
int main() {
// 32자 토큰 생성
std::cout << "토큰: " << generateToken(32) << std::endl;
return 0;
}
출력:
토큰: 7aB3xK9mP2qW5nL8vC1dF4jH6rT0yU3z
5. 자주 발생하는 문제
문제 1: 성능
#include <random>
#include <chrono>
#include <iostream>
void benchmarkRandomDevice() {
std::random_device rd;
auto start = std::chrono::high_resolution_clock::now();
// ❌ random_device 직접 사용 (매우 느림)
for (int i = 0; i < 1000000; ++i) {
volatile unsigned int r = rd();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "random_device: " << duration.count() << "ms" << std::endl;
}
void benchmarkMT19937() {
std::random_device rd;
std::mt19937 gen{rd()};
auto start = std::chrono::high_resolution_clock::now();
// ✅ mt19937 사용 (빠름)
for (int i = 0; i < 1000000; ++i) {
volatile unsigned int r = gen();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "mt19937: " << duration.count() << "ms" << std::endl;
}
int main() {
benchmarkRandomDevice(); // ~5000ms
benchmarkMT19937(); // ~50ms
return 0;
}
출력:
random_device: 5234ms
mt19937: 47ms
문제 2: 플랫폼 의존성
#include <random>
#include <iostream>
int main() {
std::random_device rd;
// 엔트로피 확인
double entropy = rd.entropy();
if (entropy == 0.0) {
std::cerr << "경고: random_device가 의사 난수를 사용합니다." << std::endl;
std::cerr << "플랫폼: 하드웨어 난수 미지원" << std::endl;
// 대안: 시간 기반 시드
auto now = std::chrono::high_resolution_clock::now();
auto seed = now.time_since_epoch().count();
std::mt19937 gen{static_cast<unsigned int>(seed)};
} else {
std::cout << "하드웨어 난수 사용 (엔트로피: " << entropy << " bits)" << std::endl;
}
return 0;
}
문제 3: 시드 품질
#include <random>
#include <array>
#include <algorithm>
// ❌ 단일 시드 (품질 낮음)
void poorSeeding() {
std::random_device rd;
std::mt19937 gen{rd()}; // 32비트만 사용
}
// ✅ 여러 시드 (품질 높음)
void goodSeeding() {
std::random_device rd;
// mt19937 상태 크기만큼 시드 생성
std::array<unsigned int, std::mt19937::state_size> seedData;
std::generate(seedData.begin(), seedData.end(), std::ref(rd));
std::seed_seq seq(seedData.begin(), seedData.end());
std::mt19937 gen{seq};
}
// ✅ 간단한 여러 시드
void simpleGoodSeeding() {
std::random_device rd;
// 8개 시드 (256비트)
std::array<unsigned int, 8> seedData;
for (auto& seed : seedData) {
seed = rd();
}
std::seed_seq seq(seedData.begin(), seedData.end());
std::mt19937 gen{seq};
}
문제 4: 재현성
#include <random>
#include <iostream>
// ❌ 재현 불가 (디버깅 어려움)
void nonReproducible() {
std::random_device rd;
std::mt19937 gen{rd()}; // 매번 다른 시드
std::uniform_int_distribution<> dist{1, 100};
std::cout << dist(gen) << std::endl; // 매번 다른 결과
}
// ✅ 재현 가능 (디버깅 용이)
void reproducible(bool debug = false) {
std::mt19937 gen;
if (debug) {
gen.seed(42); // 고정 시드
} else {
std::random_device rd;
gen.seed(rd()); // 랜덤 시드
}
std::uniform_int_distribution<> dist{1, 100};
std::cout << dist(gen) << std::endl;
}
int main() {
std::cout << "디버그 모드:" << std::endl;
reproducible(true); // 항상 같은 결과
reproducible(true); // 항상 같은 결과
std::cout << "\n프로덕션 모드:" << std::endl;
reproducible(false); // 매번 다른 결과
reproducible(false); // 매번 다른 결과
return 0;
}
6. 실전 예제: 난수 유틸리티
#include <random>
#include <string>
#include <vector>
#include <array>
#include <algorithm>
class RandomUtils {
std::mt19937 gen;
public:
// 생성자: 고품질 시드
RandomUtils() {
std::random_device rd;
std::array<unsigned int, 8> seedData;
std::generate(seedData.begin(), seedData.end(), std::ref(rd));
std::seed_seq seq(seedData.begin(), seedData.end());
gen.seed(seq);
}
// 범위 내 정수
int randomInt(int min, int max) {
std::uniform_int_distribution<> dist{min, max};
return dist(gen);
}
// 범위 내 실수
double randomDouble(double min, double max) {
std::uniform_real_distribution<> dist{min, max};
return dist(gen);
}
// 랜덤 문자열
std::string randomString(size_t length) {
const std::string chars =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
std::uniform_int_distribution<> dist{0, static_cast<int>(chars.size() - 1)};
std::string result;
result.reserve(length);
for (size_t i = 0; i < length; ++i) {
result += chars[dist(gen)];
}
return result;
}
// 랜덤 바이트
std::vector<unsigned char> randomBytes(size_t length) {
std::uniform_int_distribution<> dist{0, 255};
std::vector<unsigned char> bytes(length);
for (auto& byte : bytes) {
byte = static_cast<unsigned char>(dist(gen));
}
return bytes;
}
// 배열 셔플
template<typename T>
void shuffle(std::vector<T>& vec) {
std::shuffle(vec.begin(), vec.end(), gen);
}
};
int main() {
RandomUtils rng;
// 정수
std::cout << "랜덤 정수 (1-100): " << rng.randomInt(1, 100) << std::endl;
// 실수
std::cout << "랜덤 실수 (0-1): " << rng.randomDouble(0.0, 1.0) << std::endl;
// 문자열
std::cout << "랜덤 문자열: " << rng.randomString(16) << std::endl;
// 바이트
auto bytes = rng.randomBytes(8);
std::cout << "랜덤 바이트: ";
for (auto byte : bytes) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(byte) << " ";
}
std::cout << std::endl;
// 셔플
std::vector<int> vec = {1, 2, 3, 4, 5};
rng.shuffle(vec);
std::cout << "셔플: ";
for (int x : vec) std::cout << x << " ";
std::cout << std::endl;
return 0;
}
출력:
랜덤 정수 (1-100): 73
랜덤 실수 (0-1): 0.642857
랜덤 문자열: aB7xK3mP9qW2nL5v
랜덤 바이트: 3f 7a 9b 2c 8d 1e 4f 6a
셔플: 3 1 5 2 4
정리
핵심 요약
- random_device: 하드웨어 기반 비결정적 난수
- 용도: 시드 생성, 암호학적 난수
- 성능: 느림 (시드로만 사용)
- entropy(): 비결정성 측정 (0이면 의사 난수)
- 시드 품질: 여러 시드로 초기화
- 재현성: 디버깅 시 고정 시드 사용
random_device vs mt19937
| 특징 | random_device | mt19937 |
|---|---|---|
| 속도 | 매우 느림 | 빠름 |
| 품질 | 하드웨어 난수 | 의사 난수 |
| 용도 | 시드 생성 | 일반 난수 |
| 재현성 | 불가 | 가능 (시드 고정) |
| 플랫폼 | 의존적 | 독립적 |
실전 팁
사용 원칙:
random_device는 시드 생성에만 사용- 실제 난수는
mt19937같은 엔진 사용 - 암호학적 용도는
random_device직접 사용 고려 - 여러 시드로 엔진 초기화 (품질 향상)
성능:
random_device는 시스템 콜 사용 (느림)- 엔진은 메모리 기반 (빠름)
- 대량 난수는 반드시 엔진 사용
디버깅:
- 디버깅 시 고정 시드 사용
- 프로덕션에서는
random_device시드 entropy()확인으로 플랫폼 검증
다음 단계
- C++ Distribution
- C++ Random
- C++ Algorithm Generate
관련 글
- C++ Distribution |
- C++ 난수 생성 |
- C++ Random |
- C++ async & launch |
- C++ Atomic Operations |