C 조건부 전 처리기 지시문에서 문자열을 비교하는 방법


92

C에서 이와 같은 작업을해야합니다. 문자를 사용하는 경우에만 작동하지만 문자열이 필요합니다. 어떻게 할 수 있습니까?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif

왜 그냥 strcmp를 사용할 수 없습니까?

@Brian : 네, 질문도 읽었습니다 :-). 그가 strcmp가 존재한다는 것을 알고 있는지 확인하고 싶었고, #define 작업을 수행하는 이유를 생각할 수 없기 때문에 응답이 깨달을 수 있습니다.

2
전 처리기뿐만 아니라 일반 코드에도 똑같은 일이 적용된다는 것을 언급하고 싶었습니다. 단순한 값으로 할 때 문자열을 사용하지 마십시오. 문자열은 정수 나 열거 형보다 훨씬 더 많은 오버 헤드를 가지고 있으며 비교하는 것보다 더 많은 작업을 수행 할 필요가 없다면 문자열이 잘못된 솔루션입니다.
swestrup

질문이 원하는 행동과 실제 행동에 대한 정보를 조금 더 포함한다면 편리 할 것입니다.
Brent Bradburn 2014 년

답변:


70

전 처리기 지시문에서 가변 길이 문자열 비교를 완전히 수행하는 방법이 없다고 생각합니다. 그래도 다음을 수행 할 수 있습니다.

#define USER_JACK 1
#define USER_QUEEN 2

#define USER USER_JACK 

#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif

또는 코드를 약간 리팩토링하고 대신 C 코드를 사용할 수 있습니다.


3
아니면 #define USER_VS (3 - USER)이 특정한 경우에 할 수 있습니다. :)
Jesse Chisholm

17

[업데이트 : 2018.05.03]

주의 사항 : 모든 컴파일러가 동일한 방식으로 C ++ 11 사양을 구현하는 것은 아닙니다. 아래 코드는 내가 테스트 한 컴파일러에서 작동하지만 많은 주석가는 다른 컴파일러를 사용했습니다.

Shafik Yaghmour의 답변에서 인용 : 컴파일 시간에 C 문자열의 길이 계산. 이것은 정말로 constexpr입니까?

상수 표현식은 컴파일 타임에 평가된다는 보장이 없습니다. 우리는 C ++ 표준 섹션 5.19 상수 표현식 초안에서 비표준적인 인용문 만 가지고 있습니다.

[...]> [참고 : 변환 중에 상수 표현식을 평가할 수 있습니다 .—end note]

그 단어 can는 세상의 모든 차이를 만듭니다.

따라서 constexpr컴파일러 작성자의 사양 해석에 따라이 (또는 모든) 답변에 대한 YMMV .

[2016.01.31 업데이트]

일부는 문자열 비교가 필요없는 목표를 달성하여 OP 의 전체 측면을 했기 때문에 이전 답변을 좋아하지 않았기 때문에 compile time string compare여기에 더 자세한 답변이 있습니다.

당신은 할 수 없습니다! C98 또는 C99에는 없습니다. C11에서도 마찬가지입니다. MACRO 조작의 양은이를 변경하지 않습니다.

에서 const-expression사용되는 정의는 #if문자열을 허용하지 않습니다.

문자를 허용하므로 문자로 제한하면 다음을 사용할 수 있습니다.

#define JACK 'J'
#define QUEEN 'Q'

#define CHOICE JACK     // or QUEEN, your choice

#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

할 수 있습니다! C ++ 11에서. 비교를 위해 컴파일 시간 도우미 함수를 정의한 경우.

// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])

#define JACK "jack"
#define QUEEN "queen"

#define USER JACK       // or QUEEN, your choice

#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

따라서 궁극적으로 USER및에 대한 최종 문자열 값을 선택하려는 목표를 달성하는 방식을 변경해야합니다 USER_VS.

C99에서는 컴파일 시간 문자열 비교를 할 수 없지만 컴파일 시간에 문자열을 선택할 수 있습니다.

컴파일 시간 스팅 비교를 수행해야하는 경우 해당 기능을 허용하는 C ++ 11 또는 최신 변형으로 변경해야합니다.

[원래 답변]

시험:

#define jack_VS queen
#define queen_VS jack

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS

// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U

업데이트 : ANSI 토큰 붙여 넣기는 때때로 명확하지 않습니다. ;-디

#매크로 앞에 단일을 넣으면 베어 값 대신 해당 값의 문자열로 변경됩니다.

##두 토큰 사이 에 double을 넣으면 토큰이 단일 토큰으로 연결됩니다.

따라서 매크로 USER_VS는 설정 방법에 따라 jack_VS또는 .queen_VSUSER

캐릭터 라인 화 매크로 S(...)명명 된 매크로의 값이 문자열로 변환됩니다 있도록 매크로 간접를 사용합니다. 매크로 이름 대신.

따라서 USER##_VS이된다 jack_VS(또는 queen_VS사용자가 설정 한 방식에 따라) USER.

나중에 stringify 매크로가 ( 이 예에서) S(USER_VS)의 값으로 사용되면 해당 값 ( )을 문자열로 변환 하는 간접 단계로 전달됩니다 .USER_VSjack_VSS_(jack_VS)queen"queen"

사용자가 설정 한 경우 USERqueen그 최종 결과는 문자열입니다 "jack".

토큰 연결에 대해서는 https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html을 참조하십시오.

토큰 문자열 변환에 대해서는 https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification을 참조하십시오.

[오타 수정을 위해 2015.02.15 업데이트 됨]


5
@JesseChisholm, C ++ 11 버전을 확인 했습니까? GCC 4.8.1, 4.9.1, 5.3.0에서 작동하지 않습니다. {{#if 0 == c_strmp / * here * / (USER, QUEEN)}}의 {{missing binary operator before token "("}}
Dmitriy Elisov

3
@JesseChisholm 그래서 나는 변경 경우 C ++ (11) 예를 컴파일 관리 #if 0 == c_strcmp( USER, JACK )constexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
드미트리 Elisov

4
@JesseChisholm, 흠, 여전히 운이 없습니다. 모든 constexpr 변수는에서 0과 같습니다 #if. USER가 JACK이기 때문에 예제가 작동합니다. 사용자가 QUEEN 있었다면, 그것은 말할 것 USER IS QUEENUSER_VS IS QUEEN
드미트리 Elisov

9
이 답변의이 C ++ 11 부분은 잘못되었습니다. constexpr전 처리기 지시문에서 함수를 호출 할 수 없습니다 .
interjay

8
이 단호한 잘못된 대답은 이미 그것을 참조한 사람을 오도했습니다. 전 처리기에서 constexpr 함수를 호출 할 수 없습니다. constexpr은 번역 단계 7까지 키워드로 인식되지 않습니다. 번역 단계 4에서 전처리가 수행됩니다.
H Walters

10

다음은 clang으로 나를 위해 일했습니다. 기호 매크로 값 비교로 나타나는 것을 허용합니다. #error xxx 는 컴파일러가 실제로하는 일을 보는 것입니다. 교체 고양이 와 정의를 #DEFINE 고양이 (A, B) B A ## 휴식 것들.

#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__

#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)

#define USER jack // jack or queen

#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif

이것이 사악한 지, 훌륭했는지, 아니면 둘 다인지는 확실하지 않지만 정확히 내가 찾고 있던 것이 었습니다. 감사합니다! 또 하나의 유용한 트릭은 1부터 시작하여 xUSER_ 매크로를 #define하는 것입니다. 그런 다음 #elsif 목록 끝에 #else 절을 ​​추가하여 USER가 처리 방법을 모르는 것으로 우연히 설정된 경우를 포착 할 수 있습니다. (그렇지 않으면 0부터 번호를
매기면

8

문자열 대신 숫자 값을 사용하십시오.

마지막으로 상수 JACK 또는 QUEEN을 문자열로 변환하려면 문자열 화 (및 / 또는 토큰 화) 연산자를 사용하십시오.


2

위에서 이미 언급했듯이 ISO-C11 전처리 기는 문자열 비교를 지원 하지 않습니다 . 그러나 "반대 값"으로 매크로를 할당하는 문제는 "토큰 붙여 넣기"및 "테이블 액세스"로 해결할 수 있습니다. Jesse의 간단한 concatenate / stringify 매크로 솔루션 은 연결 평가 전에 문자열 화가 수행 되기 때문에 gcc 5.4.0에서 실패합니다 (ISO C11 준수). 그러나 다음과 같이 수정할 수 있습니다.

#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U

#define jack_VS  queen
#define queen_VS jack

S (VS (jack))
S (jack)
S (VS (queen))
S (queen)

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
S (USER)
S (USER_VS)

(매크로 첫 번째 줄은 P_()) 다음 라인 (매크로 수 있도록 한 간접 추가 VS()) 병합을 완료 하기 전에 stringization을합니다 ( 왜 매크로에 대한 간접 더블 레이어가 필요하십니까? ). 스트링 화 매크로 ( S()S_())는 Jesse의 것입니다.

OP의 if-then-else 구성보다 유지 관리가 훨씬 쉬운 테이블 (매크로 jack_VSqueen_VS)은 Jesse에서 가져온 것입니다.

마지막으로 다음 4 줄 블록은 함수 스타일 매크로를 호출합니다. 마지막 4 줄 블록은 Jesse의 답변에서 가져온 것입니다.

코드를 저장하고 foo.c전처리기를 호출 gcc -nostdinc -E foo.c하면 다음이 생성됩니다.

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

예상대로 출력됩니다. 마지막 줄은 문자열 화 전에 USER_VS매크로가 확장 되지 않았 음을 보여줍니다 .


이것은 조건부 컴파일을 수행하기 위해 생성 된 문자열 을 실제로 비교 하려고 할 때까지 훌륭하게 작동합니다 #if (S(USER)=="jack").-나는 "-를 사용할 때 전 처리기 오류가 발생합니다 error: invalid token at start of a preprocessor expression.
ysap

1

문자열이 컴파일 시간 상수 인 경우 (귀하의 경우) 다음 트릭을 사용할 수 있습니다.

#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif

컴파일러는 strcmp의 결과를 미리 알려줄 수 있으며 strcmp를 결과로 대체하여 전 처리기 지시문과 비교할 수있는 #define을 제공합니다. 컴파일러 옵션에 대한 컴파일러 / 종속성간에 차이가 있는지 모르겠지만 GCC 4.7.2에서 저에게 효과적이었습니다.

편집 : 추가 조사 결과, 이것이 GCC 확장이 아닌 도구 체인 확장 인 것처럼 보이므로이를 고려하십시오.


7
이것은 확실히 표준 C가 아니며 어떤 컴파일러에서도 어떻게 작동하는지 모르겠습니다. 컴파일러는 때때로 식의 결과를 말할 수 있지만 (인라인 인 경우 함수 호출도) 전처리 기는 알 수 없습니다. 의 사용량가 $사전 프로세서 확장의 어떤 종류는?
ugoren 2013

3
'#if $ USER_JACK == 0'구문이 작동하는 것 같습니다. 최소한 네이티브 Android 코드 (JNI)를 빌드하는 데 사용되는 GNU C ++에서는 ... 이건 몰랐지만 매우 유용합니다. 알려 주셔서 감사합니다. 그것!
gregko

6
나는 이것을 GCC 4.9.1에서 시도했지만 이것이 당신이 생각하는 것을 할 것이라고 믿지 않습니다. 코드가 컴파일되는 동안 예상 된 결과를 제공하지 않습니다. '$'는 변수 이름으로 취급됩니다. 따라서 전처리 기는 '$ USER_JACK'변수를 찾지 않고 찾아서 기본값 0을 제공합니다. 따라서 strcmp에 관계없이 항상 USER_VS가 USER_QUEEN으로 정의됩니다.
Vitali

1

PatrickJesse Chisholm 의 답변 은 다음과 같이했습니다.

#define QUEEN 'Q'
#define JACK 'J'

#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)

#define USER 'Q'

[... later on in code ...]

#if CHECK_QUEEN(USER)
  compile_queen_func();
#elif CHECK_JACK(USER)
  compile_jack_func();
#elif
#error "unknown user"
#endif

대신에 #define USER 'Q' #define USER QUEEN 작동해야하지만 테스트되지 않았습니다. 또한 작동하며 다루기가 더 쉬울 수 있습니다.

편집 : @ Jean-François Fabre의 의견에 따르면 내 대답을 수정했습니다.


변화 (s==QUEEN?1:0)에 의해 (s==QUEEN)당신은 결과가 이미 부울이다, 삼항 필요하지 않습니다
장 - 프랑수아 파브르

0
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;

#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'

#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif

그것은 기본적으로 가변 길이 대신 수동으로 초기화되는 고정 길이 정적 문자 배열입니다.


0

USER가 따옴표로 묶인 문자열로 정의 된 경우에는 그렇게 할 수 없습니다.

하지만 USER가 JACK, QUEEN, Joker 등이라면 그렇게 할 수 있습니다 .

사용할 수있는 두 가지 트릭이 있습니다.

  1. 토큰 스 플라이 싱 : 문자를 연결하여 식별자를 다른 식별자와 결합합니다. 이것은 당신이 #define JACK무언가 를 할 필요없이 JACK과 비교할 수있게합니다
  2. 가변적 인 수의 인수로 매크로를 처리 할 수있는 가변 매크로 확장. 이를 통해 특정 식별자를 다양한 수의 쉼표로 확장하여 문자열 비교가 될 수 있습니다.

이제 다음과 같이 시작하겠습니다.

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

이제 내가 쓰고 JACK_QUEEN_OTHER(USER)USER가 JACK이면 전처리 기가 그것을 다음과 같이 바꿉니다.EXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

2 단계는 연결입니다.

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

이제 JACK_QUEEN_OTHER(USER)이된다EXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

이렇게하면 문자열이 일치하는지 여부에 따라 여러 개의 쉼표를 추가 할 수 있습니다.

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

사용자가 JACK하면 JACK_QUEEN_OTHER(USER)된다EXPANSION2(x,x,x, 1, 2, 3)

사용자가 QUEEN 인 경우, JACK_QUEEN_OTHER(USER)이된다EXPANSION2(x,x, 1, 2, 3)

사용자가 다른 경우 JACK_QUEEN_OTHER(USER)가된다EXPANSION2(ReSeRvEd_other, 1, 2, 3)

이 시점에서 중요한 일이 발생했습니다. EXPANSION2 매크로에 대한 네 번째 인수는 전달 된 원래 인수가 jack, queen 또는 다른 어떤 것인지에 따라 1, 2 또는 3입니다. 그래서 우리가해야 할 일은 그것을 골라내는 것입니다. 장기적인 이유로 마지막 단계에서 두 개의 매크로가 필요합니다. 불필요한 것처럼 보이지만 EXPANSION2 및 EXPANSION3입니다.

종합하면 다음과 같은 6 개의 매크로가 있습니다.

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

그리고 다음과 같이 사용할 수 있습니다.

int main() {
#if JACK_QUEEN_OTHER(USER) == 1
  printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
  printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
  printf("Hello, who are you?\n");
#endif
}

필수 godbolt 링크 : https://godbolt.org/z/8WGa19


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.