[C++ 기초] 디버깅 기초, 입력 버퍼 기본 하드웨어 동작

라멘커비·2024년 1월 5일
0

CPP 입문

목록 보기
17/25
post-thumbnail

디버깅 기초

🌷# define 전처리문

여태 배운 전처리문은 '#include' 와 주석.
define은 코드치환 + 전처리 정의

코드치환 define

define문으로 코드를 간단하게 치환할 수 있다. 아무 것도 없는 것을 치환할 수도 있고, define된 것을 다시 치환할 수도 있다.

  • 사용 모습
#define TEN 10

int main(){
	int Value = TEN; // 10을 넣은 것과 같음
}
  • 진짜 말그대로 코드치환임 대박
    10;으로 치환되기 때문에 세미콜론;을 안 붙여도 오류가 나지 않는다.
#define TEN 10;

int main(){
	int Value = TEN
}

define 매크로 함수

#define PLUS(Value) Value + Value

int main()
{   
    int Value = PLUS(20);	// 40
}

권장되진 않는다.

매크로 함수 권장되지 않는 이유

너무 쌩치환임.

#define PLUS(Value) Value + Value
#define MUL(Value) Value * Value

int main()
{   
    int Value = MUL(PLUS(20));
}    

여기서 Value의 값은 1600이 아니다.

MUL(PLUS(20))
= MUL(20 + 20)
= 20 + 20 * 20 + 20
= 20 + 400 + 20

괄호가 없고 코드가 쌩으로 치환되는 것이기 때문에 440이 나온다.

🌷Assert

헤더 : assert.h
프로그램을 파괴할 수 있는 함수이다.
일부러 심각한 익셉션을 일으켜서 절대로 벌어지면 안 되는 사용법이 사용됐다는 것을 알려주는 것이다.

define으로 Assert 정리

위에 두 코드를 하나의 define으로 묶어 사용 가능하다.

#include<Windows.h>
#include<assert.h>

#define MsgAssert(TEXT) MessageBoxA(nullptr, TEXT, "치명적 에러", MB_OK); assert(false);  // 프로그램을 파괴할 수 있는 함수 Assert

int main()
{   
    MsgAssert("화면 크기가 0입니다.");
    return 0;
}

🌷호출 스택 보는 방법

F5를 눌렀을 때 중단점에 걸리거나 치명적인 오류가 났다면

디버그 탭 > 창 > 호출 스택 에서 호출 스택 창을 켤 수 있다.

  • 호출 스택
    위쪽이 가장 마지막에 실행된 함수이다.
    아래쪽으로 내려갈수록 그 이전에 실행된 함수들이 보이는데
    그때의 스택의 메모리 상황을 비주얼스튜디오가 모두 기록해 놓는다.

현재 호출 스택에서 뒤에 있는 스택을 선택함으로써 지금 에러가 난 부분이나 중단점이 걸린 부분 이전에 함수의 상태를 확인할 수 있다.

입력의 기본 하드웨어 동작

_getch()는 입력 버퍼에 키가 차있을 때까지 기다리는 함수이고, 입력되면 입력 버퍼에서 1개 빼서 리턴해주는 함수이다.
키를 여러 번 눌러서 버퍼에 쌓여있으면 입력을 기다리지 않고 하나씩 리턴된다. 비어있으면 기다리는거임.

그래서 실시간으로 입력받아 움직이는 게임을 만들때는 _getch()로 입력을 기다릴 필요가 없다.
(입력할 때까지 멈춰있는 게임은 없음)

키 입력할 때까지 기다리는 함수가 아닌
입력했는지 안했는지만 보는 함수가 필요하다.
_kbhit()

int InputCount = _kbhit();
if (InputCount == 0) {
	// 입력이 없었으면 바로 return;
	return;
}
// 입력이 있었으면 switch문으로 들어감
int Select = _getch();
switch (Select){
	...
}

과제

코드 이해하고 메모리맵 그려보기

🌷1차원 ScreenData

  • Galaga.cpp
#include <iostream>
#include <ConsoleEngine/ConsoleScreen.h>

int main()
{
    ConsoleScreen NewScreen = ConsoleScreen();
    NewScreen.CreateScreen(2, 2);
}
  • ConsoleScreen.h
#pragma once
#include "EngineDebug.h"
class ConsoleScreen
{
public:

	void CreateScreen(int _ScreenX, int _ScreenY);
protected:

private:
	int ScreenX = -1;	// 아직 화면이 만들어지지 않은 것을 표현하기 위해 음수
	int ScreenY = -1;
	char* ScreenData = nullptr;	// 내가 원하는 순간 생성을 할 수 있다
};
  • ConsoleScreen.cpp
#include "ConsoleScreen.h"
#include <iostream>
#include <Windows.h>
#include <assert.h>

void ConsoleScreen::CreateScreen(int _ScreenX, int _ScreenY) {
	if (_ScreenX <= 0) {
		MsgBoxAssert("스크린 X크기가 0이기 때문에 콘솔 스크린을 만들 수 없습니다.");
	}
	if (_ScreenY <= 0) {
		MsgBoxAssert("스크린 Y크기가 0이기 때문에 콘솔 스크린을 만들 수 없습니다.");
	}
	ScreenX = _ScreenX;
	ScreenY = _ScreenY;

	int ArrayCount = (ScreenX + 1) * ScreenY + 1;
	ScreenData = new char[ArrayCount] {};

	for (int y = 0; y < ScreenY; y++) {
		for (int x = 0; x < ScreenX; x++) {
			int Index = y * (ScreenX + 1) + x;
			ScreenData[Index] = '*';
		}
		
		int ReturnIndex = y * (ScreenX + 1) + ScreenX;
		ScreenData[ReturnIndex] = '\n';
	}

	printf_s(ScreenData);
}

ConsoleScreen 1차원 ScreenData 이해해보기

2 X 2 스크린일 때 출력하면
[*] [*] [\n]
[*] [*] [\n] [0] 가 되어야 함.

1차원이라 아래의 모양이 맞다.
[*] [*] [\n] [*] [*] [\n] [0]
  • ScreenData가 1차원 배열이기 때문에 개행문자 포함해서
    "맵의 행 개수 * (열 개수 + 1) + (0이들어갈자리)" 가 필요한 것이다.

  • 매번 Index = y * (ScreenX + 1) + x; 에 *을 넣는 이유는
    y행의 x자리에 별을 넣어야 하는데,
    1차원이라 행을 구분하려면 ScreenX크기만큼 곱해서 행을 알아내야 하기 때문이다.

  • int ReturnIndex = y * (ScreenX + 1) + ScreenX;는 한 행 끝날 때마다 개행문자를 넣어줘야 해서 마지막 x자리인 ScreenX를 더하는 것이다.

ScreenX = 2, ScreenY = 2
ArrayCount = (2 + 1) * 2 + 1

> y for문 0
y = 0, x = 0
	Index = 0 * (2 + 1) + 0 = 0
	[*] [0] [0] [0] [0] [0] [0]

	, x = 1
	Index = 0 * (2 + 1) + 1 = 1
	[*] [*] [0] [0] [0] [0] [0]

x 다 돌고 
	ReturnIndex = 0 * (2 + 1) + 2 = 2
	ReturnIndex에 \n 넣기
	[*] [*] [\n] [0] [0] [0] [0]

> y for문 1
y = 1, x = 0
	Index = 1 * (2 + 1) + 0 = 3
	[*] [*] [\n] [*] [0] [0] [0]
y = 1, x = 1
	Index = 1 * (2 + 1) + 1 = 4
	[*] [*] [\n] [*] [*] [0] [0]
x 다 돌고 
	ReturnIndex = 1 * (2 + 1) + 2 = 5
	ReturnIndex에 \n 넣기
	[*] [*] [\n] [*] [*] [\n] [0]

> for문 끝!
ScreenData[ArrayCount - 1] = 0으로 마지막 칸에 0을 넣어서 문자열의 끝을 나타냄

메모리맵

🌷2차원 ScreenData

1. char** ScreenData 생김
[0] [0]

2. for문에서 ScreenData[0] 생김
ScreenData[0]
[*] [*] [\n] [0]

3. for문에서 ScreenData[1] 생김
ScreenData[1]
[*] [*] [\n] [0]

→ ScreenData는 이런 느낌?
char ** ScreenData
[ [*] [*] [\n] [0] ]
[ [*] [*] [\n] [0] ]
  • ConsoleScreen.h
#pragma once
#include "EngineDebug.h"
class ConsoleScreen
{
public:

	void CreateScreen(int _ScreenX, int _ScreenY);
protected:

private:
	int ScreenX = -1;	// 아직 화면이 만들어지지 않은 것을 표현하기 위해 음수
	int ScreenY = -1;
	char** ScreenData= nullptr;	// 내가 원하는 순간 생성을 할 수 있다
};
  • ConsoleScreen.cpp
#include "ConsoleScreen.h"
#include <iostream>
#include <Windows.h>
#include <assert.h>

void ConsoleScreen::CreateScreen(int _ScreenX, int _ScreenY) {
	if (_ScreenX <= 0) {
		MsgBoxAssert("스크린 X크기가 0이기 때문에 콘솔 스크린을 만들 수 없습니다.");
	}
	if (_ScreenY <= 0) {
		MsgBoxAssert("스크린 Y크기가 0이기 때문에 콘솔 스크린을 만들 수 없습니다.");
	}
	ScreenX = _ScreenX;
	ScreenY = _ScreenY;

	ScreenData = new char* [ScreenY] {};

	for (int y = 0; y < ScreenY; y++) {
		ScreenData[y] = new char[ScreenX + 2] {0, };
		for (int x = 0; x < ScreenX; x++) {
			ScreenData[y][x] = '*';
		}
		ScreenData[y][ScreenX] = '\n';
	}

	for (int y = 0; y < ScreenY; y++) {
		printf_s(ScreenData[y]);
	}
}

메모리맵

profile
일단 시작해보자

0개의 댓글

관련 채용 정보