반응형
728x90
반응형

파일의 분할

  • 첫 파일의 분할

위와 같은 예를 파일로 분할할 것이다. 다음과 같이.

< number.c / func.c / main.c >

컴파일러가 소스 코드를 컴파일 할 때는 파일 단위로 컴파일 한다. 그렇기 때문에 다른 파일의 정보를 참조하여 컴파일을 진행하지 않는다. 그렇기 때문에 저렇게 세 파일로 나누게 되면 number 변수가 어디에 있는지, set/getNumber 함수가 어디에 있는지 무엇인지 알 수 없기 때문에 에러가 난다. 그렇기 때문에 컴파일러에게 현재 컴파일하고 있는 소스의 외부 소스에 선언 및 정의가 되어있음을 컴파일러에게 알려야 한다.

다음과 같이 수정한다. 먼저 number 변수에 대해서 외부 변수라고 알린다. 그리고 두 함수에 대해서 외부 함수라는 것을 알린다. 바로 extern 키워드를 통해서 컴파일러에게 이를 알릴 수 있다.

< number.c / func.c / main.c >


< Makefile
과 실행 결과 >

 

각 소스에서 찾을 수 없는 변수나 함수들에 대해서 extern 키워드를 이용하여 외부에 존재한다는 선언을 내려주면 정상적으로 컴파일 되는 것을 확인할 수 있다. 그리고 함수에 대해서는 extern 선언은 생략해도 상관 없다. 이와 반대로 외부에서 접근을 못하게 하고 싶다면 해당 변수나 함수에 static 키워드를 붙여주면 다른 외부 파일에서 extern을 이용한 알림이 불가능하다. static 전역 변수는 접근의 범위를 파일의 내부로 제한하는 경우에 사용한다.

 

  • 헤더 파일의 활용

#include 지시자는 선행 처리기에게 다음과 같은 지시를 전달한다.

해당 #include <…> 문장의 위치에 해당 헤더의 저장된 내용을 가져다 놓기!

그리고 다음 예제를 통해서 #include 지시자를 완전히 이해할 수 있다.


< m.c / h1.h / h2.h >

 

  • 헤더 파일을 include 하는 두 가지 방법

1.     #include <헤더 파일 이름>
-> 표준 헤더 파일이 저장되어 있는 디렉터리에서 해당 헤더 파일을 찾는다.

2.     #include “ 헤더 파일 이름
-> 이 문장을 포함한 소스 파일이 저장된 디렉터리에서 해당 헤더 파일을 찾는다.
->상대 경로(...을 이용한) 및 절대 경로를 이용하여 헤더 파일을 삽입 가능하다.

 

  • 헤더 파일에 담아야 할 요소

1.     외부에 존재하는 변수나 함수에 대해서 extern 하는 선언들을 헤더 파일에 담고, 필요할 때마다 헤더 파일만 삽입하여 외부 정의를 알릴 수 있게끔 구성한다. 그렇지 않으면 모든 소스 파일에서 필요한 extern 선언을 반복해서 해야하기 때문이다.

2.     하나의 구조체 등의 선언 및 정의를 여러 소스 파일에서 사용할 때, 이를 각 소스 마다 명시하여 중복 선언 및 정의를 없앤다. 왜냐하면 중복 정의가 되어 있으면 해당 구조체를 수정하고, 확장할 때 불편함이 따른다. 그렇기 때문에 헤더 파일을 이용하여 구조체의 선언 및 정의가 하나만 존재할 수 있도록 개선한다.
 ->
헤더 파일의 중복 문제가 발생할 수 있다.

 

  • 헤더 파일의 중복 발생 및 조건부 컴파일을 활용한 중복 삽입 문제의 해결

헤더 파일의 중복 삽입 자체는 문제가 되지 않는다.

extern int number;
extern void setNumber(int n);
extern int getNumber(void);

위와 같은 선언은 여러 번 삽입된다고 하더라도 오류가 발생하지 않는다. 그저 컴파일러에게 메시지를 전달하는 것에 지나지 않기 때문이다. 그러나 변수의 선언, 구조체 정의, 구조체 변수 선언, 함수 정의 등은 컴파일러에게 메시지, 알림을 알려주는 차원이 아니라 실행 파일의 내용에 직접적인 연관이 있는, 실행 파일의 내용이 달라질 수 있는 정보이기 때문이다.

조건부 컴파일을 활용한 중복 삽입 문제 해결

위 헤더를 처음 포함하는 소스 파일은 __HEADER_H__ 매크로 상수가 정의되지 않았기 때문에 조건부 if 절을 실행하지만, 중복 포함하는 소스의 경우는 매크로 상수가 정의되어 있기 때문에 헤더를 삽입하더라도 내용이 실행되지 않는다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

19 매크로 선행 처리기  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

매크로 선행 처리기

  • 선행 처리기

선행 처리 과정은 프로그래머가 삽입해 놓은 선행 처리 명령문 대로 소스 코드의 일부를 수정한다. 수정은 단순 치환 형태를 띠는 것이 대부분이다. 다음은 간단한 선행 처리에 대한 예제이다.

위의 예에서 선행 처리 명령문은 2번 라인과 4번 라인, 두 가지가 존재한다. 선행 처리 명령문은 # 문자로 시작하고, 명령의 끝에는 세미콜론을 붙이지 않으며 컴파일 이전에 컴파일러가 아닌 선행 처리기에 의해 처리된다. 먼저, 4번 라인의 경우, PI라는 기호를 만나면 3.1412로 치환해라 라는 의미이다. 이 때 PI 기호를 만난다는 것은 쌍 따옴표로 둘러 쌓인 문자열 내의 PI가 아닌 모든 PI 기호를 이야기한다. 선행 처리기에 의해 선행 처리가 진행되면 오른쪽 그림과 같이 치환이 된다. 그리고 2번 라인에서 헤더 파일을 삽입하는 것 또한 선행 처리기에 의해 진행된다. stdio.h라는 파일의 내용을 include 명령이 쓰인 해당 파일에 가져다 놓으라는 의미를 가지고 있다.

 

  • Object-like macro(오브젝트와 유사한 매크로, 매크로 상수) : #define

    지시자 매크로 매크로_몸체
#define   PI   3.1412

지시자는 #include, #define 등과 같은 것을 가리켜 지시자라고 한다. 그리고 이어서 매크로라고 하는 기호와 대치되어야 할 매크로의 몸체가 등장한다. 선행 처리기는 이 부분을 보고 프로그래머가 지시하는 바를 파악한다. 그 중 #define 지시자는 다음과 같은 내용을 지시한다.

이어서 등장하는 매크로를 마지막에 등장하는 매크로 몸체로 치환!

다음은 이에 대한 예이다.

4번 라인부터 6번 라인까지 매크로 상수를 정의 및 지시가 이루어 졌고, 10번 라인부터 12번 라인에서 컴파일 이전에 치환(대치)이 이루어진다. 특이한 점은 6번 라인처럼 함수의 호출 자체 또한 매크로 몸체로 올 수 있다는 점이다.

 

  • Function-like macro(함수와 유사한 매크로, 매크로 함수) : #define

형태는 매크로 상수와 동일하다. 다음은 매크로 함수의 예이다.

지시자      매크로    매크로_몸체
#define    SQUARE(X)        X*X

기존 매크로 상수에 괄호를 등장시킴으로써 함수의 형태를 보이고 치환을 진행할 수 있다. 여기서 X는 정해 지지않은 임의의 값 또는 문장을 이야기 하는데 어떤 문자로 든 올 수 있으나 매크로와 매크로_몸체에서 사용되는 문자는 같은 문자로 통일 되어야 한다. 위의 예에서 해석을 해보자면 SQUARE(X)와 같은 패턴이 등장하게 되면 X*X 형태로 바꿔라 라는 의미다. 함수 호출과 매우 유사하고, 이렇게 변환되는 과정 자체를 매크로 확장이라고 한다. 다음은 관련 예제이다.

 

  • 매크로 함수의 문제(우선순위)

위의 예제는 매크로 함수의 잘못된 사용의 결과이다. 3+2 값의 Square 연산은 5^2으로 25가 나와야 하지만 전혀 다른 결과가 나왔다. 그 이유는 매크로 함수 치환을 풀어보면 나타난다. 3+2*3+2 형태로 치환이 되었기 때문에 우선 순위 연산에 의해서 곱셈 연산자가 먼저 연산 되는 바람에 11이라는 값이 나왔다. 이를 해결하기 위해서는 매크로 함수 정의 시 괄호를 치고 구분을 해주어야 한다.

위와 같은 정의로 매크로 함수를 구성하면 정확한 결과를 얻을 수 있다.

 

  • 매크로 기타 사항

먼저, 매크로 몸체가 복잡해서 두 줄 이상에 걸쳐서 정의를 할 경우에는 두 줄에 걸쳐서 매크로를 정의해야 하는 경우도 생기는데 이 때 그냥 임의로 줄을 변경하면 에러가 발생한다. 이럴 때에는 \문자를 이용해서 줄이 바뀐 매크로 정의임을 명시한다.

그리고 또한 이미 정의한 매크로를 이용하여 또 다른 매크로 정의가 가능하다. , 매크로 정의 시, 먼저 정의된 매크로도 포함 및 사용이 가능하다.

 

 

  • 매크로 함수의 장점과 단점

 

매크로 함수는 보통 작은 크기의 함수 / 호출의 빈도 수가 높은 함수를 매크로 함수로 보통 정의한다. 함수의 크기가 작아야 에러의 발생 확률 또한 낮고, 호출의 빈도 수가 높아야 매크로 함수가 가져다주는 성능 상의 이점을 최대한 활용할 수 있다.

 

 

  • 조건부 컴파일

#if ~ #endif : 참이라면

조건부 컴파일을 이용한 예이다. 7번의 매크로 상수를 FALSE로 정의하면 12번 라인은 실행하지 않는다.

#ifdef ~ #endif : 정의되었다면

12번 라인에서 ifdef는 해당 매크로 상수가 정의되어 있으면 ifdef 내부를 실행한다. 그리고 13번 라인처럼 비교 연산 또한 가능하다. 그리고 8번 라인처럼 공백으로 매크로 상수를 정의할 수 있다. 이 경우에는 선행 처리 과정에서 공백으로 대체되므로 소멸 의미와 같다.

 

  • #elif의 삽입 : #if만 가능

#elif를 통해서 else if 와 같은 효과를 #else를 통해서 else와 같은 효과를 볼 수 있다.

 

  •  매개 변수의 결합과 문자열화

다음과 같은 문장이 가능한지 보자.

#define CONCATENATE(A,B) “A B”

위와 같은 문장은 일단 불가능하다 왜냐하면 문자열 안에서는 매크로의 매개변수 치환이 발생하지 않기 때문이다. 이러한 경우 # 연산자를 이용한다.

#define STR(A) #A

위 문장의 의미는 A로 전달되는 인자를 문자열 “A의 내용형식으로 치환하라는 의미이다.

따옴표를 넣고 안 넣고 의 차이를 확인한다.            

이런 치환 또한 가능하다. 9번 라인에서 두 문자열을 따로 썼지만 합쳐진 결과가 나타났다. 이를 활용해보면.

#define STRCAT(A,B,C) ABC

위의 경우 STRCAT(YOO, SEUNG, HO);라고 하면 “YOO SEUNG HO”라는 문자열이 조합될 것 같지만, 실제로는 조합되지 않는다. 왜냐하면 ABC ABC의 문자열 결합으로 보지 않고 ABC를 하나의 새로운 매크로 상수로 보기 때문이다. 이 경우 ABC로 그냥 치환이 될 뿐이다.

#define STRCAT(A,B,C) A B C

이 경우에는 STRCAT(as,vf,sd); 라고 호출될 경우 as vf sd 로 치환이 될 뿐이다.

이러한 문제를 해결하기 위해서는 ##연산자를 사용해야 한다.

 

  • 필요한 형태로 단순 결합 : 매크로 ## 연산자

## 연산자를 통해 인자로 전달된 요소들을 단순 결합을 시킨다. 다음은 그에 따른 예제이다.

합쳐진 것을 볼 수 있다.

참고로 소스 코드 위에 단순 치환이기 때문에 AB라는 문자열과 1234라는 문자열을 치 ## 연산할 경우 AB1234라는 문자열이 생기는 것이 아니라 소스 코드 상에 AB1234로 대치가 되는 것이다 그렇기 때문에 AB1234는 소스 코드 상에서 변수나 함수 등의 이름으로 인지될 것이고, 이는 없는 이름이기 때문에 에러가 발생한다. 이 점을 혼동하지 않아야 한다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

20 파일의 분할  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29

+ Recent posts