1. 개요
게임 세상 속에서는 아이템의 유효기간이나 유저 로그인 시간, 이펙트 발동 시간, 스킬 지속 시간 등 많은 시간들이 존재한다. 이러한 시간을 계산을 할 때 클라이언트(유저) 시간과 게임 안에서의 시간(서버시간) 등의 차이로 남은 시간을 계산한다.
이때 Windows 환경에서 흔히 사용하는 함수 중 하나가 GetTickCount() [ 컴퓨터 부팅 시간 ]이다.하지만 GetTickCount()는 최대 49.7일마다 반환값이 0으로 순환한다는 한계가 있다.
과거에는 자주 컴퓨터를 껐다 켰지만 요즘에는 컴퓨터 화면만 절전해 두고 오래 컴을 키는 경우가 많아진다. 그럴 때 DWORD 자료형 리턴값을 가지고 있는 GetTickCount를 사용하면 시간 계산이 잘 못 될 수가 있다.
2. GetTickCount()와 자료형에 따른 시간 한계 알아보기
GetTickCount(시스템이 시작된 후 경과된 시간(최대 49.7일)을 검색합니다. )를 사용하면 이러한 경고도 따라 붙는다.
warning C28159: Consider using 'GetTickCount64' instead of 'GetTickCount'. Reason: GetTickCount overflows roughly every 49 days. Code that does not take that into account can loop indefinitely. GetTickCount64 operates on 64 bit values and does not have that problem
- DWORD WINAPI GetTickCount(VOID);
- ULONGLONG WINAPI GetTickCount64(VOID);
DWORD는 unsigned long 형으로 C/C++언어의 윈도우 환경에서 4byte로 4 * 8bit = 32bit = 2의 32승 = 4,294,967,296이다.
ULONGLONG는 unsigned --int64 형으로 C/C++언어의 윈도우 환경에서 8byte로 8 * 8bit = 64bit = 2의 64승 = 18,446,744,073,709,551,615(약 1844경)이다.
시간을 밀리세컨트로 계산하면
- 1초 1,000
- 1분 = 60초 = 60 * 1,000
- 1시간 = 1분 * 60 = 60 * 60 * 1,000
- 24시간 = 1시간 * 24 = 24 * 60 * 60 * 1'000 = 86,400,000
- 2의 32승 / 24시간 = 4,294,967,296 % 86,400,000 = 49.71026..... 일 (약 49일) ★GetTickCount★
- 2의 64승 / 24시간 = 18,446,744,073,709,551,615 % 86,400,000 = 213,503,982,334,6일 (약 5억 년)
3. unsinged의 계산
GetTickCount()를 사용할 때 발생할 수 있는 문제점은 클라이언트 시간(C)이 53일 켰다고 가정하자. unsinged는 최대 크기를 벗어나는 상태를 받으면 쓰레기 값이나 음수가 되는 것이 아니라 다시 0부터 시작하는 값이 된다.
- Tick 범위: 0~49
- 실제 경과: 53
- 반환값: 53 % 50 = 3
Max:49 라 가정하고 값이 53이 들어가면 C의 값은 3이다.
게임 속에서 계산이 대부분 서버와 클라이언트의 시간 차이를 통해서 원하는 상황을 판단하는데 그 판단이 엇갈릴 수 있다는 것이다.
대안은 GetTickCount64로 바꾼다거나 C++표준에 포함된 시간 처리 라이브러리 Chrono를 사용하는 것이다.
GetTickCount64는 64비트로 확장되었기 때문에, 약 5억 년 넘게 pc를 켜놔야 문제를 일으킨다.
하지만, 여기서는 GetTickCount()를 사용하고 제한을 두는 방향을 고려해서 문제를 해결해보려고 한다.
| int32_t | -2,147,483,648 ~ 2,147,483,647 (-2^31 ~ 2^31 - 1) / 약 -24.85일 ~ 24.85일 |
| DWORD(unsigned long) | 0 ~ 4,294,967,295 / 약 0일 ~ 49.7일 |
일단 서버와 클라이언트의 시간 차이 발생 해결 방법에 대해서 알아봐야 한다.
4. 서버와 클라이언트의 Tick 차이 보정
[서버컴퓨터의 부팅시간]과 [유저 컴퓨터 부팅시간]은 다르다.
ex) [서버, ServerTick ]: GetTickCount() = 3일, [클라이언트, ClientTick] GetTickCount() = 54일, Max를 49일이라고 가정
** 게임을 처음 접속 할 때 서버틱 시간을 받는다.
DWORD ClientServerTickDiff = GetTickCount() - ServerTick; // - ((54일-> 4일)4 - 3) = 1일
** 서버에서 준 ServerEndTick = ServerTick(3일) + Duration(4일) = 7일
** 클라이언트에서는 ServerEndTick을 받으면, ClientServerTickDiff를 더해 클라이언트 타임으로 바꿔야한다.
그래야 클라이언트 접속시간 기준으로 Duration(4일)이 더해진 EndTick이 만들어진다.
ClientEndTick = ClientServerTickDiff + ServerEndTick
※ 하지만 서버에서 ServerTick을 측정한 시점과 클라이언트가 패킷을 수신한 시점 사이에는 네트워크 지연이 존재한다. 따라서 정확한 시간 동기화라기보다는 서버와 클라이언트의 Tick 기준 차이를 근사적으로 보정하는 방식이 많다.
5. DWORD를 이용한 시간 계산 시 주의사항
5-1. EndTick()까지 기다리는 경우, (EndTick - CurTick = RemainTick)을 사용하려는 경우
- 현재 Tick이 종료 Tick에 도달했는지 확인한다.
- CurTick >= EndTick처럼 단순 비교하면 Tick순환 시 잘못된 결과가 발생한다.
- 두 Tick 사이의 시간 차이를 int32_t로 변환하여 비교하는 방식을 사용한다.
가정: DWORD (0~49), int32_t = (-25~24), 현재 시각과 종료 시각의 실제 시간 차이가 앞뒤 어느 방향이든 2^31ms미만이어야 한다.
int32_t remainTick = static_cast<int32_t>(EndTick - CurTick);
if(remainTick <= 0)
{
//종료되었다고 잘못 판단!
}
else
{
//남은 시간 처리
}
ex)
* 48일 - 2일 = 46일
* int32_t: -4일

5-2. (CurTick - OldTick = ElapsedTick)을 빼서 흐른 시간을 알려고 하는 경우.
- DWORD에서는 CurTick이 OldTick보다 작을 수 있다.
- DWORD의 unsigned 뺄셈을 이용하면 한 번의 순환을 포함한 경과 시간을 계산할 수 있다.
- 실제 경과 시간 약 49.7일 미만 제한 조건을 걸어야 한다.
6. 결론
마음편하게 약 24일 정도 제한을 넘어갈 수 있는 상황이 생길걸 예방하려면 GetTickCount()는 사용하면 안된다. GetTickCount64() 또는 std::chrono::steady_clock을 사용하는 것이 좋다.
'👨🏻💻 programming > ◽ 에러 해결 메모' 카테고리의 다른 글
| [C++] std::sort invalid comparator 오류 원인과 해결 (0) | 2026.10.17 |
|---|---|
| [C++] warning C4541: dynamic_cast와 /GR- 문제 원인과 해결 (0) | 2026.03.12 |
| [Live] 다수 인원 환경에서 화면 깨짐 및 마크 잘림 현상 분석 (2) | 2025.05.26 |
| [HTTP] 416 Range Not Satisfiable 오류 원인과 해결 (1) | 2024.02.07 |
개발을 하는 이유는 단순합니다. 살면서 성취감을 처음 느껴봤습니다.