실행파일은 컴파일과 링크의 과정을 거쳐 생성된다. 이 때 컴파일을 하기 전 선행처리 과정을 거치게 된다.
선행 처리는 말 그대로 선행하는, 컴파일 이전의 처리를 의미한다. 소스 파일은 선행처리기에 의해 선행처리 되고, 컴파일러에 의해 컴파일 되며 이렇게 생성된 오브젝트 파일은 링커를 거처 실행파일을 생성한다. 오브젝트 파일은 바이너리 데이터로 이루어져있다.
하지만 선행처리를 거친 소스 코드는 여전히 소스 코드의 형태로 남아있게 되는데, 선행처리를 하면 보통 간단한 치환이나 수정만이 일어나기 때문이다.
#define 명령문은 다음과 같이 사용한다
#define PI 3.141592 //PI를 3.141592로 치환
선행처리 명령문인 #define문은 세 부분으로 나뉘는데, 각각 지시자, 매크로, 몸체이다.
이렇듯 #define으로 정의한 상수를 매크로 상수라고 한다. 정수, 실수, 문자열부터 함수 호출문까지 다양한 값을 매크로를 통해 정의할 수 있다. 매크로는 보통 대문자로 정의하는 것이 약속이다.
다음 예시를 보자.
매개변수가 존재하는 함수와 같은 형태로 매크로를 정의할 수 있다.
다음과 같이 사용한다.
#define SQUARE(X) X*X
SQUARE(123); //123*123
SQUARE(num); //num*num
이렇듯 #define으로 정의한 함수를 매크로 함수라고 한다.
❗주의
SQUARE(3+2)
와 같이 작성한 경우, SQUARE(5)
가 되어 25의 값을 도출할 것으로 기대하지만, 연산을 거친 후 선행처리를 하는 것이 아니므로 선행처리기는 단순히 SQUARE(3+2)
를 3+2*3+2
로 치환하므로 기대한 결과를 얻지 못한다. 따라서 SQUARE((3+2))
와 같이 괄호로 묶어주면 원하던 결과를 얻을 수 있다.
더 좋은 방법은 매크로 함수를 정의할 때, 몸체에 괄호를 치는 것이다. 다음과 같이 작성하는 것이 좋다.
#define SQUARE(X) ((X)*(X))
전체 식을 괄호로 묶어주는 것 또한 다른 연산에 섞여있을 때 문제가 발생할 수 있기 때문이다.
매크로를 두 줄에 걸쳐 작성하고 싶을 땐 다음과 같이 \
를 이용한다.
#define SQUARE(X) \
((X)*(X))
👉🏻매크로 정의 시, 다른 매크로를 사용할 수도 있다.
다음 예시를 보자.
📍매크로 함수의 장점
일반 함수에 비해 실행속도가 빠르다.(선행처리기에 의해 치환되므로 함수를 호출할 필요가 없고, 따라서 스택 메모리가 할당되지 않으며 매개변수로 인자를 전달하거나 결과값을 return하지 않기 때문)
자료형에 관계 없이 사용 가능하다.
매크로 함수는 정의하기 어렵고 디버깅하기 어렵다는 단점이 있으므로 남발하기에는 무리가 있다. 따라서 크기가 작고 호출 빈도수가 높은 함수를 정의할 때 사용하는 것이 적절하다.
#if...#endif 구문은 조건부 코드 삽입을 위한 지시자이다. #if와 #end 사이에 코드를 작성하고, 이 코드는 조건이 참일 경우 삽입, 거짓일 경우 삭제된다. 다음 예시와 같이 사용한다.
#ifdef...#endif 구문은 매크로가 정의되었는지, 정의되지 않았는지에 따라 사이의 코드가 실행된다. #ifdef 뒤의 매크로가 정의되었다면 #ifdef와 #endif 사이의 코드가 삽입되고 정의되지 않았다면 삭제된다. 이 때에는 정의되기만 하면 되므로 매크로 정의 시 몸체를 뺄 수 있다. 다음 예시와 같이 사용한다.
#ifndef...#endif구문은 #### #ifdef...#endif와 반대로 정의되지 않았다면 #ifndef와 #endif 사이의 코드가 실행된다. 동작 방식은 동일하다.
#if, #ifdef, #ifndef문에 #else문을 추가할 수 있다. 다음과 같이 사용한다.
if문의 else if와 유사하게, #if문에 #elif를 사용할 수 있다. 다음과 같이 사용한다.
문자열 내에서는 매개변수 치환이 자동으로 일어나지 않는다. 따라서 다음과 같이 매크로 함수를 정의하면 매개변수로 어떤 값을 넣던지 동일한 문자열로 치환된다.
#define STRING_COLOR(A, B) "A의 색은 B입니다."
//STRING_COLOR(콜라, 검정색) => "A의 색은 B입니다."
//STRING_COLOR(홍차, 붉은색) => "A의 색은 B입니다."
이 때에는 # 연산자를 이용하면 해결된다. # 연산자를 앞에 붙이면 치환하겠다는 의미이다. 다음과 같이 바꾸면 된다.
#define STRING_COLOR(A, B) #A "의 색은 " #B "입니다."
//STRING_COLOR(콜라, 검정색) => "콜라의 색은 검정색입니다."
//STRING_COLOR(홍차, 붉은색) => "홍차의 색은 붉은색입니다."
👉🏻문자열은 나란히 선언하면 연결되어 하나의 문자열로 처리된다. 따라서 char *str = "가나다라" "마바사";
는 char *str = "가나다라마바사";
와 완전히 동일하다.