2017년 9월 19일 화요일

c+ 다수의 소스나 헤더 적용법과 에러

오늘 배운 수업중 하낙가, 다수의 소스와 헤더파일의 구동법이었는데, 일전에 언리얼 코드를 뜯어본 기억이 새록새록 돋아나 흥미롭게 들었습니다.

겪어 본 이후로 더욱 더 실감하지만, 하나의 프로잭트에 여러명이 붙는순, 학생들이 배우는 방식의 코드작업은 지옥을 초래하게 됩니다.

(하나의 소스파일로 두세명이 작업하는것도 무리인데, 회사의 경우는 두세명이 아닐태니깐!)

그러한 이유로 우리는 여러개의 소스 파일과 헤더들을 이용하여 쉽게 지옥을 물리칠 수 있습니다.

우선적으로 우리가 컴파일을 시작하면, 프로잭트의 모든 Source 파일들을 읽기 시작하죠.

그 중 #include 문이 있다면,include가 가리키는 라이브러리, 혹은 헤더파일을 방문하여 읽게 됩니다.

미리 define된 라이브러리 함수들은 꺽쇠인 < > 로 include를 합니다.

하지만 우리가 만드는 사용자정의 Header는, 보통 하나의 프로젝트 내에 있기 때문에, 단순히 #include "Header이름.h" 를 사용하면 됩니다.

이렇게 선언된 글을 컴파일러가 읽게되면, 만든 header로 방문하게 되어, 선언문들을 읽은뒤, 마저 Source들을 읽게 됩니다.

저와 같은 기초반을 들었던 사람들이라면 흔히 봤을 광경입니다.


기초과정이기도 하고, 중점 교과과목은 자바였기 때문에, 깊게 파고들지 않아서 하나의 예제당 하나의 프로젝트를 만드는게 아닌, 이렇게 소스파일만 새로 만들어서, 이전 예제는 컴파일 재외를 했었습니다.

저는 개인적으로 하면서, 왜 제외해야하나몹시 궁금했었고, 고급과정을 하는동안 까마득히 잊고있었습니다.

정답은 프로젝트를 컴파일하면, 내부의 모든 소스파일들을 컴파일러가 읽어버리기 때문이었습니다.

유일하게 하나이어야만 하는 main 함수, 하나의 프로젝트에 여러번 나오게 될 경우 당연히 문제가 발생했기 때문에, 우리는 귀찮게도 이전 예제 소스파일들을 하나하나 컴파일 예외  시켰습니다.

그럼 이제 여러개의 소스파일을 컴파일 하는방법이 감이 오네요! 여태까지 안하게 막고있었으니, 그 작업만 한하면 되는거였네 ㅎㅎ;

그럼 간단한 stack 을 만들어 봅니다.
목표는
1. 클래스로 두개의 스택구조를 만들것,
2. int타입을 100개 stack 가능하며,
3. 메인 소스파일과, 함수 소스파일은 별개로 만들 것 입니다.

클래스 선언은 Header에서 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Stack
{
private : 
    int arr[100];
    int top;
public  :
    Stack() {
        for (int i = 0; i < 100; i++) 
            arr[i] = 0;
        top = -1;
    }
    void push(int value);
    int pop();
};

클래스에서는 push와 pop함수를 내장하고 있습니다. 두개의 동일한 stack구조를 만들것 이기때문에, 하나의 클래스에 모조리 넣었습니다.
하지만, 함수를 선언만 가능하며, 함수의 내용은 Header에서 함수의 내용을 적지는 않습니다.


 

헤더의 사용 이유는 이러합니다.
SourceA.cpp
----------
void FuncA(){
 FuncB();
}
-----------

SourceB.cpp
-----------
void FuncB(){
}
-----------
의 케이스에서, 우리 기대와는 달리 SourceA에 FuncB() 는 인식하지 않습니다.
이를 해결하기 위해서는

SourceA.cpp
----------
void FuncB();
...

void FuncA(){
 FuncB();
}
-----------
요로코럼 Forward declaration을 해야만 합니다.

이 작업이 수많은 소스파일들도 다 해주어야만 하면, 끔찍하고 지루한 반복작업이 되기떄문에, 이를 해결하기 위해 나온것이 헤더인 것 입니다.

선언을 돕는거죠.






Source02 입니다.

1
2
3
4
5
6
7
8
9
10
#include "Header01.h"
void Stack::push(int val)
{
    this->arr[++this->top] = val;
}
int Stack::pop()
{
    return this->arr[this->top--];
}

Header를 include하여, Stack 클래스가 무엇인지 컴파일러는 알게 되었습니다.
push() 와 pop() 함수를정의하는데, 그 앞에 Stack:: 이 적혀있습니다.

이는 Scope 라고 부릅니다.

C와 달리, C++에서는 같은 이름의 함수명을 이용 할 수 있게 되었습니다.
그렇기 때문에, 어디출신의 push() 함수인가를 명확하게 알려주어야만이, 컴파일이 가능해 졌습니다.

어디 출신인지 알려주는 역활이 Scope이며, Stack 클래스에 있는 함수 push 다 라고 알려주기 위해, Stack::push() 라고 쓰게 됩니다.


이제 준비가 끝났습니다. 메인 역활의 함수를 만들기 위해 Source01 소스를 만듭니다
1
2
3
4
5
6
7
8
9
#include "Header01.h"
#include<iostream>
using namespace std;
void main()
{
    Stack s1, s2;
    s1.push(100);
    cout << s1.pop() << endl;
}

이번 소스에서도 header를 알려줍니다.

Stack클래스를 정의만 했을뿐, 그 어디에소 실제로 선언하진 않았기때문에, 선언이 필요합니다. 그 작업을 Source01 에서 할것 이기 때문에, Stack 이 무엇인지, 컴퓨터는 알지 못합니다, Source02에서는 알았지만요.

저는 처음 이것을 만들 때에 어처구니없는 실수를 범했습니다.
Source01 에서 Header01을 include 하는것이 아니라, Source02를 했습니다.

컴파일 단계에서 Source02는 따로 명시하지 않아도 읽어들입니다. Source02에는 push와 pop 함수에 대한 정의가 있는데요,

제가 이미 정의된 push와 pop 함수를 한번 더 정의해버린 꼴이 되었기 때문에, 컴파일러는오류를 뱉어내면서(LNK1169) 작동을 중지했습니다.

이때 까지만 해도 소스파일을 모조리 컴파일 한다는 생각을 하지 않았기 때문에 일어난 일이었습니다.


그걸 정리하려고 이 글을 쓴거구




+ 추가
vs17부터는 자동으로 추가 되었지만, 학교에서 사용하는 vs12에서는 Header에 기본적인 세팅이 되지 않습니다.
덕분에 조금 더 배우게 되었는데요,

Source파일들이 많아지고, Header파일들이 많아질떄, 우리는 실수로 같은 Header를 중복으로 읽게 만드는 일이 빈번히 발생 할 수 있게됩니다.

이에 Header에서 중복으로 include를 할때 오류를 범하지 않게 하기위한 솔루션이 두가지가 있습니다.




1. Header 에

#pragma once
선언

2. Header 에 전처리문 작성.

#ifndef _HEADER_H
#define _HEADER_H
선언문 작성..
#endif

1번의 경우 어느정도 최신버전에서부터 지원하는듯 합니다. 간단하고 간결하죠,
하지만 우리가 실무에 갔을떄 최신버전만을 쓰지는 않을터..
솔루션을 알아둬야죠 ㅠ

우선 뜻 해석입니다.

#ifndef -> if not defined

_HEADER_H -> _HEADER_H 라는 사용자 정의 이름.

#define _HEADER_H -> _HEADER_H 를 선언한다.

#endif -> #ifndef문 종료

이부분은 추가적인 공부가 필요합니다. 전처리문으로 컴파일러가 알아먹는 언어인데, 이친구가 _HEADER_H 가 있는지 제일 처음 검사를 한다는 의미입니다.
만약 없으면 _HEADER_H를 define 하고, 아닐시, 그냥 #endif로 넘어갑니다.

_HEADER_H 는 뭐냐 싶었는데, 단순하게도 그냥 개발자간의 약속입니다.
_헤더이름_확장자 인거죠.
전처리문은 . 을 인식하지 못하기 때문에 _ 를 쓰며, 모두 대문자 또한 개발자간의 약속입니다.


 

댓글 없음:

댓글 쓰기