75.4 스왑 매크로 정의하기

이번에는 두 변수의 값을 바꾸는 매크로를 정의하는 방법입니다. 매크로를 정의하기 전에 먼저 함수로 만들어 보겠습니다.

void swap(int *a, int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

이 함수의 단점은 바꿀 수 있는 변수의 자료형이 int로 고정되어 있다는 점입니다. 매개변수를 int 포인터 대신 void 포인터로 지정하고, 자료형의 종류를 열거형 등으로 받아도 되지만 코드가 길어집니다('63.2 void 포인터 매개변수 사용하기' 참조).

#define로 매크로를 만들 때 do while을 사용하면 모든 자료형을 만족하면서 두 변수의 값을 바꿀 수 있습니다.

swap_macro.c

#include <stdio.h>

// 매크로 안에서 변수를 선언할 수 있도록 do while 사용
// a와 b의 값을 서로 바꿈
#define SWAP(a, b, type) do { \
    type temp; \
    temp = a;  \
    a = b;     \
    b = temp;  \
} while (0)

int main()
{
    int num1 = 10;
    int num2 = 20;

    SWAP(num1, num2, int);            // 값을 바꿀 자료형으로 int를 지정
    printf("%d %d\n", num1, num2);    // 20 10: 두 변수의 값이 바뀜

    float num3 = 1.5f;
    float num4 = 3.8f;

    SWAP(num3, num4, float);          // 값을 바꿀 자료형으로 float를 지정
    printf("%f %f\n", num3, num4);    // 3.800000 1.500000: 두 변수의 값이 바뀜

    return 0;
}

실행 결과

20 10
3.800000 1.500000

#define으로 SWAP을 정의할 때 바꿀 변수가 들어갈 ab 그리고 자료형이 들어갈 type을 지정해줍니다. 함수에서는 자료형을 매개변수에 그대로 전달할 수 없지만 매크로는 전달하는 것이 아니라 지정한 코드 자체가 바뀌게 되므로 자료형도 지정할 수 있습니다.

// 매크로 안에서 변수를 선언할 수 있도록 do while 사용
// a와 b의 값을 서로 바꿈
#define SWAP(a, b, type) do { \
    type temp; \
    temp = a;  \
    a = b;     \
    b = temp;  \
} while (0)

다음은 SWAP(num1, num2, int);와 같이 int형 변수 num1, num2와 자료형 int를 지정한 뒤 전처리기를 거쳤을 때 바뀐 코드를 나타냅니다. 즉, type 부분이 자료형 int로 바뀌면서 temp 변수를 선언하게 됩니다. ab도 지정된 변수로 바뀌므로 자료형은 따지지 않아도 됩니다.

do {
//   ↓ type 부분이 자료형으로 바뀌므로 지정한 자료형으로 변수를 선언할 수 있음
    int temp;
    temp = num1;
    num1 = num2;
    num2 = temp;
} while (0);

여기서 눈 여겨볼 부분은 do while (0)입니다. 별 의미 없는 코드 같지만 실제로는 중요한 역할을 담당합니다. do while (0){ } (중괄호)로 묶여 있기 때문에 안에서 변수를 마음대로 선언할 수 있고, 선언된 변수는 do while (0)을 벗어나면 변수가 사라집니다. 즉, SWAP 매크로를 계속 사용했을 때 같은 이름으로 된 변수가 여러 개 생기지만 컴파일 에러를 방지할 수 있습니다.

다음은 SWAP(num1, num2, int);SWAP(num3, num4, float);가 전처리기를 거쳤을 때 바뀐 코드를 나타냅니다.

do {
//   ↓ 이름이 같은 변수가 두 개 선언되지만 do while (0) 안이므로 컴파일 에러가 발생하지 않음
    int temp;
    temp = num1;
    num1 = num2;
    num2 = temp;
} while (0);

do {
//    ↓ 이름이 같은 변수가 두 개 선언되지만 do while (0) 안이므로 컴파일 에러가 발생하지 않음
    float temp;
    temp = num3;
    num3 = num4;
    num4 = temp;
} while (0);

즉, 변수 temp가 두 개 선언되지만 do while (0) 안이므로 컴파일 에러가 발생하지 않습니다. 만약 SWAP 정의에서 do while (0)과 중괄호를 삭제하고 컴파일하면 temp가 재정의 되었다는 컴파일 에러가 발생하게 됩니다.

특히 매크로에서 do while (0)을 사용하면 if 조건문과 for, while 반복문에서도 문제 없이 사용할 수 있습니다.

if (num1 == 10)
    SWAP(num1, num2, int);
else
    printf("10이 아님\n");

이 코드가 전처리기를 거치면 다음과 같이 됩니다.

if (num1 == 10)
    do {
        int temp;
        temp = num1;
        num1 = num2;
        num2 = temp;
    } while(0);
else
    printf("10이 아님\n");

만약 SWAP 매크로를 do while (0) 대신 { } (중괄호)로만 정의하면 전처리기를 거쳤을 때 잘못된 코드가 나옵니다.

// 중괄호로만 매크로 정의
#define SWAP(a, b, type) { \
    type temp; \
    temp = a;  \
    a = b;     \
    b = temp;  \
}

if (num1 == 10)
    SWAP(num1, num2, int);
else
    printf("10이 아님\n");

이 코드가 전처리기를 거치면 다음과 같이 } (닫는 중괄호) 뒤에 ; (세미콜론)이 붙어서 컴파일 에러가 발생합니다.

if (num1 == 10)
    {
        int temp;
        temp = num1;
        num1 = num2;
        num2 = temp;
    };    // 세미콜론 때문에 컴파일 에러가 발생
else
    printf("10이 아님\n");