3장. 64비트 기반 프로그래밍

유니야·2023년 1월 5일

✏️ Win32 vs Win64

💻 하드웨어 관점에서의 64비트 컴퓨터

  • 구분 기준
    • 한 번에 송수신 가능한 데이터 크기
      => 즉, I/O Bus 시스템이 한 번에 송수신 가능한 데이터의 크기.
    • 데이터 처리 능력
      => 즉, CPU가 한 번에 읽어들일 수 있는 명령어의 크기.

💻 프로그래머 입장에서의 64비트 컴퓨터

  • 32비트 -> 64비트
    • 프로그램으로 표현할 수 있는 범위의 증가
    • 표현할 수 있는 메모리의 전체 크기
  1. 포인터의 크기
    포인터가 크면 클수록 프로그래머에게 유리하다.
    그 이유는 포인터가 크다 = 메모리 내 접근 가능한 주소의 범위가 크다 이기 때문.
  • 32비트 시스템

    • 포인터가 32비트
    • 한 번에 2^32 = 4G 주소 범위에 접근 가능.
  • 64비트 시스템

    • 포인터가 64비트
    • 한 번에 2^64 = 8G 주소 범위에 접근 가능.

포인터의 크기가 클수록 메모리 전체에 한 번에 접근할 수 있다.

  1. Bus의 용량 또한 고려해야 한다
    포인터의 크기가 크면 클수록 좋다는 이유로 128비트 시스템을 이용하고 I/O Bus 크기는 64비트인 상황을 가정해보자. Bus는 한 번에 64비트의 주소를 CPU로 보낼 수 있다. 이 때 128비트 크기의 주소를 전달해야 한다면 Bus는 64비트씩 두 번에 걸쳐 주소를 옮겨야 한다. 하나의 주소를 보내기 위해 I/O Bus를 두 번 이용하는 것은 시스템의 전체적인 성능 저하의 원인이 된다.

따라서 포인터의 크기는 크면 클수록 좋지만, I/O Bus가 한 번에 송수신할 수 있는 데이터의 크기만큼 클 때 가장 효율적이다.

✏️ 프로그램 구현 관점의 Win32 vs Win64

💻 64비트 기반 프로그래밍

64비트 시스템을 고려한 프로그래밍으로 자료형에 대해 고려해야 함.

  • LLP64 & LP64
    32비트 시스템과의 호환성을 중시한 모델
    포인터의 크기만 다를 뿐 거의 같다.

💻 64비트와 32비트 공존의 문제점

  • 데이터 손실의 문제
    아래 예시에서 32비트 시스템의 경우 포인터와 int의 크기가 각 4바이트이기 때문에 둘 사이의 형변환에서 문제가 없다. 그러나 64비트 시스템의 경우 포인터의 크기는 8바이트, int의 크기는 4바이트로 둘의 크기가 다르다. 따라서 int 데이터가 4바이트를 넘어서는 위치에 할당될 가능성이 있고, 형변환 과정에서 데이터가 손실될 수 있다.
#include <stdio.h>
#include <tchar.h>

int _tmain(int argc, TCHAR* args[])
{
    int arr[10] = {0,};
    int arrVal = (int)arr; // 데이터 손실이 발생할 수 있는 위치
                 
    printf("pointer: %d\n", arrVal); 

    return 0;
}

💻 Windows 스타일 자료형

  • Polymorphic 자료형
    다형성의 특징을 지니는 자료형.
#if defined(_WIN64)
    typedef __int64          LONG_PTR;
    typedef unsigned __int64 ULONG_PTR;
    typedef __int64          INT_PTR;
    typedef unsigned __int64 UINT_PTR;
#else 
    typedef long             LONG_PTR;
    typedef unsigned long    ULONG_PTR;
    typedef int              INT_PTR;
    typedef unsigned int     UINT_TPR;  
#endif

// 위에서 PTR은 POINTER가 아님. 포인터 연산을 위한 Polymorphic 자료형.

아래의 포인터 연산 예시에서 UINT_PTR 대신 UNIT를 사용한다면, UNIT는 4바이트이므로 64비트 시스템에서는 문제가 생길 수 있다. 따라서 UINT대신 Polymorphic 자료형인 UINT_PTR를 사용하는 편이 좋다.

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <math.h>
 
UINT_PTR CalculateDistance(UINT_PTR uDest, UINT_PTR uSour)
{
    return uDest - uSour;
}

int _tmain(int argc, TCHAR* args[])
{
    INT val1 = 10;
    INT val2 = 20;

    // 두 포인터 사이의 거리를 구하고자 함.
    _tprintf(_T("Address: %p, %p\n"), (UINT_PTR)&val1, (UINT_PTR)&val2);
    _tprintf(_T("Distance between two address: %d\n"), CalculateDistance((UINT_PTR)&val1, (UINT_PTR)&val2));

    return 0;
}

💻 GetLastError 함수와 에러코드

함수 호출의 성공여부를 확인할 수 있다.
윈도우 시스템에서는 오류가 발생할 때마다 전역 공간에 오류의 이유를 저장해둔다. GetLastError()함수는 해당 전역 공간에 접근해서 저장된 값을 가져온다. 해당 값을 가지고 오류의 원인을 알 수 있다.

ex) GetLastError 함수 호출 예시 (존재하지 않는 파일을 찾을 경우)

int _tmain(int argc, TCHAR* args[])
{
    HANDLE hFile = CreateFile( // Windows system 함수
        _T("ABC.DAT"), GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (INVALID_HANDLE_VALUE == hFile) // INVALID_HANDLE_VALUE는 에러 발생 유무 체크 벨류
    {
        _tprintf(_T("Error code: %d\n"), GetLastError());
        // 에러가 발생했다면 이렇게 바로 호출해야 함.
        // 에러가 발생하고 다른 Windows System 함수를 호출하고 GetLastError()를 호출하면 바뀔수도 있음에 주의.
        return 1;
    }
    return 0;
}

0개의 댓글