시작 전 소개

뇌를 자극하는 윈도우즈 프로그래밍
1챕터 정리본 바로가기
2챕터 정리본 바로가기

주소값에 대해 잘 모른다면

위 부분만 따로 공부 시 참조한 링크 등이 아니라 하나의 하이퍼링크로 다룬 이유는 설명이 너무 잘되어있다.
또한 이전에 내가 공부를 하며 놓친 부분이 무엇이었는지, 왜 내가 어떤 개념들을 이해하지 못했는지에 대해 스스로 알게해주는 글이어서 따로 빼놓았다.
포인터나 메모리를 공부할 때 나처럼 어려움을 겪었거나 정확히 어딘지는 모르지만 구멍이 존재한다고 느끼는 사람이면 이 글을 먼저 보고 3장을 읽었을 때 책이 술술 읽힐 것이다.

WIN32 VS WIN64

64비트와 32비트

🟢CPU가 버스를 통해서 한번에 전송 및 수신할 수 있는 데이터의 크기에 따라서 나눈다.
🟢데이터 처리의 능력 역시 나누는 기준에 포함된다.

한번에 송수신 할 수 있는 데이터 크기와 한번에 처리 가능한 데이터 크기를 기준으로
32비트 컴퓨터와 64비트 컴퓨터를 구분짓는다.

프로그래머의 시점에서 보는 비트

메모리 공간이 충분할 대에는 주소값의 범위가 넓은만큼 더 넓은 메모리 공간 활용이 가능하다.
예를 들어 메모리가 1GB인데, 주소값표현에는 4비트만 사용된다면
2의 4승에 해당하는 16바이트 이외에 부분은 활용하지 않게되고, 이는 상당히 비효율적이다.
그러나 주소값을 무조건 크게 잡는 것이 좋은 건 아니다.
32비트의 처리 능력을 가진 컴퓨터에 64비트만큼의 주소값을 할당한다면
ALU에 32비트씩 쪼개서 두번의 전송을 하고, 연산 역시 32비트씩 두번 진행해야한다.

주소값에 대한 이해

왜 주소값 표현에 4비트만 사용하면 16바이트가 사용될까?
간단하게 설명하면 4개의 칸에 0과 1로만 이루어진 주소값을 집어넣는다.
그러면 1층에는 0000, 2층에는 0001이런식으로 구성될 것이다.
이렇게 모든 경우를 계산하면 16개의 층이 생기기 때문이다.

구현하는 입장에서 본 비트

우선 윈도우는 64비트 기반의 운영체제지만, 32비트와의 호환성을 중시한다.
때문에 이에 맞춘 프로그래밍 스타일이 있으며 이것을 따르는 것이 바로 64비트 기반 프로그래밍 스타일의 기본이다.

윈도우:LLP64라는 모데이터 포현 모델 따름
유닉스:LP64라는 데이터 모델을 취하는데, 윈도우와 달리 LONG을 8비트로 표현한다.

주소값과 int

32비트 시스템에서는 주소값을 int로 형변환을 해도 문제는 되지 않지만
64비트 시스템에서는 4바이트 정수형 데이터로 형 변환을 해서는 안된다.

그런데 생각을 해보면 이상한다.
저자는 4바이트 정수형 데이터로 형 변환을 하지 말라고 하면서 long도 포함하여 설명한다.
(아래의 내용은 코드의 이식성과 관계 있는 내용이다)
그 이유는 unix에서는 8바이트형인 long이 window에서는 4바이트이기 때문이다.
만약 unix에서 포인터를 부득이하게 형변환해야한다면
intptr_t또는 uniptr_t를 사용한는 것이 좋다.

윈도우 스타일의 자료형

-32비트 형태의 자료형은 시스템에 상관없이 32비트로 표현되고
-64비트 형태에서만 사용가능한 자료형은 시스템에 상관없이 64비트로 표현된다.
따라서 윈도우의 철학 중 하나인 호환성 중시를 위해서는
아주 큰 비트를 필요로 하지 않는 상황에서는 -32비트 형태의 자료형을 사용하는 것이 유익할 수도 있다.

다형적 자료형

아마 기존에 객체지향 언어를 다루어본 사람이라면 다형성이라는 단어가 친숙할 것이다.
하나의 대상이 다양한 의미를 지닌다는 말이 모호하게 느껴진다면
(내 기준으로는)코틀린에서 접한 interface를 떠올리면 어떤 느낌인지 감이 올 것이다.

위에서 말한 예시를 들자면, 매크로 함수를 사용하여
유니코드로 표현된 부분을 아스키코드로 바꾼다거나,
64비트로 표현된 포인터를 형변환하는 작업 등을 떠올리면 된다.

UNIT 64, UNIT 등은 상황에 따라 다른 모습을 가지게 되기 때문에 다형적 자료형에 속한다.

오류 확인하기

GetLastError함수를 사용하면 된다.
그러나 이 함수의 경우 윈도우 시스템 함수가 호출될 때마다 갱신되기 때문에
에러가 발생했다면 그 즉시 오류확인을 해야하만한다.

예제코드

참고로 나는 자꾸 scanf를 쓸 때처럼 에러가 떠서 _tcstok_s을 사용했고, 이 때문에 NextToken도 함께 사용하였다.
미리 말하자면 책 대로 해도 잘만 돌아가고 그냥 내가 초반에 혼자 작성하다 다른 부분에서 실수한 것 때문에 코드가 오류가 떴던 것이었다.
그렇지만, 보안강화에 도움이 된다고하니 앞으로는 _tcstok_s를 더 자주 쓸 것 같아서 책에 나온 예제가 작동하는 것을 확인한 후에도
따로 수정은 하지 않았다.

#include <windows.h>
#include <locale.h>
#include <tchar.h>

#define STR_LEN 256
#define CMD_TOKEN_NUM 10

_TCHAR ERROR_CMD[] = _T("%s는 실행할 수 있는 프로그램이 아닙니다. 다시 확인해주세요\n");

_TCHAR cmdString[STR_LEN];
_TCHAR TokenList[CMD_TOKEN_NUM][STR_LEN];
TCHAR seps[] = _T(" ,\t\n");

TCHAR* StrLower(TCHAR* PTR) {
	TCHAR* ret = PTR;
	while (*PTR) {
		if (!_istupper(*PTR)) {
			*PTR = _istlower(*PTR);
			PTR++;
		}
	}
	return ret;
}

int Processing(void) {
	int tokenNum = 0;
	_TCHAR* next_token = 0;

	_fputts(_T("명령>> \n"), stdout);
	_getts_s(cmdString, STR_LEN);

	_tsetlocale(LC_ALL, _T(".UTF-8"));

	_TCHAR* token = _tcstok_s(cmdString, seps, &next_token);
	while (token != NULL) {
		_tcscpy_s(TokenList[tokenNum++], STR_LEN, StrLower(token));
		token = _tcstok_s(NULL, seps, &next_token);
	}

	if (!_tcscmp(TokenList[0], _T("exit"))) {
		return TRUE;
	}
	else if (!_tcscmp(TokenList[0], _T("추가 되는 명령어 1")))
	{
	}
	else if (!_tcscmp(TokenList[0], _T("추가 되는 명령어 2")))
	{
	}
	else
	{
		_tprintf(ERROR_CMD, TokenList[0]);
	}

	return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
	_tsetlocale(LC_ALL, _T(".UTF-8"));
	DWORD32 is_Exit;

	while (1) {
		is_Exit = Processing();
		if (is_Exit == TRUE) {
			_fputts(_T("처리를 종료합니다.\n"), stdout);
			break;
		}
	}

	return 0;
}


댓글남기기