## 전 처리기 연산자의 응용 프로그램과 고려해야 할 사항은 무엇입니까?


88

이전의 많은 질문에서 언급했듯이 K & R을 통해 작업 중이며 현재 전처리기를 사용하고 있습니다. 더 흥미로운 것 중 하나는 이전에 C를 배우려는 시도에서 알지 못했던 것입니다. ##전 처리기 연산자입니다. K & R에 따르면 :

전 처리기 연산자 ## 는 매크로 확장 중에 실제 인수를 연결하는 방법을 제공합니다. 대체 텍스트의 매개 변수가에 인접한 ##경우 매개 변수는 실제 인수로 대체되고 ##주변 공백이 제거되고 결과가 다시 스캔됩니다. 예를 들어, 매크로 paste 는 두 개의 인수를 연결합니다.

#define paste(front, back) front ## back

그래서 paste(name, 1)토큰을 만듭니다 name1.

누군가가 실제 세계에서 이것을 어떻게 그리고 왜 사용합니까? 그 사용의 실제 예는 무엇이며 고려해야 할 사항이 있습니까?

답변:


47

CrashRpt : ##을 사용하여 매크로 멀티 바이트 문자열을 유니 코드로 변환

CrashRpt (충돌보고 라이브러리)의 흥미로운 사용법은 다음과 같습니다.

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

여기서 그들은 문자 당 1 바이트 문자열 대신 2 바이트 문자열을 사용하려고합니다. 이것은 정말 무의미한 것처럼 보이지만 정당한 이유가 있습니다.

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

날짜와 시간이 포함 된 문자열을 반환하는 다른 매크로와 함께 사용합니다.

L옆에 넣으면 __ DATE __컴파일 오류가 발생합니다.


Windows : 일반 유니 코드 또는 멀티 바이트 문자열에 ## 사용

Windows는 다음과 같은 것을 사용합니다.

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

그리고 _T코드의 모든 곳에서 사용됩니다.


깨끗한 접근 자 및 수정 자 이름을 사용하는 다양한 라이브러리 :

또한 접근 자와 수정자를 정의하기 위해 코드에서 사용되는 것을 보았습니다.

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

마찬가지로 다른 유형의 영리한 이름 생성에도 동일한 방법을 사용할 수 있습니다.


한 번에 여러 변수 선언을 만드는 데 사용하는 다양한 라이브러리 :

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

3
컴파일 타임에 문자열 리터럴을 연결할 수 있으므로 BuildDate 식을 줄여 std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); 전체 문자열을 한 번에 암시 적으로 빌드 할 수 있습니다 .
user666412

49

토큰 붙여 넣기 ( ' ##') 또는 스트링 화 ( ' #') 전처리 연산자를 사용할 때 알아야 할 한 가지는 모든 경우에 올바르게 작동하려면 추가 수준의 간접 지정을 사용해야한다는 것입니다.

이렇게하지 않고 토큰 붙여 넣기 연산자에 전달 된 항목이 매크로 자체 인 경우 원하는 결과가 아닐 수 있습니다.

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

출력 :

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

1
이 전 처리기 동작에 대한 설명은 stackoverflow.com/questions/8231966/…을
Adam Davis

@ MichaelBurr 나는 당신의 대답을 읽고 있었고 의심이 있습니다. 이 LINE 이 줄 번호를 인쇄하는 이유 는 무엇입니까?
HELP PLZ

3
@AbhimanyuAryan : 이것이 당신이 요구하는 것인지 확실하지 않지만 __LINE__, 소스 파일의 현재 줄 번호로 전처리기로 대체되는 특별한 매크로 이름입니다.
Michael Burr


14

다음은 새 버전의 컴파일러로 업그레이드 할 때 발생한 문제입니다.

토큰 붙여 넣기 연산자 ( ##)를 불필요하게 사용하면 이식이 불가능하며 원하지 않는 공백, 경고 또는 오류가 발생할 수 있습니다.

토큰 붙여 넣기 연산자의 결과가 유효한 전 처리기 토큰이 아닌 경우 토큰 붙여 넣기 연산자는 불필요하고 해로울 수 있습니다.

예를 들어, 토큰 붙여 넣기 연산자를 사용하여 컴파일 타임에 문자열 리터럴을 빌드하려고 할 수 있습니다.

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

일부 컴파일러에서는 예상되는 결과가 출력됩니다.

1+2 std::vector

다른 컴파일러에서는 원하지 않는 공백이 포함됩니다.

1 + 2 std :: vector

상당히 최신 버전의 GCC (> = 3.3 정도)는이 코드를 컴파일하지 못합니다.

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

해결책은 전 처리기 토큰을 C / C ++ 연산자에 연결할 때 토큰 붙여 넣기 연산자를 생략하는 것입니다.

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

연결에 대한 GCC CPP 문서 장 에는 토큰 붙여 넣기 연산자에 대한 더 유용한 정보가 있습니다.


고마워요-저는이 사실을 몰랐습니다 (하지만 전처리 연산자를 너무 많이 사용하지 않습니다 ...).
Michael Burr

3
이유 때문에 "토큰 붙여 넣기"연산자라고합니다. 작업이 완료되면 단일 토큰으로 끝납니다. 좋은 글입니다.
Mark Ransom

토큰 붙여 넣기 연산자의 결과가 유효한 전 처리기 토큰이 아니면 동작이 정의되지 않습니다.
alecov 2014-06-30

16 진수 부동, 또는 (C ++에서) 숫자 구분 기호 및 사용자 정의 리터럴과 같은 언어 변경은 "유효한 전처리 토큰"을 구성하는 요소를 지속적으로 변경하므로 그렇게 남용 하지 마십시오 ! (언어 고유) 토큰을 분리해야하는 경우 두 개의 개별 토큰으로 철자를 지정하고 전 처리기 문법과 고유 언어 간의 우연한 상호 작용에 의존하지 마십시오.
Kerrek SB

6

이것은 불필요하게 자신을 반복하지 않기 위해 모든 종류의 상황에서 유용합니다. 다음은 Emacs 소스 코드의 예입니다. 라이브러리에서 여러 함수를로드하려고합니다. 함수 "foo"는에 할당되어야합니다 fn_foo. 다음 매크로를 정의합니다.

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

그런 다음 사용할 수 있습니다.

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

이점은 fn_XpmFreeAttributes및 둘 다 작성하지 않아도된다는 것입니다 "XpmFreeAttributes"(그리고 둘 중 하나를 잘못 입력 할 위험이 있습니다).


4

Stack Overflow에 대한 이전 질문에서는 오류가 발생하기 쉬운 재 입력없이 열거 형 상수에 대한 문자열 표현을 생성하는 부드러운 방법을 요청했습니다.

링크

그 질문에 대한 나의 대답은 작은 전 처리기 마법을 적용하면 어떻게 당신이 다음과 같이 열거를 정의 할 수 있는지를 보여주었습니다 (예를 들어) ...;

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

... 매크로 확장은 열거 형 (.h 파일)을 정의 할뿐만 아니라 일치하는 문자열 배열 (.c 파일)도 정의한다는 이점이 있습니다.

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

문자열 테이블의 이름은 ## 연산자를 사용하여 매크로 매개 변수 (예 : Color)를 StringTable에 붙여 넣은 것입니다. 이와 같은 응용 프로그램 (트릭?)은 # 및 ## 연산자가 중요한 곳입니다.


3

매크로 매개 변수를 다른 것과 연결해야 할 때 토큰 붙여 넣기를 사용할 수 있습니다.

템플릿에 사용할 수 있습니다.

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

이 경우 LINKED_LIST (int)는

struct list_int {
int value;
struct list_int *next;
};

마찬가지로 목록 순회를위한 함수 템플릿을 작성할 수 있습니다.


2

나는 C 프로그램에서 이것을 사용하여 일종의 호출 규칙을 준수해야하는 일련의 메서드에 대한 프로토 타입을 올바르게 적용하는 데 도움을줍니다. 어떤 의미에서 이것은 직선 C에서 가난한 사람의 객체 방향에 사용할 수 있습니다.

SCREEN_HANDLER( activeCall )

다음과 같이 확장됩니다.

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

이렇게하면 다음과 같은 경우 모든 "파생 된"개체에 대해 올바른 매개 변수화가 적용됩니다.

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

헤더 파일 등에 위의 내용이 있습니다. 정의를 변경하거나 "객체"에 메서드를 추가하려는 경우에도 유지 관리에 유용합니다.


2

SGlib 는 기본적으로 C에서 템플릿을 퍼지하기 위해 ##을 사용합니다. 함수 오버로딩이 없기 때문에 ##은 생성 된 함수의 이름에 유형 이름을 붙이는 데 사용됩니다. list_t라는 목록 유형이 있으면 sglib_list_t_concat 등과 같은 이름의 함수를 얻게됩니다.


2

임베디드 용 비표준 C 컴파일러에서 홈 롤링 어설 션에 사용합니다.



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 



3
컴파일러가 문자열 붙여 넣기를하지 않고 토큰 붙여 넣기를 수행했다는 '비표준'을 의미한다고 생각합니다. 아니면 없이도 작동했을 ##까요?
PJTraill

1

매크로로 정의 된 변수에 사용자 지정 접두사를 추가하는 데 사용합니다. 그래서 다음과 같습니다.

UNITTEST(test_name)

확장 :

void __testframework_test_name ()

1

주된 용도는 명명 규칙이 있고 매크로에서 해당 명명 규칙을 활용하려는 경우입니다. 아마도 image_create (), image_activate (), image_release (), file_create (), file_activate (), file_release (), mobile_create (), mobile_activate () 및 mobile_release ()와 같은 몇 가지 메서드 계열이있을 수 있습니다.

객체 수명주기를 처리하기위한 매크로를 작성할 수 있습니다.

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

물론, 일종의 "최소한의 객체 버전"이 적용되는 유일한 명명 규칙은 아닙니다. 거의 대부분의 명명 규칙은 공통 하위 문자열을 사용하여 이름을 형성합니다. 함수 이름 (위와 같음), 필드 이름, 변수 이름 또는 그 밖의 모든 것이 될 수 있습니다.


1

WinCE에서 한 가지 중요한 용도 :

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

레지스터 비트 설명을 정의하는 동안 다음을 수행합니다.

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

BITFMASK를 사용하는 동안 다음을 사용하십시오.

BITFMASK(ADDR)

0

로깅에 매우 유용합니다. 넌 할 수있어:

#define LOG(msg) log_msg(__function__, ## msg)

또는 컴파일러가 functionfunc를 지원하지 않는 경우 :

#define LOG(msg) log_msg(__file__, __line__, ## msg)

위의 "기능"은 메시지를 기록하고 정확히 어떤 기능이 메시지를 기록했는지 보여줍니다.

내 C ++ 구문이 정확하지 않을 수 있습니다.


1
그걸로 뭘하려고 했어요? ","를 "msg"에 토큰으로 붙여 넣을 필요가 없기 때문에 "##"없이도 잘 작동합니다. 메시지를 문자열 화하려고 했습니까? 또한 FILELINE 은 소문자가 아닌 대문자 여야합니다.
bk1e

당신 말이 맞아요. ##이 어떻게 사용되었는지 보려면 원본 스크립트를 찾아야합니다. 부끄러워, 오늘은 쿠키가 없어!
ya23
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.