#define: Object-like macro
매크로 : 컴퍼일러에게 코드의 특성을 알려주는 키워드
(위키백과 참고)
선행처리 명령문은 기본적으로 세 부분.
#define을 가리켜 '지시자'
선행처리기가 이 부분을 보고 프로그래머가 지시하는 바를 파악하기 때문에.
"이어서 등장하는 매크로를 마지막에 등장하는 매크로 몸체로 치환하라!"
#define 지시자 뒤, '매크로' → '매크로 몸체'
선행처리 명령문은 선행처리기에 아래와 같이 지시
"매크로 PI를 매크로 몸체 3.1415로 전부 치환하라"
= PI라는 이름의 매크로는 그 자체로 상수 3.1415가 된 셈.
PI와 같은 매크로를 '오브젝트와 유사한 매크로(object-like macro)' 또는 '매크로 상수'
예제
#include <stdio.h>
#define NAME "홍길동"
#define AGE 24
#define PRINT_ADDR puts("주소: 서울 특별시\n")
int main(void)
{
printf("이름 : %s \n", NAME);
printf("나이 : %d \n", AGE);
PRINT_ADDR;
return 0;
}
매크로의 이름은 대문자로 정의하는 것이 일반적. 식별자가 매크로라는 사실을 부각시킬 수 있기 때문.
#define: Function-like macro
매크로는 매개변수가 존재하는 형태로도 정의 가능.
매개변수가 존재하는 매크로는 동작방식이 마치 함수와 유사하여 '함수와 유사한 매크로(function-like macro)'라 하는데, 줄여서 간단히 '매크로 함수'
#define SQUARE(X) X*X
위 그림에서 정의한 매크로를 접한 선행처리기는 SQUARE(X)와 동일한 패턴을 만나면, 무조건 X*X로 치환.
SQUARE(123);
SQUARE(NUM);
선행처리 후 아래와 같이 변함
123*123;
NUM*NUM;
변환의 결과가 마치 함수의 호출과 유사.
이렇게 선행처리기에 의해서 변환되는 과정 자체를 가리켜 '매크로 확장(macro expansion)'
#include <stdio.h>
#define SQUARE(X) X*X
int main(void)
{
int num = 20;
/* 정상적 결과 출력 */
printf("Square of num : %d \n", SQUARE(num));
printf("Square of -5 : %d \n", SQUARE(-5));
printf("Square of 2.5 : %g \n", SQUARE(2.5));
/* 비정상적 결과 출력*/
printf("Square of 3+2 : %d \n", SQUARE(3+2));
return 0;
}
잘못된 매크로 정의
SQUARE(3+2)
이를 함수의 관점에서 본다면 3과 2의 합인 5를 SQUARE 함수의 인자로 전달하는 것으로 생각
즉, 25가 반환되어야 한다.
그러나 출력은 11.
왜?
먼저 연산을 하고, 그 연산결과를 가지고 함수를 호출하게끔 돕는 것은 컴파일러지 선행처리기가 아님
그러나 매크로는 선행처리기에 의해서 처리됨
따라서 위 식은 아래와 같이 치환됨
3+2*3+2
그렇다면 결과로 11이 출력된다.
그렇다면 해결책은?
아래와 같이 수정
SQUARE((3+2))
(3+2)*(3+2)
그러나 안정적이지 못함. 조금 더 수정하면 될 것 같음
매크로 몸체에 괄호를...
아래와 같이 코드를 수정하면 3+2로 넣어도 정상적으로 출력이 가능하다.
#define SQUARE(X) (X)*(X)
아래와 같이 치환
(3+2)*(3+2)
그러나 또 문제가 발생 가능성
예시
int num = 120 / SQUARE(2);
SQAURE(2)는 4이므로 num이 30으로 초기화할 것을 기대할 수 있지만 실제로는 120으로 나옴
왜?
int num = 120 / (2) * (2);
위처럼 되기 때문
따라서 이러한 문제들을 모두 해결하기 위해서는 아래와 같은 매크로 함수 정의
#define SQUARE(X) ((X) * (X))
이제 변수 num은 다음 식에 의해서 30으로 초기화
int num = 120 / ( (2) * (2) );
매크로를 두 줄에 걸쳐서 정의?
#define SQUARE(X)
((X) * (X)) // 에러 발생
두 줄 이상에 걸쳐서 정의할 때 아래와 같이
#define SQUARE(X) \
((X) * (X))
매크로 정의 시, 먼저 정의된 매크로도 사용이 가능하다.
먼저 정의된 매크로는 뒤에서 매크로를 정의할 떄 사용 가능
#include <stdio.h>
#define PI 3.14
#define PRODUCT(X, Y) ((X)*(Y))
#define CIRCLE_AREA(R) (PRODUCT((R), (R)) * PI)
int main(void)
{
double rad = 2.1;
printf("반지름 %g인 원의 넒이 : %g \n", rad, CIRCLE_AREA(rad));
}
위를 보면 먼저 정의된 매크로는 새로운 매크로를 정의하는데 사용이 가능하다.
매크로 함수의 장점
- 매크로 함수는 일반 함수에 비해 실행속도가 빠르다.
- 자료형에 따라서 별도로 함수를 정의하지 않아도 된다.
실행속도가 빠른이유
함수가 호출되면 다음 사항들이 동반
- 호출된 함수를 위한 스택 메모리의 할당
- 실행위치의 이동과 매개변수로의 인자 전달
- return 문에 의한 값 반환
따라서 함수의 빈번한 호출은 실행속도의 저하로 이어진다.
반면 매크로 함수는 선행처리기에 의해서 매크로 함수 몸체부분이 매크로 함수의 호출 문장을 대신하기 때문에, 위에서 언급한 사항들을 동반하지 않는다. 따라서 실행속도상의 이점이 있다.
매크로 함수의 단점
- 정의하기가 정말로 까다롭다
- 디버깅하기가 쉽지 않다.
정의하기가 까다로운 예시
함수 형태
int DiffABS(int a, int b)
{
if (a > b)
return a - b;
else
return b - a;
}
매크로 형태
#define DIFF_ABS(X, Y) ((X)>(Y) ? (X)-(Y) : (Y)-(X))
정의하기가 어려움
디버깅 어려움
#define DIFF_ABS(X, Y) ((x)>(y) ? (x)-(y) : (x)-(y))
대소문자 구분 어려움
결국 매크로 함수를 정의할 때 아래와 같은 기준을 정한다음 함수를 정의하는 게 좋다.
- 작은 크기의 함수
- 호출의 빈도수가 높은 함수
참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.
'Language&Framework&Etc > C' 카테고리의 다른 글
매크로와 선행처리기(26-4) 매개변수의 결합과 문자열화 (0) | 2020.12.18 |
---|---|
매크로와 선행처리기(26-3) 조건부 컴파일(Conditional Compilation)을 위한 매크로 (0) | 2020.12.18 |
매크로와 선행처리기(26-1) 선행처리기와 매크로 (0) | 2020.12.17 |
메모리 관리와 메모리의 동적 할당(25-2) 메모리의 동적 할당 (0) | 2020.12.17 |
메모리 관리와 메모리의 동적 할당(25-1) C언어의 메모리 구조 (0) | 2020.12.17 |