C 전처리 기와 두 번 연결하고“arg ## _ ## MACRO”와 같이 매크로를 확장하는 방법은 무엇입니까?


152

일부 함수의 이름이 다음과 같은 매크로를 사용하여 특정 매크로 변수의 값에 의존하는 프로그램을 작성하려고합니다.

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

불행히도, 매크로 NAME()는 그것을

int some_function_VARIABLE(int a);

오히려

int some_function_3(int a);

그래서 이것은 분명히 잘못된 길입니다. 다행스럽게도 VARIABLE에 대한 가능한 값의 수가 적기 때문에 #if VARIABLE == n모든 경우를 개별적으로 나열하고 나열 할 수는 있지만 영리한 방법이 있는지 궁금합니다.


3
함수 포인터를 대신 사용하지 않겠습니까?
György Andrasek

8
@Jurily-함수 포인터는 런타임에 작동하고 전처리 기는 컴파일 시간에 작동합니다. 동일한 작업에 두 가지를 모두 사용할 수 있더라도 차이가 있습니다.
Chris Lutz

1
요점은 그것이 사용되는 것은 빠른 계산 기하학 라이브러리입니다. 이것은 특정 차원을 위해 고정 배선되어 있습니다. 그러나 때로는 누군가가 몇 가지 다른 차원 (예 : 2 및 3)으로 사용하기를 원하기 때문에 차원 종속 함수 및 유형 이름으로 코드를 생성하는 쉬운 방법이 필요합니다. 또한 코드는 ANSI C로 작성되므로 템플릿과 전문화가 포함 된 펑키 한 C ++ 항목은 여기에 적용 할 수 없습니다.
JJ.

2
이 질문은 재귀 매크로 확장에 관한 것이므로 stackingflow.com/questions/216875/using-in-macros 는 일반적인 "좋은 것"입니다. 이 질문의 제목은 더 정확해야합니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

이 예제가 최소화되기를 바랍니다. 같은 일이 발생합니다 #define A 0 \n #define M a ## A. 두 개가 ##핵심이 아닙니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

답변:


223

표준 C 전 처리기

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

두 가지 수준의 간접

다른 답변에 대한 의견에서 Cade Roux 왜 두 가지 수준의 간접 지식 이 필요한지 물었습니다 . 플립 팬트의 대답은 그것이 표준이 작동하는 방식이기 때문입니다. 문자열 화 연산자와 동등한 트릭이 필요하다는 것을 알았습니다.

C99 표준의 섹션 6.10.3은 '매크로 대체'를 다루고 6.10.3.1은 '인수 대체'를 다루고 있습니다.

함수형 매크로의 호출에 대한 인수가 식별되면 인수 대체가 수행됩니다. 교체리스트에 변수가없는 한 선행 #또는 ##전처리 또는 토큰 따르는 ##전처리 모든 매크로 내부에 전개 된 후 포함 된 (아래 참조) 토큰 대응하는 인자로 대체된다. 대체되기 전에 각 인수의 전처리 토큰은 마치 전처리 파일의 나머지 부분을 구성하는 것처럼 완전히 매크로로 대체됩니다. 사용 가능한 다른 전처리 토큰이 없습니다.

호출 NAME(mine)에서 인수는 'mine'입니다. 그것은 '광산'으로 완전히 확장되었습니다. 그런 다음 대체 문자열로 대체됩니다.

EVALUATOR(mine, VARIABLE)

이제 매크로 EVALUATOR가 발견되고 인수는 'mine'및 'VARIABLE'로 분리됩니다. 후자는 '3'으로 완전히 확장되고 대체 문자열로 대체됩니다.

PASTER(mine, 3)

이 작업은 다른 규칙 (6.10.3.3 '## 연산자')에 의해 처리됩니다.

함수와 유사한 매크로의 대체 목록에서 매개 변수 바로 앞에 또는 ##전처리 토큰이 오는 경우 해당 매개 변수는 해당 인수의 전처리 토큰 순서로 대체됩니다. [...]

객체 형 및 함수형 매크로 호출 모두에 대해 교체 용 매크로를 대체 할 추가 매크로 이름으로 교체 목록을 재검토하기 전에 교체 형 목록의 ##전처리 토큰 (인수가 아닌) 의 각 인스턴스 가 삭제되고 이전 전처리 토큰이 연결됩니다. 다음 전처리 토큰으로.

그래서, 대체 목록이 포함 x뒤에 ##또한 ##다음 y; 그래서 우리는 :

mine ## _ ## 3

##토큰을 제거하고 양쪽에 토큰을 연결하면 'mine'과 '_'및 '3'을 결합하여 다음을 생성합니다.

mine_3

원하는 결과입니다.


우리가 원래의 질문을 보면, 코드는 ( 'some_function'대신 'mine'을 사용하도록 적응되었습니다) :

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

NAME에 대한 논쟁은 분명히 '광산'이며 완전히 확장되었습니다.
6.10.3.3의 규칙에 따라 다음을 찾습니다.

mine ## _ ## VARIABLE

이는의 경우 ##사업자가 제거에 매핑 :

mine_VARIABLE

질문에보고 된대로.


전통적인 C 전 처리기

Robert Rüger 가 묻습니다 .

토큰 붙여 넣기 연산자가없는 기존 C 프리 프로세서를 사용하여이 작업을 수행 할 수있는 방법이 ##있습니까?

어쩌면 아닐 수도 있습니다. 전처리기에 따라 다릅니다. 표준 전 처리기의 장점 중 하나는이 기능이 안정적으로 작동하는 반면, 사전 표준 전처리기에는 다른 구현이 있다는 것입니다. 하나의 요구 사항은 전처리 기가 주석을 대체 할 때 ANSI 전처리 기가 요구하는대로 공간을 생성하지 않아야한다는 것입니다. GCC (6.3.0) C 전처리 기는이 요구 사항을 충족합니다. XCode 8.2.1의 Clang 전처리 기는 그렇지 않습니다.

작동하면 다음 작업을 수행합니다 ( x-paste.c).

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

사이에 공간이 아님을 참고 fun,하고 VARIABLE있는 경우,이 출력에 복사하고, 당신이 결국 때문에 중요하다 - mine_ 3물론, 구문 적으로 유효하지 않은 이름으로가. (이제 머리 좀 돌려 주 시겠어요?)

GCC 6.3.0 (실행 중 cpp -traditional x-paste.c)을 사용하면 다음을 얻습니다.

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

XCode 8.2.1의 Clang을 사용하면 다음을 얻을 수 있습니다.

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

그 공간은 모든 것을 망칩니다. 두 전처리 기가 모두 정확하다는 것을 알고 있습니다. 서로 다른 사전 표준 프리 프로세서는 두 가지 동작을 모두 나타내므로 코드를 포트하려고 할 때 토큰 붙여 넣기가 매우 성 가시고 신뢰할 수없는 프로세스가되었습니다. ##표기법 이있는 표준 은 그것을 근본적으로 단순화합니다.

다른 방법이있을 수 있습니다. 그러나 이것은 작동하지 않습니다.

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC는 다음을 생성합니다.

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

가까이 있지만 주사위는 없습니다. 물론 YMMV는 사용중인 사전 표준 전처리기에 따라 다릅니다. 솔직히, 당신이 협력하지 않는 전처리기를 고집하고 있다면, 표준보다 전 처리기 대신에 표준 C 전처리기를 사용하는 것이 더 간단 할 것입니다. 일을하는 방법을 찾기 위해 많은 시간을 보내십시오.


1
그렇습니다, 이것은 문제를 해결합니다. 나는 두 가지 수준의 재귀가있는 트릭을 알았습니다. 최소한 한 번은 스트링 화로 연주해야했지만이 작업을 수행하는 방법을 몰랐습니다.
JJ.

토큰 붙여 넣기 연산자 ##가없는 기존 C 전처리기를 사용 하여이 작업을 수행 할 수있는 방법이 있습니까?
Robert Rüger

1
@ RobertRüger : 답의 길이를 두 배로 늘리지 만 다루기 위해 정보를 추가했습니다 cpp -traditional. 결정적인 답변은 없습니다. 이는 전처리기에 따라 다릅니다.
Jonathan Leffler

답변 주셔서 감사합니다. 이건 정말 대단해! 그 동안 나는 또 다른 약간 다른 해결책을 찾았습니다. 여기를 참조 하십시오 . 또한 clang에서는 작동하지 않는다는 문제가 있습니다. 운 좋게도 그것은 내 응용 프로그램에 문제가되지 않습니다 ...
Robert Rüger

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

솔직히, 이것이 왜 작동하는지 알고 싶지 않습니다. 그것이 왜 효과가 있는지 아는 경우, 이런 종류의 일을 알고있는 직장에서 그 사람 이되어 모든 사람이 질문을 할 것입니다. =)

편집 : 실제로 왜 그것이 효과가 있는지 알고 싶다면 아무도 나를 이길 수 없다고 가정하여 행복하게 설명을 게시 할 것입니다.


왜 두 가지 수준의 간접 지시가 필요한지 설명해 주시겠습니까? 한 수준의 리디렉션으로 답변을 받았지만 Visual Studio에 C ++를 설치해야 작동하지 않아 답변을 삭제했습니다.
Cade Roux
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.