Home C언어 (24) 매크로와 인라인 함수
Post
Cancel

C언어 (24) 매크로와 인라인 함수

C언어 (24) 매크로와 인라인 함수

C언어 책 마지막엔 전처리기와 분할 컴파일에 대한 이야기가 있다.
사실 잘 안쓰는 잡다한 내용이라 여기까지 진도도 안나가고 안 읽어봐서 처음 보는 내용이었다.
이번에 싹 정리하는 겸 다 내용을 알아보자.

전처리기

전처리는 본격적으로 소스 파일을 컴파일 하기 전에 먼저 처리해야 하는 일이다.
코드의 가장 위에 있는 #으로 시작하는 문장을 전처리기 지시자라고 부른다.
대표적으로 헤더 파일을 포함하는 #include와 매크로 상수를 정의하는 #define이 있다.

이런 전치리기 지시자는 #문자로 시작하고, 맨 뒤에 세미콜론을 사용하지 않는다.

전처리기 지시자설명
#include헤더 파일을 포함할 때 사용.
#define상수나 매크로를 정의하고 코드 내에서 지정한 이름으로 값을 치환.
#undef이미 정의된 매크로를 해제
#if, #elif, #else, #endif조건에 따라 컴파일
#ifdef매크로가 정의되어 있는지 확인. 정의되어 있으면 코드 블록을 컴파일.
#ifndef매크로가 정의되어 있지 않으면 코드 블록을 컴파일.

이렇게 전처리기 지시자의 종류가 있고, #include는 어떤 파일을 포함하는 전처리지 지시자이다.

#define

#define으로 시작하는 전처리 문장을 매크로라고 부르며, 크게 매크로 상수와 함수로 나뉜다.

우선 매크로 상수는 이전에 봤던 것처럼 매크로 상수에 값을 넣어주는 것이다.

1
2
3
4
5
6
#include <stdio.h>
#define PI 3.14
int main() {
    printf("%lf", PI);
    return 0;
}

이렇게 #define PI 3.14를 통해 PI를 3.14 대신 쓸 수 있다.

1
2
3
4
5
6
7
#include <stdio.h>
#define int long long
int main() {
    int a;
    printf("%d", sizeof(a));
    return 0;
}

꼭 숫자가 아니라 자료형의 이름도 바꿀 수 있다.
이렇게 int를 long long으로 정의하면 a는 long long형 변수가 되고 코드의 결과는 8이 나온다.

그렇다면 구조체에서 봤던 typedef와 #define의 차이는 무엇일까?

1
2
3
#define uchar unsigned char

typedef unsigned char uchar

두 문장은 같은 의미를 가지는 것 같다.

하지만 내부적으로 보면 typedef는 컴파일러에 의해 처리되고, #define은 전처리기에 처리된다.
그렇기에 #define으로 치환하면 uchar이 unsigned char로 전부 치환된 뒤 컴파일이 된다.
하지만 typedef는 사용자가 새로운 것을 정의한 것이기에 컴파일러가 의미를 알고 가지고 있다.

이번에는 #define을 통한 함수 매크로를 보자.
매크로 함수는 함수처럼 인자를 설정할 수 있는 매크로이지만 단순 치환이기에 실제 함수는 아니다.

1
2
3
4
5
6
#include <stdio.h>
#define MUL(a,b) a*b
int main() {
    printf("%d", MUL(3,4));
    return 0;
}

사용 방법은 간단한데, MUL(3,4)는 전처리기에 의해 3*4로 치환이 된다.
그렇기에 결과는 12가 된다. 또한 컴파일 전에 치환되기에 자료형에 상관이 없다.

1
2
3
4
5
6
#include <stdio.h>
#define MUL(a,b) a*b
int main() {
    printf("%lf", MUL(2.3,3.4));
    return 0;
}

이렇게 실수로 바꿔도 잘 계산이 된다. 만약 함수였다면 자료형을 바꿔줘야 했을 것이다.
하지만 계산을 하는 것이 아니라 치환을 하는 것이기에 문제가 생길 수도 있다.

1
2
3
4
5
6
#include <stdio.h>
#define MUL(a,b) a*b
int main() {
    printf("%d", MUL(3+2,4));
    return 0;
}

이 코드의 결과로 5*4를 해서 20을 기대하지만 결과는 11이 나온다.
그 이유는 3+2*4로 단순 치환이 되기에 3+8이 되어 11이 나오므로 주의해야 한다.

매크로 함수를 만들 때 사용할 수 있는 연산자로 #과 ##이 있다.
#연산자는 매크로 함수의 인자를 문자열로 바꾸어 준다.

1
2
3
4
5
6
7
8
#include <stdio.h>
#define MUL1(a,b) a + b
#define MUL2(a,b) #a "+" #b
int main() {
    printf("%d\n", MUL1(3,4));
    printf("%s", MUL2(3,4));
    return 0;
}
1
2
7
3+4

이렇게 a와 b를 문자열로 치환해서 출력한다. 결과는 문자열이므로 %s로 출력한다.

##연산자는 토큰 결합 연산자라고 부르며 함수 안에서 토큰을 결합한다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define OUTPUT(a,b,c) a ## b ## c
int main() {
    int a = 3;
    printf("%d\n", a);
    OUTPUT(a, =, 5);
    printf("%d\n", a);
    return 0;
}

이렇게 OUTPUT을 호출하면 a, =, 5가 각각 a, b, c에 들어가 합쳐진다.
그러면 a = 5라는 구문이 실행되고 변수 a의 값이 신기하게도 5로 변경된다.

#undef

말그대로 #define으로 정의되어 있는 것을 해제하고 치환을 중지한다.

1
2
3
4
5
6
7
#include <stdio.h>
#define PI 3.14
int main() {
    #undef PI
    printf("%lf", PI); //오류 발생
    return 0;
}

이렇게 사용하면 PI의 매크로가 사라져 오류가 발생한다.

미리 정의된 매크로

알아두면 필요할 때 쓸 수 있는 기본적으로 정의되어 있는 매크로도 있다.

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
    printf("파일 이름 : %s\n", __FILE__);
    printf("행 번호 : %d\n", __LINE__);
    printf("컴파일 날짜 : %s\n", __DATE__);
    printf("컴파일 시간 : %s\n", __TIME__);
    return 0;
}
1
2
3
4
파일 이름 : C:\Users\82102\source\repos\hello\Project3\file.c
행 번호 : 4
컴파일 날짜 : Jan 30 2025
컴파일 시간 : 02:34:40

이렇게 사용할 수 있다.

inline 함수

inline 함수란 함수 호출 시 실제 코드로 치환하도록 요청하는 함수이다.
원래 함수를 호출하면 기존 함수에서 벗어나 다른 함수로 갔다오는 과정이 생기고 자원이 소모되는데
함수를 호출하는 대신 함수 본문을 직접 코드에 삽입하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
__inline int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}
int main() {
    printf("3 과 2 중 최대값은 : %d", max(3, 2));
    return 0;
}

이렇게 __inline을 통해 만들 수 있고, 프로그램의 성능에 큰 역할을 한다.
그러나 최신 컴파일러는 자동으로 inline을 해 최적화하기 때문에 명시적으로 쓰는 경우는 드물다.


여러 잡다한 매크로 기능에 대해서 봤다.
흔히 쓰지는 않지만 알아두면 필요할 때 어딘가 쓸 곳이 있겠지?.?
이제 조건부 컴파일과 분할 컴파일에 대해 알아보자.

This post is written by PRO.