본문 바로가기

C++

c++ 10 (C++ 의 컴파일에 관하여 - LNK 오류에 대처하기)

소스파일 - 오브젝트 파일

소스 파일은 컴파일이 될 때 소스파일 하나하나가 

오브젝트 파일을 생성하게 된다.

(.cpp -> .obj)

맥이나 리눅스의 경우 .o 파일이 된다.

c++의 컴파일은 이렇게 만들어진 obj 파일을 묶어서

하나의 실행파일을 만들어내게 된다.

 

obj파일은 cpp파일이 컴파일 되면서 나오는 중간 파일

그래서 obj파일에 문제가 있다면 cpp파일에 문제가 있다는 거

그런데 이것을 헤더파일에서 찾으려고 하면 안된다.

왜냐하면 헤더파일에 대한 에러는 절대 나오지 않는다.

c++는 cpp파일을 기준으로 컴파일을 하기 때문

 

cpp 파일이 모든 include를 다 한 다음에 그 cpp파일을 기준으로 obj 파일을 만들어내게 된다

이 각각의 헤더파일들을 include 한 cpp파일에서 obj 파일들이 만들어낸다.

 

obj 파일 이라는 것은 cpp파일과 헤더파일을 include로 다 합친 그것을

실제로 실행 파일로 만들기 직전에 만드는 실행 파일, 중간 단계의 파일

 

우리는 이렇게 include되어 있고 그렇게 만들어진 obj 파일들을 이용해 한 번에 실행 파일을 만들어내게 된다.

obj 파일들이 한꺼번에 뭉쳐져서 실제로 exe 파일이 만들어지게 된다.

 

Header.h
Display.h
GameEngine.h
TetrisHelper.h Header.h
Display.h
Header.h
GameEngine.h
Source.obj TetrisHelper.obj Display.obj GameEngine.obj

 

이 때 중요한 것은 이 헤더파일들에 선언되어 있는 변수, 함수들을 obj 파일들을 만드는 과정에서 역으로 가져온다.

함수의 선언부이 있는데 이 실제 함수의 몸체는 .cpp 파일에 지금 없다.

그럼 언제 선언부와 몸체가 합쳐지냐면

obj 파일이 전부 다 만들어 지고 나서 그 obj 파일이 한 번에 exe 파일이 만들어지는 순간

함수들을 전부 다 선언해서 다른 obj 파일을 검사하면서 함수의 원형들을 하나하나 찾아 참조하고 연결한다

그 연결하는 과정이 링크(Link) 

 

 

동일한 이름을 활용한 변수를 만들었을 때 에러가 난다는 것을 알 수 있다.

변수가 만약에 진짜 완전한 선언이라면 동일하게 두 개가 있어도 절대로 에러가 나지않아야한다.

선언 (declaration)은 지금 이것을 어디 있는지는 모르겠는데 아무튼 있어라고 말하는게 선언이고 여러 번 돼도 상관 없다

 

함수의 정의 (definition)인 몸체는 꼭 하나가 있어야 됨

대신 변수는 선언이라고 하는 게 편해서 그런지 몰라도 변수를 선언한다고 하는데

정확한 것은 선언과 정의가 동시에 들어간다.

 

<Source Files>

Source.cpp

#include "Headers.hpp"
#include "Displays.hpp"
#include "GameEngine.hpp"


#define INTERVAL 1.0/60.0


int displayData[GRID_HEIGHT][GRID_HEIGHT] = { 0, };


int main() {
    srand(time(0));
    
    float prev = (float)clock() / CLOCKS_PER_SEC;
    // clock이라는 함수가 프로그램이 시작된 순간부터 프로그램에 clock을 계속 구하고 있다.
    // CLOCKS_PER_SEC은 보통 1000이기 때문에 나누게 되면 프로그램이 시작된 순간부터 몇 초가 지났는지 나오게 된다.
    Display* display = new Display();
    GameEngine* gameEngine = new GameEngine();
    gameEngine->init();
    
    showConsoleCursor(false); // 커서 숨기기
    
    while(true) { // 프로그램이 정확하게 게임루프를 구현하고 있다는 것을 알 수 있다.
        float curr = (float)clock() / CLOCKS_PER_SEC;
        float dt = curr - prev; // 현재 시간과 이전에 만든 시간을 빼준다
        
        if (dt < INTERVAL) continue; // 정해준 INTERVAL(1/60초) 보다 작으면 while 루프 처음으로 돌아감
        prev = curr; // 크다면 while루프를 지속적으로 진행해서 prev를 curr로 바꿔준다.
        
        //printf("%.3f", curr); // curr을 출력 1/60초마다 printf 구문이 실행되고 있다. 테스트
        
        bool left = keyState('a');
        bool right = keyState('d');
        bool down = keyState('s');
        bool rotate = keyState('w');
        
        if (left) {
            // 왼쪽으로 블록 이동
            gameEngine->next(dt, 'a');
        }
        else if (right) {
            gameEngine->next(dt, 'd');
        }
        else if (down) {
            gameEngine->next(dt, 's');
        }
        else if (rotate) {
            gameEngine->rotate();
        }
        else {
            gameEngine->next(dt, 0); // 그냥 블록 떨어지게
        }
        
        
        
        // 화면 출력
        gameEngine->makeDisplayData();
        display->draw();
        
        // 게임 상태 판별
        if (gameEngine->state == GameEngine::GameState::GAMEOVER){
            break; // GAMEOVER라면 break로 무한루프 탈출하고 게임 종료
        }
        
    }
    
    return 0;
}

 

Displays.cpp

#include "Displays.hpp"


void Display::draw() {
    for (int i = 0; i < GRID_HEIGHT; i++){
        for (int k = 0; k < GRID_WIDTH; k++){
            
            //drawPosition(k, i, displayData[i][k] == 1);
            if (displayData[i][k] == 0) {
                drawPosition(k, i, false);
            }
            else {
                drawPosition(k, i, true);
            }
        }
    }
}

 

GameEngine.cpp

#include "GameEngine.hpp"


void GameEngine::init() {
    // 최초의 게임 엔진을 초기화 하는 과정을 맡아 합니다.
    makeUserBlock();
}




void GameEngine::next(float dt, char keyboardInput) {
    //blockY++; // 테트리스는 블록이 계속 떨어지니 blockY 값이 1씩 늘어난다.
    if (state == GameState::GAMEOVER) return; // GAMEOVER이라면 next 더이상 실행하지않음
    
    elapsed += dt; // elapsed에 dt를 더해서 넣는다.
    if (elapsed >= 0.5f) {
        if (canGoDown()) {
            blockY++;
        }
        else {
            // userblock을 gameGridData에 전사하면 된다.
            trans();
            if (gameOverDecision()) state == GameState::GAMEOVER;
            // gameOverDecision을 내리고 이게 true라면 GameState를 GAMEOVER로
        }
        elapsed -= 0.5f; // // elapsed에 dt를 빼서 넣는다.
    }
    
    controlCheck = controlCheck + dt;
    
    if (keyboardInput == 'a' && canGoLeft() && controlCheck > 0.1) {
        blockX--;
        controlCheck = 0.0f;
    }
    if (keyboardInput == 'd' && canGoRight() && controlCheck > 0.1) {
        blockX++;
        controlCheck = 0.0f;
    }
    if (keyboardInput == 's' && canGoDown() && controlCheck > 0.1) {
        blockY++; // blockY++를 한 번 더 빠르게 해준다
        controlCheck = 0.0f;
    }
}




bool GameEngine::canGoDown() {
    for (int i = 0; i < USERBLOCK_SIZE; i++){
        for (int k = 0; k < USERBLOCK_SIZE; k++){
            if (userBlock[i][k] == 1 && i + blockY + 1 >= GRID_HEIGHT)
                // 채워진 위치의 아래쪽 위치가 높이를 벗어났으면 더이상 이동할 수 없다.
                return false;
            if (userBlock[i][k] == 1 && gameGridData[i + blockY + 1][k + blockX] == 1)
                return false;
            
        }
    }
    return true;
}




bool GameEngine::canGoLeft() {
    for (int i = 0; i < USERBLOCK_SIZE; i++){
        for (int k = 0; k < USERBLOCK_SIZE; k++) {
            if (userBlock[i][k] == 1 && k + blockX - 1 < 0){
                return false;
            }
            
            if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX - 1] == 1){
                return false;
            }
        }
    }
    return true;
}




bool GameEngine::canGoRight() {
    for (int i = 0; i < USERBLOCK_SIZE; i++) {
        for (int k = 0; k < USERBLOCK_SIZE; k++){
            if (userBlock[i][k] == 1 && k + blockX + 1 > GRID_WIDTH - 1) {
                return false;
            }
            if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX + 1]){
                return false;
            }
        }
    }
    return true;
}




bool GameEngine::isLineFilled(int y) {
    for (int i = 0; i < GRID_WIDTH; i++) {
        if (gameGridData[y][i] == 0) return false; // 한 칸이라도 0이 되어있으면 안된다
    }
    return true;
}




void GameEngine::eraseLine(int y) {
    for (int i = 0; i < GRID_WIDTH; i++) {
        gameGridData[y][i] = 0;
    }
}




void GameEngine::drop(int y) {
    for (int i = y; i >= 0; i--) {
        for (int k = 0; k < GRID_WIDTH; k++) {
            gameGridData[i][k] = i - 1 < 0 ? 0 : gameGridData[i - 1][k];
            // i-1이 배열 범위를 벗어나기 전에 핸들링 해줘야한다. 쓰레기값 있으니까
            // i-1이 0보다 작다면 0이 아니라면 배열번호를 넘어간 것, 배열번호를 넘어갔다면 0으로 drop 그게 아니라면 위쪽에 있는 값으로 drop
        }
    }
}




void GameEngine::trans() {
    // 현재 데이터를 gameGridData에 전사를 해야됨
    for (int i = 0; i < USERBLOCK_SIZE; i++) {
        for (int k = 0; k < USERBLOCK_SIZE; k++) {
            // makeDisplayData 처럼 최적화가 가능
            gameGridData[i + blockY][k + blockX] = userBlock[i][k] == 1? userBlock[i][k] : gameGridData[i + blockY][k + blockX];
        }
    }
    
    // TODO: 한 줄이 가득 차 있는지 확인
    for (int i = 0; i < GRID_HEIGHT; i++) {
        if (isLineFilled(i)) {
            eraseLine(i); // i번째 칸의 라인이 채워져 있으면 eraseLine
            drop(i);
        }
    }
    
    // 새로운 블록 생성
    makeUserBlock();
}




bool GameEngine::gameOverDecision() {
    for (int i = 0; i < USERBLOCK_SIZE; i++) {
        for (int k = 0; k < USERBLOCK_SIZE; k++) {
            if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX] == 1) {
                return true;
            }
        }
    }
    return false;
}




void GameEngine::makeUserBlock() {
    blockX = GRID_WIDTH / 2 - USERBLOCK_SIZE / 2; // 정중앙에 위치
    blockY = 0;
    
    int various = rand() % 3; // various에 3가지 중 랜덤 값을 가져오기
    for(int i = 0; i < USERBLOCK_SIZE; i++) {
        for(int k = 0; k < USERBLOCK_SIZE; k++){
            userBlock[i][k] = userBlockVarious[various][i][k]; // 랜덤한 배열의 i,k번째를 한꺼번에 for 루프로 복제
        }
    }
    // TODO: 랜덤을 통해서 새로운 블록을 만든다.
}


void GameEngine::rotate() {
    // TODO: 회전을 구현해 보세요.
}




void GameEngine::makeDisplayData() {
    // gameGridData와 userBlock의 위치로 만들어지게 된다.
    for (int i = 0; i < GRID_HEIGHT; i++){
        for (int k = 0; k < GRID_WIDTH; k++){
            displayData[i][k] = gameGridData[i][k]; // gameGridData만 넣어 출력
        }
    }
    
    for (int i = 0; i < USERBLOCK_SIZE; i++){ // i가 y축 줄 수를 의미
        for (int k = 0; k < USERBLOCK_SIZE; k++){
            // userBlock의 위치를 계산해서 넣어주기
            if (i + blockY < 0 || i + blockY > GRID_HEIGHT) {
                // DO NOTHING
            }
            else if (k + blockX < 0 || k + blockX > GRID_WIDTH) {
                // DO NOTHING
            }
            else {
                // TODO:
                // 동일한 역할을 하는 코드
                //displayData[i + blockY][k + blockX] = userBlock[i][k] == 1 ? userBlock[i][k] : displayData[i + blockY][k + blockX];
                int _x = k + blockX;
                int _y = i + blockY;
                displayData[_y][_x] = userBlock[i][k] | displayData[_y][_x];
            }
        }
    }
}

 

TetrisHelper.cpp

#include "TetrisHelper.hpp"




bool consoleInitialized = false;
int lastX = 0;
int lastY = 0;






void putStringOnPosition(int x, int y, const char* content) {
    char buff[100] = { 0, };
    sprintf(buff, "\033[%d;%dH", y, x);
    printf("%s", buff);
    printf("%s", content);
    lastX = x;
    lastY = y;
}


void showConsoleCursor(bool showFlag) {
    
}


void drawPosition(int x, int y, bool filled) {
    if(!consoleInitialized) {
        consoleInitialized = true;
        printf("\033[2J");
    }
    putStringOnPosition(x+1, y, filled ? "■" : "□");
}


Boolean isPressed( unsigned short inKeyCode ){
    unsigned char keyMap[16];
    GetKeys((BigEndianUInt32*) &keyMap);
    putStringOnPosition(lastX, lastY, "         "); // remove arrow input character
    return (0 != ((keyMap[ inKeyCode >> 3] >> (inKeyCode & 7)) & 1));
}




bool keyState(char c) {
    bool ret;
    // keycode from http://web.archive.org/web/20100501161453/http://www.classicteck.com/rbarticles/mackeyboard.php
    switch (c) {
        case 'a': ret = isPressed(123); break;
        case 'd': ret = isPressed(124); break;
        case 's': ret = isPressed(125); break;
        case 'w': ret = isPressed(126); break;
        default: ret = false;
    }
    
    return ret;
}

 

 

<Header Files>

Headers.hpp

#ifndef __HEADERS_H__
#define __HEADERS_H__
#define _CRT_SECURE_NO_WARNINGS




#include "TetrisHelper.hpp"




#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cstring>
#include <cmath>




#define GRID_WIDTH 7
#define GRID_HEIGHT 10
#define USERBLOCK_SIZE 3




extern int displayData[GRID_HEIGHT][GRID_HEIGHT];






#endif

 

Displays.hpp

#ifndef __Displays_H__
#define __Displays_H__


#include "Headers.hpp"


class Display{
public:
    void draw();
};


#endif /* Display_hpp */

 

GameEngine.hpp

#ifndef __Game_Engine_H__
#define __Game_Engine_H__


#include "Headers.hpp"


// 게임 엔진 만들기
class GameEngine {
public:
    enum class GameState {
      PLAYING, GAMEOVER
    };
    GameState state = GameState::PLAYING;
    
    int gameGridData[GRID_HEIGHT][GRID_WIDTH] = { 0, };
    int userBlock[USERBLOCK_SIZE][USERBLOCK_SIZE] = { 0, }; // 떨어지는 블록
    
    int userBlockVarious[3][USERBLOCK_SIZE][USERBLOCK_SIZE] = {
        {
            {0,1,0},
            {0,1,0},
            {0,1,0}
        },
        {
            {0,0,0},
            {0,1,1},
            {0,1,1}
        },
        {
            {0,0,0},
            {0,1,0},
            {1,1,0}
        },
    };
    
    int blockX = 0;
    int blockY = 0; // 떨어지는 블록의 위치 좌표
    
    float elapsed = 0.0f;
    float controlCheck = 0.0f;
    
    void init();
    
    // next는 while 루프에서 매번 불려지는 함수가 될 것 입니다.
    void next(float dt, char keyboardInput);
    
    // 블록이 아래로 내려갈 수 있냐
    bool canGoDown();
    
    // 블록이 왼쪽으로 갈 수 있냐
    bool canGoLeft();
    
    // 블록이 오른쪽으로 갈 수 있냐
    bool canGoRight();
    
    // y번째 있는 블록들이 한 줄이 가득 차있는지 확인하는 함수
    bool isLineFilled(int y);
    
    // 라인이 가득 찬 경우 라인 지우기
    void eraseLine(int y);
    
    // 지운 후 y의 좌표를 기준으로 라인들을 위에서 부터 한 칸씩 아래로 내림
    void drop(int y);
    
    // userblock을 gameGrid에 전사하는 함수
    void trans();
    
    // 게임오버인지 판단
    bool gameOverDecision();
    
    void makeUserBlock();
    
    void rotate();
    
    // 화면에 출력해줄 데이터는 전역으로 선언해준 displayData로 gameGridData를 전사 해주는 작업
    // 실제 게임 데이터를 화면에 출력할 수 있는 데이터로 바꿔줍니다.
    void makeDisplayData();
};


#endif /* GameEngine_h */

 

TetrisHelper.hpp

#ifndef __TETRIS_HELPER_H__
#define __TETRIS_HELPER_H__




// 반드시 읽어주세요! 맥 사용자를 위한 주의사항
// 맥 사용자는 wasd 를 통해 코드를 만들지만 실제로는 방향키로 조작하게 됩니다.
// 맥 사용자는 왼쪽 상단의 프로젝트를 클릭하고
// Frameworks And Libraries 에서 Carbon.framework를 추가해야합니다.
// 추가로 Edit Scheme 를 열고 Options > Console > Use Terminal 을 사용해야 합니다.




#include <termios.h>
#include <cstdio>
#include <thread>
#include <Carbon/Carbon.h>




//#include "TetrisHelper.hpp"




void putStringOnPosition(int x, int y, const char* content);




void showConsoleCursor(bool showFlag);




void drawPosition(int x, int y, bool filled);


Boolean isPressed( unsigned short inKeyCode );




bool keyState(char c);






#endif // !__TETRIS_HELPER_H__