공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 매크로와 선행 처리기 

  • 선행 처리 명령문
    * define : Object-like macro
    * define : Function-like macro
    - 매크로를 두 줄에 걸쳐 정의하는 경우
    - 매크로 정의 시 먼저 정의된 매크로 사용 가능
  • 조건부 컴파일(Conditional Compilation)을 위한 매크로
    * #if... #endif : 참이라면 코드 삽입
    * #ifdef... #endif : 정의되었다면 삽입
    * #ifndef... #endif : 정의되지 않았다면 삽입
    * (#if, #ifdef, #ifndef)의 #else
    * (#if)의 #elif
  • 매개변수의 결합과 문자열화
    * 문자열 내에서 매크로의 매개변수 치환 : # 연산자
    * 필요한 형태대로 단순 결합 : 매크로 # # 연산자

 


 

선행 처리기

실행파일 생성 과정

  위의 그림에서 선행 처리는 선행 처리기에 의해, 컴파일은 컴파일러에 의해, 링크는 링커에 의해 진행된다. 컴파일 이전에 소스파일은 선행 처리과정을 거치는데 이 과정은 프로그래머가 삽입해 놓은 선행 처리 명령문대로 소스코드의 일부를 수정(대부분 단순 치환(substitution)하는 형태)하는 작업이다. (선행 처리기에 의해 변환되는 과정 자체를 가리켜 매크로 확장(macro expanision)이라고 한다.) 또한 선행 처리해도 여전히 소스파일이며, 컴파일 과정을 거쳐야 바이너리 데이터로 이루어진 오브젝트 파일이 생성된다.

###include<stdio.h>##도 ## # ##문자로 시작하는 선행 처리 명령문이다. 따라서 직접 ##stdio.h##파일을 열어 그 안에 있는 내용을 옮겨도 같은 결과가 나온다.

 

 

선행 처리 명령문

  ■ define: Object-like macro

#define [매크로] [매크로 몸체]

// 사용예시
#define PI 3.1415

  매크로 자체가 숫자나, 단순 값 등을 의미하는 등의 경우 오브젝트와 유사한 매크로(object-like macro) 또는 간단히 매크로 상수라고 한다. 매크로의 이름은 대문자로 정의해서 매크로라는 사실을 부각시키는 것이 일반적이다.

 

  • ``#define`` : 지시자라고 한다. 선행 처리기가 이 부분을 보고 프로그래머가 지시하는 바를 파악한다.
  • 매크로 : #define 지시자 뒤에 등장하는 것들을 매크로라고 부른다.
  • 매크로 몸체(대체 리스트) : 매크로를 매크로 몸체로 치환한다.

 

#include <stdio.h>

#define NAME "홍길동"	// NAME이 문자열 "홍길동"으로 대체
#define AGE	24	// AGE가 24로 대체
#define PRINT_ADDR puts("주소 : 경기도 용인시\n"); // PRINT_ADDR가 함수호출문으로 대체

int main(void)
{
	printf("이름 : %s \n", NAME);
	printf("나이 : %d \n", AGE);
	PRINT_ADDR;
	
	return 0;
}

매크로 상수를 사용하는 예시이다.

 

실행 결과

 

 

 

  ■ define: Function-like macro

#define SQUARE(X) X * X		// SQUARE(X)가 X * X로 대체
#define SQUARE(X) ((X) * (X));	// 각 전달인자를 괄호()로 묶고, 전체도 괄호로 묶어 주는 것이 좋다.

  매개변수가 존재하는 매크로도 정의 가능하다. 동작 방식이 함수와 유사하여 함수와 유사한 매크로(function-like macro)라고 하고 간단히 매크로 함수라고도 한다. 아래와 같은 장단점이 있기 때문에 보통 작은 크기의 함수나 호출의 빈도수가 높은 함수를 매크로로 정의한다.

  위의 예시의 경우 ``SQUARE(X)``와 동일한 패턴을 만나면 무조건 ``X * X``로 치환하는데, 치환이 의도한 바와 다르게 되어 다른 값이 출력된다. 매크로에 몸체 부분을 구성하는 X와 같은 전달 인자 하나하나에 괄호를 하고, 전체를 괄호로 한번 더 묶어주는 것이 좋다.

 

   매크로 함수의 장점 

  • 일반 함수에 비해 실행 속도가 빠르다.
    매크로 함수는 선행 처리에 의해 매크로 함수의 몸체 부분이 매크로 함수의 호출 문장을 대신하기 때문에 실행 속도상의 이점이 있다.
  • 자료형에 따라서 별도로 함수를 정의하지 않아도 된다.
    전달 인자의 자료형에 상관없이 제대로 치환되기 때문이다.

   매크로 함수의 단점 

  • 정의하기 까다롭다
  • 디버깅하기 쉽지 않다.
    매크로를 잘못 정의할 경우 에러 메시지는 선행 처리 이후의 소스파일을 기준으로 출력되기 때문에 일반적인 에러 메시지보다 이해하기 힘들다.

 

#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 * 3 + 2`` 가되버려서 결과값이 11이 나와버린다. @@#define SQUARE(X) ((X) * (Y))@@로 선언해야 의도하는 결과가 나온다.

 

실행결과

 

 

 

  + 매크로를 두 줄에 걸쳐 정의하는 경우

#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)	// 먼저 정의된 PRODUCT(X, Y) 사용가능

int main(void)
{
	double rad = 2.1;
	printf("반지름이 %g인 원의 넓이 : %g \n", rad, CIRCLE_AREA(rad));
	
	return 0;
}

먼저 정의된 매크로는 뒤에서 매크로를 정의할 때 사용 가능하다.

 

실행 결과

 

 

 

 

조건부 컴파일(Conditional Compilation)을 위한 매크로

  매크로 지시자 중에는 특정 조건에 따라 소스코드의 일부를 삽입하거나 삭제할 수 있도록 디자인된 지시자가 있다.

 

 

  ■ #if... #endif : 참이라면 코드 삽입

  조건부 코드 삽입을 위한 지시자이다. ``#if``문 뒤에는 반드시 ``#endif``문이 등장해야 하고, 두 지시자 사이에 존재하는 코드는 조건에 따라서 삽입 및 삭제가 된다.

 

#include <stdio.h>
#define ADD 1
#define MIN 0

int main(void)
{
	int num1, num2;
	printf("두 개의 정수 입력 : ");
	scanf("%d %d", &num1, &num2);
	
#if ADD	// ADD가 참이면
	printf("%d + %d = %d \n", num1 ,num2 ,num1 + num2);
#endif

#if MIN	// MIN이 참이면
	printf("%d - %d = %d \n", num1, num2, num1 - num2);
#endif
	
	return 0;
}

  ``#if ... #endif``를 사용하는 예시이다.

``ADD``가 ``1``이기 때문에 ``if ADD``와 ``#endif``사이에 있는 코드가 삽입되고,

``if MIN``과 ``#endif``사이의 코드는 삭제되어 결과적으로 두 정수의 덧셈이 일어난다.

 

실행 결과

 

 

 

  ■ #ifdef... #endif : 정의되었다면 삽입

  ``#ifdef``는 매크로가 정의되었느냐, 정의되지 않았느냐에 따라를 기준으로 동작한다.

 

#include <stdio.h>
// 선언했느냐가 중요하고 매크로의 값은 중요하지 않기 때문에 생략해서 정의해도 된다.(선행처리 과정에서 공백으로 대체됨(소멸됨))
// #define ADD 1
#define MIN 0

int main(void)
{
	int num1, num2;
	printf("두 개의 정수 입력 : ");
	scanf("%d %d", &num1, &num2);
	
#ifdef ADD	// 매크로 ADD가 정의되었다면
	printf("%d + %d = %d \n", num1 ,num2 ,num1 + num2);
#endif

#ifdef MIN	// 매크로 MIN이 정의되었다면 
	printf("%d - %d = %d \n", num1, num2, num1 - num2);
#endif
	
	return 0;
}

  ``#ifdef ... #endif``를 사용하는 예시이다.

``ADD``가 정의되었으면 ``#ifdef ADD``와 ``#endif``사이의 코드가 삽입된다.

 ``MIN``은 정의되지 않았기 때문에 ``#ifdef MIN``과 ``#endif``사이의 코드는 삭제된다.

 

실행 결과

 

 

 

  ■ #ifndef... #endif : 정의되지 않았다면 삽입

  ``#ifdef ... #endif``와 반대로 정의되지 않았으면 삽입된다. 헤더 파일의 중복 포함을 막기 위해 주로 사용된다.

 

 

  ■ (#if, #ifdef, #ifndef)의 #else

  ``#if``와 ``#ifdef``, ``#ifndef``문에 ``#else``문을 추가할 수 있다. 의미는 반복문 ``if``와 비슷하다.

 

#include <stdio.h>
#define HIT_NUM 5 

int main(void)
{
#if HIT_NUM == 5
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");
#else
	puts("매크로 상수 HIT_NUM은 현재 5가 아닙니다.");
#endif

	return 0;
}

``#else``를 사용하는 예시이다. 

 

실행 결과

 

 

 

  ■ (#if)의 #elif

  ``#if``문에도 ``#elif``를 여러 번 추가할 수 있고, ``#elif``의 끝을 ``#else``로 마무리할 수 있다.

 

#include <stdio.h>
#define HIT_NUM 7

int main(void)
{
#if HIT_NUM == 5
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");
#elif HIT_NUM == 6	
	puts("매크로 상수 HIT_NUM은 현재 6입니다.");
#elif HIT_NUM == 7
	puts("매크로 상수 HIT_NUM은 현재 7입니다.");
#else
	puts("매크로 상수 HIT_NUM은 5, 6, 7은 확실히 아닙니다.");
#endif
	
	return 0;
}

``#elif``를 사용하는 예시이다.

 

실행 결과

 

 

 

 

매개변수의 결합과 문자열화

  ■ 문자열 내에서 매크로의 매개변수 치환 : # 연산자

#define STR(ABC)	#ABC	// 매개변수 ABC에 전달되는 인자를 문자열 ABC로 치환
STR(123)		// "123"으로 치환
STR(12, 23, 34)	// "12, 23, 34"로 치환

// 문자열을 나란히 선언하면 하나의 문자열로 간주된다.
char * str = "ABC" "DEF";	// char * str = "ABCDEF"와 동일
char * str = STR(12) STR(34);	// char * str = "12" "34";로 치환되어 char * str = "1234";와 동일해짐

``#``연산자는 치환의 결과를 문자열로 구성하는 연산자이다. 나란히 선언하면 하나의 문자열로 간주된다. 

 

#include <stdio.h>
#define STRING_JOB(A, B) #A "의 직업은 " #B "입니다."

int main(void)
{
	printf("%s \n", STRING_JOB(이동춘, 나무꾼));
	printf("%s \n", STRING_JOB(한상순, 사냥꾼)); 
	
	return 0;
}

``#``연산자를 이용해서 매개변수를 치환하는 예시이다.

 

실행 결과

 

 

 

  ■ 필요한 형태대로 단순 결합 : 매크로 # #연산자(붙여 써야 함)

  ``# #`` 연산자는 매크로 함수의 전달 인자를 다른 대상(전달 인자, 숫자, 문자, 문자열등)과 이어 줄 때 사용한다.

(코드블럭 명령어로 지정해놔서 띄어놨는데 원래 #끼리 붙여 써야 맞는 표현이다... ㅜㅜ)

 

#include <stdio.h>
// #define STNUM(Y, S, P) YSP
#define STNUM(Y, S, P) Y # # S # # P	// Y와 S, P를 단순연결

int main(void)
{
	printf("%d \n", STNUM(10, 65, 175));
	printf("%d \n", STNUM(10, 65, 075));
	
	return 0;
}

``# #``연산자를 사용해서 매크로의 전달 인자를 단순 결합하는 예시이다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 비트연산자  (0) 2020.11.07
[C] 파일의 분할, 헤더파일  (0) 2020.06.17
[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01

+ Recent posts