Rylah's Study & Daily Life

03. 64비트 기반 프로그래밍 본문

Study/System Programming

03. 64비트 기반 프로그래밍

Rylah 2022. 4. 4. 21:32

1. WIN32 vs WIN64

 - 하드웨어 입장에서의 32비트 vs 64비트

32비트와 64비트 구분 방법

- 한번에 수신/송신 가능한 데이터의 크기 (Input/Output Bus)

- 데이터 처리 능력 (CPU의 능력)

 

한번에 처리할 수 있는 데이터의 크기와 수신/송신 할 수 있는 데이터의 크기를 기준으로 32비트 시스템과 64비트 시스템이 결정 된다.

 

프로그래머 입장에서 32비트 vs 64비트

- 프로그램으로 표현할 수 있는 범위의 증가 

- 표현할 수 있는 메모리 전체의 크기

- 포인터의 크기가 32비트는 4바이트, 64비트는 8바이트이다.

- 포인터가 크면 클 수록 프로그래머에게 유리하다. -> 접근 가능한 자료의 값의 최대가 더 크다

- 32비트는 최대 4기가, 64비트는 16TB?

- Input/Output 버스와 CPU의 데이터 처리 능력이 모두 64비트여야 온전한 64비트 머신이라고 할 수 있다.

 

64비트 기반 프로그래밍

- 64비트 시스템을 고려한 프로그래밍은 자료형에 고려해야 된다.

 

 

2. 프로그램 구현 관점에서의 WIN32 vs WIN64

LLP64 vs LP64

- 32비트 시스템과의 호환성을 중시한 모델 

 

운영체제 모델 char short int long pointer
Windows LLP64 1 byte 2 byte 4 byte 4 byte 8 byte
UNIX LP64 1 byte 2 byte 4 byte 8 byte 8 byte

이 책에서는 이렇게 소개하고 있는데 컴파일러에 따라서 다르다고 보는 것이 더 정확할 것이다.

 

64비트와 32비트의 공존

64비트 시스템에서 문제가 될만한 코드를 소개한다고 한다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(void)
{
    int arr[10= { 0, };
    int arrVal = (int)arr;
    printf("pointer : %d\n", arrVal);
 
    return 0;
}
cs

이 코드는 왜 문제일까?

배열이 선언된 주소값을 출력하기 위한 arrVal 변수인데 32비트 시스템은 4바이트가 주소값이지만 64비트는 8바이트가 주소값이라서 4바이트를 담는 int로 표현하면 문제가 생길 수 있다. 

32비트 실행한 예시이다.

64비트로 실행한 예시이다.

 

- 64비트에서는 포인터가 지니고 있는 주소값을 4바이트 정수형으로 형변환 하지 말자. (애초에 요즘 Modern C++에서는 이런 형변환도 사용해서는 안된다.)

 

 

Windows 스타일 자료형

기본 자료형을 사용하는 것이 나쁜 것은 아니지만, Windows에서만 실행된다는 것을 가정하면 마이크로소프트에서 정의한 자료형을 사용하는 것도 하나의 방법이다.

아래 표는 마이크로소프트에서 제공하는 자료형이다.

Windows 자료형 의미 정의
BOOL Boolean variable typedef int BOOL
DWORD 32-bit unsigned Integer typedef unsigned long DWORD;
DWORD32 32 bit unsigned Integer typedef unsigned int DWORD32
DWORD64 64-bit unsigned Integer typedef unsigned __int64 DWORD64
INT 32-bit signed integer typedef int INT;
INT32 32-bit signed integer typedef signed int INT32
INT64 64-bit signed integer typedef signed __int64 INT64
LONG 32-bit signed integer typedef long LONG
LONG32 32-bit signed integer typedef signed int LONG32
LONG64 64-bit signed integer typedef signed __int64 LONG64
UINT Unsigned INT typedef unsigned int UINT
UINT32 Unsigned INT32 typedef unsigned int UINT32
UINT64 Unsigned INT64 typedef unsigned __int64 UINT64
ULONG Unsigned LONG typedef unsigned int ULONG
ULONG32 Unsigned LONG32 typedef unsigned int ULONG32
ULONG64 Unsigned LONG64 typedef unsigned __int64 ULONG64

굳이 외울 것은 아니지만 알고 생각하고 정의를 찾아보면 된다.

 

포인터에 대한 윈도우 자료형의 정의도 다음과 같다.

WINDOWS 자료형 의미 정의
PINT INT32에 대한 포인터 typedef int* PINT;
PINT32 INT32에 대한 포인터 typedef signed int* PINT32
PINT64 INT64에 대한 포인터 typedef signed __int64* PINT64
PLONG LONG에 대한 포인터 typedef LONG* PLONG
PLONG32 LONG32에 대한 포인터 typedef signed int* PLONG32
PLONG64 LONG64에 대한 포인터 typedef signed __int64* PLONG64
PUINT UINT에 대한 포인터 typedef unsigned int* PUINT;
PUINT32 UINT32에 대한 포인터 typedef unsigned int* PUINT32;
PUINT64 UINT64에 대한 포인터 typedef unsigned __int64* PUINT64;
PULONG ULONG에 대한 포인터 typedef ULONG* PULONG
PULONG32 ULONG32에 대한 포인터 typedef unsigned int* PULONG32
PULONG64 ULONG64에 대한 포인터 typedef unsigned __int64* PULONG64

 

Windows 스타일 자료형 확인하기

책에서는 우클릭에 Go to definition이라고 하지만 우리가 보통 하는 것은 Ctrl 클릭 혹은 f12이다.

아마 책이 쓰일 때 비쥬얼 스튜디오 버전은 잘해야 2008 ~ 2010이었기에 이런 차이가 발생한 것 같다.

 

Polymorphic 자료형

- 마이크로소프트에서는 WIN64 기반으로 넘어가면서 Polymorphic 자료형을 정의하고 있다. 보통 C++와 같이 객체 지향 언어에서 처음 접하게 되는 이 Polymorphic 단어는 "다양한 모습이 있는" 혹은 "다형적"이라고 해석 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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_PTR;
#endif
cs

예제 코드로 한번 살펴보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
 
UINT CalDistance(UINT a, UINT b)
{
    return a - b;
}
 
int _tmain(void)
{
    INT val1 = 10;
    INT val2 = 20;
 
    _tprintf(_T("Position %u, %u \n"), (UINT)&val1, (UINT)&val2);
    _tprintf(_T("Distance %u \n"), 
        CalDistance((UINT)&val1, (UINT)&val2)
    );
 
    return 0;
 
}
cs

64비트와 32비트의 차이는 이렇게 나타난다.

그래서 64비트에서는 함수 형태가 UINT64로 반환형이 되어야 한다.

이걸 상호호환성을 높이려면 다음과 같은 작업이 필요하다.

1
2
3
4
5
6
7
8
#if defined(_WIN64)
UINT64 CalDistance(UINT64 a, UINT64 b)
#else
UINT CalDistance(UINT a, UINT b)
#endif
{
    return a - b;
}
cs

하지만 우리는 매 코드를 짤 때 마다 if else endif와 같은 전처리기를 설정하기는 귀찮을 것이다.

 

그래서 이런 자료형으로 선언하여 호환성을 높인다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
 
UINT_PTR CalDistance(UINT_PTR a, UINT_PTR b)
{
    return a - b;
}
 
int _tmain(void)
{
    INT32 val1 = 10;
    INT32 val2 = 20;
 
    _tprintf(_T("distance : %d \n"),
        CalDistance((UINT_PTR)&val1, (UINT_PTR)&val2)
    );
 
    return 0;
}
cs

결과는 컴파일러 따라 바이트가 달라서 다르게 출력되지만 나는 -4가 나왔다.

 

3. 오류 확인

GetLastError 함수와 에러 코드

- Windows 시스템 함수를 호출 하는 과정에서 오류가 발생하면, GetLastError 함수 호출을 통해 오류를 확인 할 수 있다.

1
DWORD GetLastError(void);
cs

https://docs.microsoft.com/ko-kr/windows/win32/debug/system-error-codes

 

자습서 - 시스템 오류 코드 디버그 - Win32 apps

WinError.h 헤더 파일에 정의된 시스템 오류 코드에 대한 링크를 & 시스템 오류 코드 디버깅에 대한 지침을 제공합니다.

docs.microsoft.com

이 링크에서 확인하는 것이 훨씬 나을 것이다.

 

예제를 통해 확인해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
 
int _tmain(void)
{
    HANDLE hFile = CreateFile(
        _T("ABC.DAT"), GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
        );
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        _tprintf(_T("error code : %d \n"), GetLastError());
        return 0;
    }
 
    return 0;
}
cs

하나만 봐서는 감이 오지 않으니 하나 더 보자

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
 
int _tmain(void)
{
    HANDLE hFile = CreateFile(
        _T("ABC.DAT"), GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    );
 
    _tprintf(_T("error code : %d\n"), GetLastError()); 
 
    hFile = CreateFile(
        _T("ABC2.DAT"), GENERIC_WRITE, FILE_SHARE_READ,
        NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
    );
 
    _tprintf(_T("error code : %d\n"), GetLastError());
    return 0;
}
cs

 

에러 코드 0번은 오류가 발생하지 않았다는 뜻이다.

그런데 여기서 한번 더 실행하면 에러코드 80을 뱉는다.

ABC2.DAT이 이미 생성되서 호출에 실패한 것이다.

 

4. System Programming Project Design

명령 프롬프트 프로젝트 제안

여기서 명령프롬프트에서 help를 입력하면 다음과 같이 나온다.

더보기

특정 명령어에 대한 자세한 내용이 필요하면 HELP 명령어 이름을 입력하십시오.
ASSOC    파일 확장명 연결을 보여주거나 수정합니다.
ATTRIB   파일 속성을 표시하거나 바꿉니다.
BREAK    확장된 CTRL+C 검사를 설정하거나 지웁니다.
BCDEDIT        부팅 로딩을 제어하기 위해 부팅 데이터베이스에서 속성을 설정합니다.
CACLS    파일의 액세스 컨트롤 목록(ACL)을 표시하거나 수정합니다.
CALL     한 일괄 프로그램에서 다른 일괄 프로그램을 호출합니다.
CD       현재 디렉터리 이름을 보여주거나 바꿉니다.
CHCP     활성화된 코드 페이지의 번호를 표시하거나 설정합니다.
CHDIR    현재 디렉터리 이름을 보여주거나 바꿉니다.
CHKDSK   디스크를 검사하고 상태 보고서를 표시합니다.
CHKNTFS  부팅하는 동안 디스크 확인을 화면에 표시하거나 변경합니다.
CLS      화면을 지웁니다.
CMD      Windows 명령 인터프리터의 새 인스턴스를 시작합니다.
COLOR    콘솔의 기본색과 배경색을 설정합니다.
COMP     두 개 또는 여러 개의 파일을 비교합니다.
COMPACT  NTFS 분할 영역에 있는 파일의 압축을 표시하거나 변경합니다.
CONVERT  FAT 볼륨을 NTFS로 변환합니다. 현재 드라이브는
         변환할 수 없습니다.
COPY     하나 이상의 파일을 다른 위치로 복사합니다.
DATE     날짜를 보여주거나 설정합니다.
DEL      하나 이상의 파일을 지웁니다.
DIR      디렉터리에 있는 파일과 하위 디렉터리 목록을 보여줍니다.
DISKPART       디스크 파티션 속성을 표시하거나 구성합니다.
DOSKEY       명령줄을 편집하고, Windows 명령을 다시 호출하고,
               매크로를 만듭니다.
DRIVERQUERY    현재 장치 드라이버 상태와 속성을 표시합니다.
ECHO           메시지를 표시하거나 ECHO를 켜거나 끕니다.
ENDLOCAL       배치 파일에서 환경 변경의 지역화를 끝냅니다.
ERASE          하나 이상의 파일을 지웁니다.
EXIT           CMD.EXE 프로그램(명령 인터프리터)을 종료합니다.
FC             두 파일 또는 파일 집합을 비교하여 다른 점을
         표시합니다.
FIND           파일에서 텍스트 문자열을 검색합니다.
FINDSTR        파일에서 문자열을 검색합니다.
FOR            파일 집합의 각 파일에 대해 지정된 명령을 실행합니다.
FORMAT         Windows에서 사용할 디스크를 포맷합니다.
FSUTIL         파일 시스템 속성을 표시하거나 구성합니다.
FTYPE          파일 확장명 연결에 사용되는 파일 형식을 표시하거나
               수정합니다.
GOTO           Windows 명령 인터프리터가 일괄 프로그램에서
               이름표가 붙여진 줄로 이동합니다.
GPRESULT       컴퓨터 또는 사용자에 대한 그룹 정책 정보를 표시합니다.
GRAFTABL       Windows가 그래픽 모드에서 확장 문자 세트를 표시할
         수 있게 합니다.
HELP           Windows 명령에 대한 도움말 정보를 제공합니다.
ICACLS         파일과 디렉터리에 대한 ACL을 표시, 수정, 백업 또는
               복원합니다.
IF             일괄 프로그램에서 조건 처리를 수행합니다.
LABEL          디스크의 볼륨 이름을 만들거나, 바꾸거나, 지웁니다.
MD             디렉터리를 만듭니다.
MKDIR          디렉터리를 만듭니다.
MKLINK         바로 가기 링크와 하드 링크를 만듭니다.
MODE           시스템 장치를 구성합니다.
MORE           출력을 한번에 한 화면씩 표시합니다.
MOVE           하나 이상의 파일을 한 디렉터리에서 다른 디렉터리로
               이동합니다.
OPENFILES      파일 공유에서 원격 사용자에 의해 열린 파일을 표시합니다.
PATH           실행 파일의 찾기 경로를 표시하거나 설정합니다.
PAUSE          배치 파일의 처리를 일시 중단하고 메시지를 표시합니다.
POPD           PUSHD에 의해 저장된 현재 디렉터리의 이전 값을
               복원합니다.
PRINT          텍스트 파일을 인쇄합니다.
PROMPT         Windows 명령 프롬프트를 변경합니다.
PUSHD          현재 디렉터리를 저장한 다음 변경합니다.
RD             디렉터리를 제거합니다.
RECOVER        불량이거나 결함이 있는 디스크에서 읽을 수 있는 정보를 복구합니다.
REM            배치 파일 또는 CONFIG.SYS에 주석을 기록합니다.
REN            파일 이름을 바꿉니다.
RENAME         파일 이름을 바꿉니다.
REPLACE        파일을 바꿉니다.
RMDIR          디렉터리를 제거합니다.
ROBOCOPY       파일과 디렉터리 트리를 복사할 수 있는 고급 유틸리티입니다.
SET            Windows 환경 변수를 표시, 설정 또는 제거합니다.
SETLOCAL       배치 파일에서 환경 변경의 지역화를 시작합니다.
SC             서비스(백그라운드 프로세스)를 표시하거나 구성합니다.
SCHTASKS       컴퓨터에서 실행할 명령과 프로그램을 예약합니다.
SHIFT          배치 파일에서 바꿀 수 있는 매개 변수의 위치를 바꿉니다.
SHUTDOWN       컴퓨터의 로컬 또는 원격 종료를 허용합니다.
SORT           입력을 정렬합니다.
START          지정한 프로그램이나 명령을 실행할 별도의 창을 시작합니다.
SUBST          경로를 드라이브 문자에 연결합니다.
SYSTEMINFO     컴퓨터별 속성과 구성을 표시합니다.
TASKLIST       서비스를 포함하여 현재 실행 중인 모든 작업을 표시합니다.
TASKKILL       실행 중인 프로세스나 응용 프로그램을 중단합니다.
TIME           시스템 시간을 표시하거나 설정합니다.
TITLE          CMD.EXE 세션에 대한 창 제목을 설정합니다.
TREE           드라이브 또는 경로의 디렉터리 구조를 그래픽으로
               표시합니다.
TYPE           텍스트 파일의 내용을 표시합니다.
VER            Windows 버전을 표시합니다.
VERIFY         파일이 디스크에 올바로 기록되었는지 검증할지
         여부를 지정합니다.
VOL            디스크 볼륨 레이블과 일련 번호를 표시합니다.
XCOPY          파일과 디렉터리 트리를 복사합니다.
WMIC           대화형 명령 셸 내의 WMI 정보를 표시합니다.

일단 이걸 구현하나하나 해가는게 프로젝트인데, 골격 코드는 다음과 같다.

 

어우 최신버전에 맞추려고하니 수정할게 꽤 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <locale.h>
#include <Windows.h>
 
#define STR_LEN            256
#define CMD_TOKEN_NUM    10
#define _CRT_SECURE_NO_WARNINGS
 
TCHAR ERROR_CMD[] = _T("'%s'은(는) 실행할 수 있는 프로그램이 아닙니다! \n");
 
int CmdProcessing(void);
TCHAR* StrLower(TCHAR*);
 
int main(int argc, TCHAR* argv[])
{
    // Korean Support
    _tsetlocale(LC_ALL, _T("Korean"));
 
    DWORD isExit;
    while (true)
    {
        isExit = CmdProcessing();
        if (isExit == TRUE)
        {
            _fputts(_T("명령어 처리를 종료합니다. \n"), stdout);
            break;
        }
 
    }
    return 0;
}
 
TCHAR cmdString[STR_LEN];
TCHAR cmdTokenList[CMD_TOKEN_NUM][STR_LEN];
TCHAR seps[] = _T(" ,\t\n");
// TChar int CmdProcessing(void)
// 명령어를 입력 받아서 해당 명령어에 지정되어 있는 기능을 수행한다.
// Exit가 입력 되면 True를 반환하고 이는 프로그램 종료로 이어진다.
 
int CmdProcessing(void)
{
    _fputts(_T("Best Commend prompt >> "), stdout);
    _getts_s(cmdString);
 
    TCHAR* token = _tcstok(cmdString, seps);
    int tokenNum = 0;
 
    while (token != NULL)
    {
        _tcscpy(cmdTokenList[tokenNum++], StrLower(token));
        token = _tcstok(NULL, seps);
 
    }
 
    if (!_tcscmp(cmdTokenList[0], _T("exit")))
    {
        return TRUE;
    }
 
    else if (!_tcscmp(cmdTokenList[0], _T("추가되는 명령어 1")))
    {
 
    }
    else if (!_tcscmp(cmdTokenList[0], _T("추가되는 명령어 2")))
    {
 
    }
    else
    {
        _tprintf(ERROR_CMD, cmdTokenList[0]);
    }
 
    return 0;
 
 
}
cs

이렇게 수정하면 돌아간다.

 

 

정리

1. 64비트 시스템과 32비트 시스템의 구조적 차이

 - 64비트 시스템과 32비트 시스템을 구분 짓는 기준 두가지

 ⓐ 한번에 송/수신 할 수 있는 데이터의 크기

 ⓑ 한번에 처리할 수 있는 데이터의 크기

 - 64비트 시스템에서는 한 번에 64비트 데이터를 전송 및 처리할 수 있으며, 32비트 시스템에서는 한 번에 32비트 데이터를 전송 처리 할 수 있다.

 

2. 주소 값 표현에 사용되는 바이트 수가 지니는 의미

- 프로그래머 관점에서 64비트 시스템은 주소를 표현하는 데 64비트를 활용한다는 데 초점이 맞춰진다. 

- 64비트 시스템은 한번에 처리 할 수 있는 데이터가 64비트이므로, 주소값을 표현하는 데도 64비트를 사용한다.

 (포인터가 8바이트라는 이야기)

 

3. Polymorphic 자료형

- Polymorphic 자료형의 필요성은 예제를 통해서 알아보는게 좋다. 위의 예제를 참고하자.

 

4. LLP64와 LP64

 LLP64와 LP64가 무엇인지 확인하자. 기존 32비트 시스템의 자료형과 비교해서 일치하는게 무엇인지 일치하지 않는게 무엇인지 확인하자.

 - long (4바이트 vs 8바이트) / windows vs UNIX

 

5. GetLastError 함수

 - 가장 많이 호출 하는 함수 중 하나이다. 

 

6. 명령 프롬프트 프로젝트 시작

 

 

'Study > System Programming' 카테고리의 다른 글

04. 컴퓨터 구조 (2)  (0) 2022.04.05
02. 아스키 코드 vs 유니코드  (0) 2022.04.04
01. 시스템 프로그래밍 시작  (0) 2022.04.03