# 과 ##

Programming/C / C++ 2015. 2. 7. 19:49
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

#과 ##은 전처리기의 연산자로서 컴파일러가 #define 전처리 과정에서만 사용하는 특수한 연산자이다. C 언어 자체의 연산자는 아니므로 우선 순위나 결합 규칙 등은 적용되지 않는다. 둘 다 사용 빈도가 높지는 않지만 잘 알아 두면 매크로의 활용도를 높여 반복되는 코드를 간단하게 작성할 수 있어 작업 효율 향상을 꾀할 수 있다.

# 연산자(stringizing operator)는 #define문의 인수 앞에 사용되며 피연산자를 문자열로 바꾸는 역할을 한다. 피연산자가 실인수로 치환된 후 양쪽에 따옴표를 붙여 치환된 결과 그대로 문자열 상수가 된다. 다음 예제가 이 연산자를 사용하는 가장 전형적인 예제이다.

 

  : SharpOp

#include <Turboc.h>

 

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

 

void main()

{

     result(5*3);

     result(2*(3+1));

}

 

result 매크로는 인수로 전달된 수식을 printf 함수로 출력하되 수식 자체와 수식의 평가 결과를 같이 출력한다. 실행 결과는 다음과 같다.

 

5*3=15

2*(3+1)=8

 

result(5*3) 호출문은 전처리기에 의해 다음과 같이 치환된다.

 

result(5*3)

 

printf(#5*3"=%d",(5*3)); 인수 치환

 

printf("5*3""=%d",(5*3)); # 다음의 인수가 문자열이 됨

 

printf("5*3=%d",(5*3));   인접한 문자열을 합침

 

매크로로 전달된 5*3 수식이 # 연산자에 의해 문자열로 치환되며 인접한 문자열은 합쳐지므로 5*3이라는 수식 자체가 printf의 서식 문자열의 일부가 된다. 만약 다음과 같이 매크로의 인수 자체를 문자열 내에서 직접 쓰게 되면 이 실인수는 치환되지 않고 exp라고만 출력될 뿐 호출부의 실제 수식이 출력되지 않을 것이다.

 

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

 

요컨데 #연산자는 문자열 상수 내부의 형식 인수를 실인수로 치환시킬 때 사용하는 연산자라고 할 수 있다. #연산자는 정확한 문자열 변환을 위해 몇 가지 규칙을 적용하는데 상식 수준에서 쉽게 이해되는 규칙들이다. #과 형식 인수 사이의 공백, 형식 인수 다음의 공백은 무시되므로 #exp, # exp는 동일하다. 실인수내의 공백은 하나만 인정되며 둘 이상의 공백은 하나만 남기고 모두 삭제된다. 실인수내에 주석이 있으면 이 주석은 하나의 공백으로 대체된다.

 

호출부

치환 결과

result(5*3)

5*3=15

result(5 * 3)

5 * 3=15

result(5* 3)

5* 3=15

result(5     *     3)

5 * 3=15

result(5*/*곱하기*/3);

5* 3=15

 

실인수에 겹따옴표나 역슬레쉬 등 확장열로 처리해야 할 문자가 있다면 이 문자 앞에 확장열 선두 문자인 \가 자동으로 삽입된다. #define println(msg) printf(#msg"\n") 이라는 매크로가 있을 때 이 매크로의 치환 결과는 다음과 같다.

 

호출부

치환 결과

출력 결과

println(메시지);

printf("메시지\n")

메시지

println("메시지");

printf("\"메시지\"\n")

"메시지"

println("""");

printf("\"\"\"\"\n")

""""

 

#연산자를 잘 활용하면 2진수 형태의 상수를 표기할 수 있다. C++은 8진, 10진, 16진 상수 표기법은 지원하지만 2진 상수 표기법은 지원하지 않으므로 암산을 통해 16진수로 만들어야 한다. 꼭 필요할 경우 좀 색다른 방법을 동원할 수 있는데 표준 함수중에 문자열을 수치로 변환하는 strtol 함수는 기수를 지정할 수 있다. 그래서 2진수 형태의 문자열로부터 원하는 값을 만들어 내는 것이 가능하다. 예를 들어 이진수 00110100 상수를 정의하고 싶다면 strtol("00110100",NULL,2)라고 호출하면 된다.

그런데 이 함수를 매번 호출하는 것은 무척 번거로우므로 좀 더 편리하게 사용할 수 있는 매크로 함수를 정의하고 싶다고 하자. 문제는 이 함수가 요구하는 2진 표기가 반드시 문자열이어야 한다는 점이다. 이럴 때 #연산자를 사용하면 실인수를 문자열로 바꿔 주므로 2진값을 바로 적어도 된다. 다음은 2진 상수를 표기하는 BIN 매크로이다.

 

  : BinaryConst

#include <Turboc.h>

 

#define BIN(a) strtol(#a,NULL,2)

 

void main()

{

     printf("%x\n",BIN(00010010001101001111000001011100));

}

 

BIN 매크로의 실인수로 2진수 표기를 적기만 하면 이 표기를 문자열로 바꾼 후 strtol 함수에 의해 수치값으로 변환되어 리턴될 것이다. 출력 결과는 16진수 1234f05c이며 BIN 매크로의 2진수와 같은 값이다. 이 매크로는 문자열을 거쳐 수치를 만들어 내므로 효율은 좋지 못하지만 2진수 암산이 잘 안되는 사람에게는 아주 유용하다.

## 연산자(merge operator) 역시 #define 문 내에서만 사용되며 형식 인수 사이에 위치한다. 형식 인수를 분리하여 각각 치환되도록 하며 치환 후에는 주변의 공백과 함께 사라져 두 인수의 치환 결과가 하나의 토큰으로 연결될 수 있도록 한다. 다음 예제를 보자.

 

  : SharpSharpOp

#include <Turboc.h>

 

#define var(a,b) (a##b)

 

void main()

{

     int var(Total, Score);

     TotalScore=256;

     printf("총점 = %d\n",TotalScore);

}

 

var 매크로는 두 개의 형식 인수를 받아 들여 이 두 명칭을 연결해서 하나의 명칭으로 만드는데 형식 인수 a와 b 사이에 ## 연산자가 사용되었다. 만약 ## 연산자없이 var(a,b) (ab)로 정의한다면 전처리기가 ab를 a와 b 인수가 아닌 별도의 명칭으로 인식하므로 실인수로 치환되지 못하고 그대로 ab로 남아 있을 것이다. 이 두 형식 인수가 ##에 의해 구분됨으로써 양쪽 모두 실인수로 치환되며 치환 후에 ##은 사라진다. var(Total, Score)가 치환되는 과정은 다음과 같다.

 

var(Total, Score)

 

Total##Score 인수 치환

 

TotalScore    치환 후 ##은 사라진다

 

##은 주변의 공백까지 같이 제거하므로 매크로 정의문의 ## 좌우 공백은 무시된다. (a##b)로 쓰나 (a ## b)로 쓰나 결과는 동일하다. ##은 치환전에 두 토큰을 분리하여 각 토큰이 치환될 수 있도록 구분하는 역할을 하며 치환 후에는 주변의 공백과 함께 자폭하여 두 토큰을 하나로 연결한다. 이 연산자는 주로 일괄적인 타입 정의에 사용된다.

 

  : DefineType

#include <Turboc.h>

 

#define defptype(type) typedef type *p##type

 

void main()

{

     defptype(int);

     defptype(double);

     defptype(char);

 

     pint pi;

     int i=3;

     pi=&i;

     printf("i = %d\n",*pi);

}

 

defptype 매크로는 int, double 등의 타입을 인수로 전달받으며 원래 타입앞에 p를 붙여 포인터 타입을 새로 정의한다. 예를 들어 defptype(int)는 정수형 포인터 pint를 정의하고 defptype(double)은 실수형 포인터 pdouble을 정의한다. 형식 인수 type이 매크로 호출문으로 전달된 실인수(int, double 등)으로 먼저 치환된 후 앞에 p자를 붙이기 위해 ##이 치환을 돕고 있다. 사용자 정의 타입에 대해서도 물론 사용할 수 있다.

이런 목적으로 사용되는 ##연산자는 윈도우즈의 표준 헤더 파일과 메시지 크래커, MFC 소스 코드, COM 헤더 파일에서 흔히 발견할 수 있다. 다음이 몇 가지 예이다.

 

#define OLESTR(str)     L##str

#define MAKE_ENUM(Method, Interface)    Interface##_##Method

#define HANDLE_MSG(hwnd, message, fn)    \

    case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))

#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

static const AFX_DATA CRuntimeClass class##class_name; \

 

매크로 정의문들이 하나같이 무척 복잡해 보이는데 이런 매크로의 도움으로 실제 코드는 훨씬 더 간단해질 수 있는 것이다. 이 코드들은 관련 부분에서 다시 살펴볼 기회가 있을 것이며 또한 분석해봐야 한다. 당장 이 문장들의 의미를 분석할 수는 없겠지만 장래 이 매크로를 분석해야 할 때를 대비해서 여기서는 ## 연산자의 정의와 동작에 대해서만 잘 정리해 두도록 하자.

행 계속 문자로 알려진 \도 일종의 전처리 연산자이며 자신과 뒤쪽의 개행 문자를 없는 것으로 취급하여 두 줄을 하나로 연결하는 용도로 사용한다. 이 문자가 행 끝에 올 때 자신의 뒤쪽에 있는 공백들과 개행코드까지 몽땅 제거하는 역할을 한다. 그래서 다음 문장은 두 행으로 분리되어 있지만 전처리 후에 한 문장으로 합쳐진다. 초기화할 문자열이 한 행에 다 쓸 수 없을만큼 길어질 때 줄 끝에 \를 적고 개행한 후 계속 쓰면 된다.

 

char Message[]="이 문자열은 \

아래의 문자열과 합쳐집니다.";

 

단, \ 연산자는 기계적으로 두 행을 연결할 뿐이며 다음 행의 선두에 있는 공백까지도 윗줄에 붙이기 때문에 두 번째 줄을 들여쓰기해서는 안되는 불편함이 있다. 그래서 이 방법보다는 문자열 상수를 연속으로 적는 방법이 더 편리하다. 재미있는 것은 이 연산자가 컴파일되기 전에 처리되기 때문에 명칭의 중간에도 사용할 수 있다는 점이다. printf를 pri\까지만 쓰고 다음행에 나머지 ntf를 적어도 잘 동작한다. 물론 이렇게 해야 할 이유는 없지만 전처리 과정이 컴파일 전에 수행된다는 것을 보여주는 명백한 증거로 볼 수 있다. 어쨌든 약간 재미있기는 하다.


출처 : http://www.winapi.co.kr/clec/cpp2/18-2-1.htm

'Programming > C / C++' 카테고리의 다른 글

문자열 _T("")와 L""  (0) 2015.02.07
레지스트리의 값 가져오기  (0) 2015.02.07
strncpy 와 strncpy_s 에 대한 오해  (0) 2015.02.07
함수 포인터 typedef  (0) 2015.02.07
enum, 보다 나은 enum  (0) 2015.02.07
Posted by 역시인생한방
,