본문 바로가기
c언어

c언어 함수 매크로에 대하여 알아보기

by 개발자 L 2023. 2. 21.
반응형

c언어 함수 매크로에 대하여 알아보기

네 안녕하세요, 이번 포스팅에서는 함수 매크로에 대하여 알아보도록 하겠습니다.

제가 이전 포스팅에서 매크로의 종류는 크게 두 가지가 있다고 그랬습니다.

  • 단순 매크로
  • 함수 매크로

이 두 가지 중에 이전 포스팅에서 단순 매크로에 대해 다뤘고,

이번에는 함수 매크로에 대하여 자세히 다뤄보도록 하겠습니다.

그럼 지금부터 시작하겠습니다.

 

1. 함수 매크로

함수 매크로는 매크로가 함수처럼 매개 변수를 가지는 형태의 매크로를 말합니다.

이 매크로를 사용하는 이유는 함수처럼 복잡한 계산을 숨기고 훨씬 간단하게 표현을 하기 위해 씁니다.

예를 하나 들어보도록 하겠습니다.

#define SQUARE(x) ((x) * (x))

이렇게 써봤습니다.

이건 어떤 수의 제곱을 구하는 매크로를 정의한 것인데요,

매크로를 정의하지 않으면 저기 보이는 '(x) * (x)'를 계속 써줘야 합니다.

이건 굉장히 불편하죠?
그래서 그냥 어떤 함수 식을 기호 상수화 해서 정의를 아예 기호 상수로 만들어버렸습니다.

이렇게도 쓸 수 있고, 삼항 연산자 등을 써서 나타낼 수도 있습니다.

이런 식으로 말이죠.

#define MAX(x, y) ((x) > (y)) ? (x) : (y)
#define MIN(x, y) ((x) < (y)) ? (x) : (y)

 보통 어떤 조건을 다루는 함수를 쓴다면 이런 식으로도 가능합니다.

 

2. 매크로를 사용할 때 주의할 점

함수 매크로에서는 매개 변수의 자료형을 써주지 않습니다.

그래서 그 어떠한 자료형에 대해서도 적용을 할 수 있습니다.

예를 들어서 제가 아까 정의한 SQUARE로 보여드리도록 하겠습니다.

v = SQUARE(7); // 정수의 제곱
v = SQUARE(1.23); // 실수의 제곱
v = SQUARE(a + b); // 수식의 제곱

이런 식으로 다 가능하기 때문에 엄청난 이식성을 보여주지만,

이런 점 때문에 주의 사항이 생깁니다.

 

2 - 1. 반드시 매개 변수를 괄호로 묶어줘야 한다.

 

함수 매크로에서는 매개 변수가 기계적으로 대치되기 때문에

반드시 매크로에 쓰이는 매개 변수들을 괄호로 묶어줘야 합니다.

만일에 매크로를 정의할 때 괄호로 묶지 않았다고 해봅시다.

이런 식으로 말이죠.

#define SQUARE(x) x*x

v = SQUARE(a + b);

이랬을 때의 결과는 이런 식으로 나옵니다.

a + a*b + b

괄호로 묶지 않았기 때문에 a와 b를 가지고서 제곱을 하는 것이 아니라 분배법칙을 해버립니다.

그렇기 때문에 절대로 묶어야 한다는 것입니다.

 

2 - 2. 매크로를 정의할 때 쓴 매개 변수들은 다 써야 한다.

이런 경우가 있죠.

#define HALFOF(x , y) ((x) / 2)

이렇게 쓰면 매개 변수 하나가 쓰이지 않았기 때문에 버려지는 매개 변수가 되어 오류가 납니다.

 

2 - 3. 매크로의 이름과 매개 변수 사이에 공백이 존재해서는 안된다.

이 경우도 흔히 하는 실수입니다.

#define ADD (x, y) ((x) + (y))

잘 보시면 기호 상수 ADD와 매개 변수를 묶은 괄호가 띄어져 있죠?
그렇게 되면 이는 함수 매크로가 아니라 단순 매크로 취급을 해서

컴파일러가 ADD라는 기호 상수를 '(x, y) ((x) + (y))'로 치환하게 되는 치명적인 오류를 발생시키므로

주의를 하셔야 합니다.

 

2 - 4. 함수 매크로를 한 줄 이상으로 쓰고 싶을 경우에는 백슬래시(\)를 이용한다.

이는 파이썬에서도 쓰는 방법인데요,

줄이 너무 길어져서 화면 이동이 생길 것 같을 때 이어서 쓰는 방법입니다.

이런 식으로 쓰시면 됩니다.

#define PRINT(x) if(debug == 1 && \
                    mode == 1) \
                        printf("%d\n");

이런 식으로 쓰시면 됩니다.

그냥 엔터키를 눌러 강제로 개행을 하면 에러가 나니 주의하셔야 합니다.

그럼 이를 가지고서 간단한 예제를 한 번 작성해 보도록 하겠습니다.

#include <stdio.h>

#define SQUARE(x) ((x) * (x))

int main()
{
    int x = 2;

    printf("%d\n", SQUARE(x));
    printf("%d\n", SQUARE(3));
    printf("%f\n", SQUARE(1.2));
    printf("%d\n", SQUARE(x + 3));
    printf("%d\n", 100 / SQUARE(x));
    printf("%d\n", SQUARE(++x)); // 후위연산이므로 9가 아닌 16 출력
    
    return 0;
}

이렇게 작성을 해봤습니다.

그럼 결과를 보도록 하겠습니다.

4
9
1.440000
25
25
16

결과도 잘 나온 것을 확인할 수 있습니다.

여기서 후위 연산에 대하여 좀 더 알고 싶으신 분들이 계신다면

제가 써 둔 포스트 중에 산술 연산자에 들어가시면 자세히 나와있습니다.

제가 아래에 링크를 걸어두도록 하겠습니다.

 

2022.11.27 - [분류 전체 보기] - c언어 산술 연산자에 대하여 알아보기

 

c언어 산술 연산자에 대하여 알아보기

c언어 산술 연산자에 대하여 알아보기 네 안녕하세요, 이번 포스팅에서는 c언어 산술 연산자에 대하여 알아보는 시간을 가져보려 합니다. 흔히 우리가 이야기하는 산술 연산자와 거의 똑같습니

funnycoderl.tistory.com

 

여기로 들어가시면 됩니다.

반응형

 

3. #연산자

우리가 함수 매크로를 사용을 하다 보면 매크로의 인수를 문자열로 변경하고 싶은 경우가 생깁니다.

예를 들어서 변수명과 값을 동시에 출력을 하고 싶다고 가정을 해봅시다.

그럴 경우에는 문자열을 받아야겠죠?
일반적인 코드를 작성하면 이런 결과가 나옵니다.

#include <stdio.h>

#define PRINT(exp) printf("exp = %d\n", exp);

int main()
{
    int x = 5;

    PRINT(x);

    return 0;
}

이렇게 작성을 할 수 있겠죠?
이럴 경우에 어떤 식으로 나오는지 한 번 봅시다.

exp = 5

음... 우리는 main() 함수 내에 정의된 변수명과 값을 내고 싶은 건데,

기호 상수로 정의한 매개 변수가 출력이 되었죠?

이럴 경우에 씁니다.

이런 식으로 바꿔주면 됩니다.

#include <stdio.h>

#define PRINT(exp) printf(#exp" = %d\n", exp); // exp에 # 연산자를 넣고, 쌍따옴표의 위치를 변경함

int main()
{
    int x = 5;

    PRINT(x);

    return 0;
}

이렇게 쓰면 결과는 이렇게 나옵니다.

x = 5

우리가 원하던 결과가 나옴을 확인할 수 있습니다.

 

4. 내장 매크로

내장 매크로는 컴파일러가 개발자들이 유용하게 사용을 하도록 제공하는 선정의 되어있는 매크로입니다.

가장 많이 사용하는 것은 아래 표에 있는 4가지입니다.

내장 매크로 설명 용도 형식 지정자
__ DATE__ 소스가 컴파일된 날짜(월 일 년)로 치환 프로그램의 버전 확인 %s
__TIME__ 소스가 컴파일된 시간(시 : 분 : 초)으로 치환 프로그램의 버전 확인 %s
__LINE__ 소스 파일에서의 현재 줄 번호로 치환 디버깅 관련 정보 출력
(__FILE__과 주로 같이 쓰임)
%d
__FILE__ 소스 파일의 이름으로 치환 디버깅 관련 정보 출력
(__LINE__과 주로 같이 쓰임)
%s

이렇습니다.

해당 매크로들을 개발자들이 사용을 하게 되면 이 매크로에 정의가 되어있는 코드로 치환이 됩니다.

예를 들자면 이런 코드를 썼다 해봅시다.

printf("컴파일 날짜는 %s 입니다.\n", __DATE__);

이렇게 쓰게 되면 소스 코드를 컴파일한 날짜를 볼 수 있게 되는데

이 기능을 쓰게 되면 프로그램의 버전이 최신 버전인지 아닌 지를 확인할 수 있습니다.

그 외에 __LINE__과 __FILE__의 경우는 디버깅에 관한 정보를 뽑을 때 쓰는데,

이런 식으로 씁니다.

printf("오류가 발생한 파일의 이름은 %s, 줄 번호는 %d 입니다.\n", __FILE__, __LINE__);

이렇게 주로 같이 쓰이게 되며,

해당 파일에 작성된 소스에 문제가 생긴 것이므로,

해당 파일에 작성이 된 줄 번호도 같이 출력을 해줍니다.

그리고 여기서 중요한 사실은 __LINE__은 나머지 내장 매크로들과는 다르게 줄의 번호만 출력을 하므로,

형식 지정자는 정수형인 % d로 받아주어야 한다는 것을 주의하셔야 합니다.

 

5. 함수 매크로를 함수 대신 썼을 때의 장단점

함수 매크로는 함수와 매우 비슷한 점이 많습니다.

그 이유는 일단 함수 매크로는 함수를 기호 상수화 시켜놓은 것이기 때문에

일반 함수들과 같이 매개 변수를 이용한다는 점에서 매우 비슷합니다.

그러면 함수 매크로를 함수 대신 썼을 때의 장단점은 무엇이 있을까요?

 

5 - 1. 함수 매크로를 함수 대신 썼을 때의 장점

우선 장점에 대하여 이야기를 하자면,

매크로는 일반 함수에 비해서 수행 속도가 빠릅니다.

그 이유는 매크로는 호출이 아니라 전처리기에 의해서 해당 위치에 삽입되어 고정되는 것이기 때문에

함수 호출을 할 때 쓰이는 복잡한 과정을 거칠 필요가 없습니다.

실제로 함수를 호출할 때는 호출을 할 인수, 그리고 호출 후 복귀 주소를 시스템 스택에 저장을 해줘야 하는데,

이러한 과정을 거칠 필요가 없기 때문에 훨씬 빠릅니다.

 

5 - 2. 함수 매크로를 함수 대신 썼을 때의 단점

단점이 있다면 곧이 길이를 일정 한도 이상 길게 입력을 할 수 없습니다.

대개 한 줄, 정말 많아야 두세 줄 까지가 한계입니다.

그 이상도 가능하지만,

오히려 그 길어짐으로 인하여 가독성이 떨어질 수 있습니다.

또한, 매크로는 전처리기가 발견이 될 때마다 매크로의 정의 부분에 있는 코드로 확장을 하기 때문에

매크로를 쓰면 쓸수록 파일의 크기가 커져서 메모리를 잡아먹는 양이 많아지기도 합니다.

그에 비하면 함수는 단 하나의 코드만 가지고 있기 때문에 메모리 관리가 효율적입니다.

 

그래서 간단한 함수라면 매크로를 사용하는 것이 실행 속도와 가독성 측면에서 우세하고,

복잡한 함수를 쓴다면 메모리 보전과 기타 효율을 높이기 위해서 일반 함수만 쓰는 것을 권장합니다.

 

여기까지 함수 매크로에 대하여 알아보았습니다.

다음 포스팅에서는 #ifdef와 #endif에 대하여 알아보도록 하겠습니다.

긴 글 읽어주신 독자분들께 진심으로 감사드립니다~

반응형

댓글