programing

C 조건부 프리프로세서 디렉티브 문자열 비교 방법

yoursource 2022. 8. 9. 21:55
반응형

C 조건부 프리프로세서 디렉티브 문자열 비교 방법

C에서 이런 거 해야 돼요.문자를 사용해야만 작동하는데 줄이 필요해요.이거 어떻게 해?

#define USER "jack" // jack or queen

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

프리프로세서 디렉티브에서는 가변 길이의 스트링을 완전히 비교하는 방법은 없다고 생각합니다.다음 작업을 수행할 수 있습니다.

#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코드를 사용해도 됩니다.

다음은 나에게 쨍그랑과 함께 효과가 있었다.심볼 매크로 값의 비교를 허용합니다.#error xxx는 컴파일러의 실제 기능을 확인하는 것입니다.cat 정의를 #define cat(a, b) a ##b로 바꾸면 문제가 발생합니다.

#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

문자열 대신 숫자 값을 사용합니다.

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

USER가 따옴표로 둘러싸인 문자열로 정의되어 있는 경우에는 이 작업을 수행할 수 없습니다.

하지만 USER가 JACK이나 QUEEN이나 JOKER나 뭐 그런 거라면 할 수 있어요.

다음 두 가지 방법을 사용할 수 있습니다.

  1. 토큰 스플라이싱: 문자를 연결하는 것만으로 식별자를 다른 식별자와 결합할 수 있습니다.에 의해, 「JACK」를 「JACK」로 하지 , 할 수 .#define JACK( 에)
  2. variadic macro expansion: 변수 수를 가진 매크로를 처리할 수 있습니다.이를 통해 특정 식별자를 여러 개의 쉼표로 확장하여 문자열 비교가 됩니다.

우선 다음과 같이 시작합시다.

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

그럼 ㅇㅇㅇㅇㅇㄹㄹㄹㄹㄹㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴㄴJACK_QUEEN_OTHER(USER)JACK이고 는 이를 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) becomes가 되다EXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

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

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

JACK일 JACK 입니다.JACK_QUEEN_OTHER(USER) becomes가 되다EXPANSION2(x,x,x, 1, 2, 3)

QUEEN이면 USER 'QUEEN' 。JACK_QUEEN_OTHER(USER) becomes가 되다EXPANSION2(x,x, 1, 2, 3)

USER 。JACK_QUEEN_OTHER(USER) becomes가 되다EXPANSION2(ReSeRvEd_other, 1, 2, 3)

이 시점에서 중요한 문제가 발생했습니다. EXPANSION2 매크로의 네 번째 인수는 원래 인수가 잭인지 퀸인지 또는 기타 인수에 따라 1, 2, 또는 3 중 하나입니다.그래서 우리가 해야 할 일은 그것을 고르는 것이다.장기간의 이유로 마지막 단계에는 EXPANSION2와 EXPANSION3의 2개의 매크로가 필요합니다.단, 1개는 불필요하다고 생각됩니다.

모든 것을 종합하면, 다음의 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


MSVC 업데이트:MSVC에서도 동작하도록 하려면 , 괄호를 약간 다르게 할 필요가 있습니다.EXPANSION* 매크로는 다음과 같습니다.

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

필수: https://godbolt.org/z/96Y8a1

앞에서 설명한 바와 같이 ISO-C11 프리프로세서는 문자열 비교를 지원하지 않습니다.그러나 "반대값"을 사용하여 매크로를 할당하는 문제는 "토큰 붙여넣기" 및 "테이블 액세스"로 해결할 수 있습니다.문자열화는 ISO C11에 준거한 연결 평가 전에 이루어지기 때문에 Jesse의 간단한 연결/문자열화 매크로 솔루션은 gcc 5.4.0에서 실패합니다.단, 다음과 같이 수정할 수 있습니다.

#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_()인 표현을 추가하여 다음 즉, 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은 1은VS()) 문자열화 전에 연결을 완료합니다('매크로에 이중 레이어의 인다이렉션이 필요한 이유' 참조).문자열화 매크로(S() ★★★★★★★★★★★★★★★★★」S_())는 제시의 는 제시가 보낸 것입니다.

)jack_VS ★★★★★★★★★★★★★★★★★」queen_VS제시, if-then-else, else, if-then-else, else, else, else, else, else, else, else)

마지막으로 다음 4행 블록은 함수 스타일의 매크로를 호출합니다.마지막 네 줄 블록은 제시의 대답입니다.

「 」에 를 보존합니다.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스트링화 전에는 매크로가 확장되지 않습니다.

프리 프로세서는 문자열에 대해 매우 제한적이지만 대부분의 컴파일러는 컴파일문자열에 대해서도 잘 알고 있습니다.예를 들어, 이것은 성공적으로 비교할 수 있다.__BASE_FILE__ ★★★★★★★★★★★★★★★★★」__FILE__[ ] :

const int zero_div_warning __attribute__((unused)) =
     42 / !strcmp(__FILE__ , __BASE_FILE__);

「 」를 gcc -Wno-div-by-zero -Werrc하고 .h 파일 함수한다.c " " " 、 . h " " ( " . h " ) 。 " 。 " 스 succeeds succeeds succeeds succeeds succeeds succeeds succeeds succeeds succeeds ( " . h " ) 。 " 。 " succeeds succeeds succeeds succeeds succeeds succeeds succeeds succeeds succeeds succeeds 。

-Wno-div-by-zero는 벽의 일부입니다.

이것이 특정 사용 사례를 해결하지는 못할 수도 있지만 컴파일 시 상수 문자열을 비교할 수 있는 많은 가능성을 열어줍니다.

엄밀히 말하면 이것이 OP의 질문에 대한 답변이 아니라는 것을 알고 있지만, 위의 답변을 보면 프리프로세서의 문자열 비교는 트릭이나 다른 컴파일러 고유의 마법에 의존하지 않고서는 쉬운 방법이 없다는 것을 알 수 있습니다.그래서 제 상황에 맞게 다시 생각해 보면, 프리프로세서는 정적 스트링을 사용해야 하기 때문에 실제로는 비교할 수 있는 고정 스트링 세트밖에 없다는 것을 알 수 있었습니다.따라서 코드 내의 "문자열"과 비교할 수 있는 것은 스타일리시한 것에 가깝습니다.그래서 (읽을 때) 문자열과 같은 구문을 가지지만 정수에 대한 정의만 있는 정의를 추가하기로 했습니다. 다른 사람들이 제안한 것처럼 보입니다.예를 들어 다음과 같습니다.

#if USER == USER_JACK
  // do something
#elif USER == USER_QUEEN
  // do something else
#elif USER == USER_KING
  // do something completely different
#else
  // abort abort
#end

따라서 이제 적절한 정의를 설정하는 것이 문제일 뿐입니다.

좀 더 구체적인 예로서 시리얼 시리얼라이제이션라이브러리를 사용할 때 기본 아카이브 타입을 지정할 수 있도록 문자열을 비교하려고 했습니다.시리얼에는 JSON, XML, Binary의 3가지 유효한 아카이브 타입이 있으며, 사용자가 CMake 내에서 그것들을 문자열 변수로 입력할 수 있도록 하고 싶었습니다.저는 여전히 그것을 가능하게 합니다(그리고 CMake의 CASH STRINGS 속성을 사용하여 변수를 제한합니다). 그러나 컴파일러 정의로 전달하기 전에 문자열을 정수로 변환합니다.(이것은 CMake 중심이며 원래 질문의 일부가 아니었기 때문에 미리 사과드립니다.)

CMake를 사용하여 CMake Lists에서 작업을 자동화합니다.txt 파일에는 다음 SetupCeral.cmake 스크립트가 포함되어 있습니다.

set( CEREAL_DIR "" CACHE PATH "Path to Cereal installation" )
set( CEREAL_INCLUDE_DIR ${CEREAL_DIR}/include )

# Set up the  user input variable and constrain to valid values
set( CEREAL_ARCHIVE_DEFAULT_TYPE "JSON"
    CACHE STRING
    "Default Archive type to use for Cereal serialization"
)
set_property( CACHE CEREAL_ARCHIVE_DEFAULT_TYPE
    PROPERTY STRINGS JSON XML BINARY
)

# Convert the string to integer for preprocessor comparison
if ( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "JSON")
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 0 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "XML" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 1 )
elseif( "${CEREAL_ARCHIVE_DEFAULT_TYPE}" STREQUAL "BINARY" )
  set( CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE 2 )
endif()

# Setup the corresponding preprocessor definitions
set( CEREAL_DEFINES
    -DCEREAL_ARCHIVE_JSON=0
    -DCEREAL_ARCHIVE_XML=1
    -DCEREAL_ARCHIVE_BINARY=2
    -DCEREAL_ARCHIVE_DEFAULT_TYPE=${CEREAL_ARCHIVE_DEFAULT_TYPE_VALUE}
)

그런 다음 다음과 같은 CerialArchive.hpp 헤더를 작성했습니다.

#pragma once

#if CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_JSON
#  include <cereal/archives/json.hpp>

namespace cereal
{
  using DefaultOutputArchive = JSONOutputArchive;
  using DefaultInputArchive  = JSONInputArchive;
}

#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_XML
#  include <cereal/archives/xml.hpp>

namespace cereal {
  using DefaultOutputArchive = XMLOutputArchive;
  using DefaultInputArchive  = XMLInputArchive;
} // namespace cereal

#elif CEREAL_ARCHIVE_DEFAULT_TYPE == CEREAL_ARCHIVE_BINARY
#  include <cereal/archives/binary.hpp>

namespace cereal
{
  using DefaultOutputArchive = BinaryOutputArchive;
  using DefaultInputArchive  = BinaryInputArchive;
}

#endif // CEREAL_ARCHIVE_DEFAULT_TYPE

다음으로 클라이언트 코드는 다음과 같습니다.

#include <CerealArchive.hpp>
#include <sstream>

std::ostringstream oss;
{
    cereal::DefaultOutputArchive archive( oss );
    archive( 123 );
}
std::string s = oss.str();

그런 다음 개발자는 기본 아카이브 유형을 CMake 문자열 변수로 선택할 수 있습니다(물론 다시 컴파일 후).

따라서 이 솔루션은 엄밀히 말하면 문자열을 비교하는 것이 아니라 구문적으로는 동일한 동작/외관입니다.

또한 SetupCeral.cmake는 함수에 설정을 캡슐화하기 위해 더욱 일반화되어 유사한 유형의 정의를 정의하는 다른 상황에서 사용할 수 있다고 생각합니다.

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

#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에서는 동작했습니다.

EDIT: 더 자세히 조사해보니 GCC 확장이 아닌 툴 체인 확장인 것 같으니 참고하시기 바랍니다.

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-Franchois Fabre의 코멘트에 따라 나는 내 대답을 수정했다.

[업데이트: 2021.01.04]

제가 2014년에 처음 글을 올린 이후로 바뀐 것은#pragma message.

요즘은 팔렌이 필수입니다!

#pragma message ("USER    IS " USER)
#pragma message ("USER_VS IS " USER_VS)

그러나 2016년 코드(문자 대신 문자 사용)는 VS2019에서는 문자열이 아닌 문자를 사용합니다.

그러나 @Artyer가 지적한 바와 같이 이 버전은c_strcmp는 최신 컴파일러에서는 동작하지 않습니다.

[업데이트: 2018.05.03]

경고: 모든 컴파일러가 C++11 사양을 동일한 방식으로 구현하는 것은 아닙니다.아래 코드는 테스트한 컴파일러에서 동작합니다만, 많은 코멘트가 다른 컴파일러를 사용하고 있습니다.

Shafik Yaghmour의 답변에서 인용: 컴파일 시 C 문자열의 길이를 계산합니다. 이게 정말 콘스펙스르인가요?

상수 표현은 컴파일 시에 평가될 수 있는 것이 보증되지 않으며, 초안 C++ 표준 섹션 5.19 상수 표현에서 다음과 같은 비표준 인용문만 있습니다.

[...]>[주의:상수 표현은 변환 중에 평가할 수 있습니다.: end note ]

그 말can세상을 크게 변화시킵니다.

이 (또는 모든) 답변에 대한 YMMV는constexpr컴파일러 라이터의 사양에 대한 해석에 따라 달라집니다.

[2016.01.31 갱신]

어떤 사람들은 나의 빠른 답변이 전체를 회피한다는 이유로 좋아하지 않았다.compile time string compare문자열 비교 없이 목표를 달성함으로써 OP의 양상을 확인할 수 있습니다.자세한 답변은 다음과 같습니다.

할 수 없습니다!C98 또는 C99에는 없습니다.C11에도 없어요.매크로를 아무리 조작해도 이것은 변경되지 않습니다.

의 정의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에서.비교를 위해 컴파일 시간 도우미 함수를 정의하는 경우.

[2021.01.04: CAVEAT: This does not work in any MODERN compiler. See comment by @Artyer.]

// 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 토큰 붙여넣기가 명확하지 않을 수 있습니다.|-D

을 넣다#매크로에 의해 값이 베어 값이 아닌 값의 문자열로 변경되기 전에.

★★★★★★★★★★★★★★★★★★★##2개의 토큰 사이에 있는 경우, 1개의 토큰으로 연결됩니다.

이 매크로는USER_VS되어 있다jack_VS ★★★★★★★★★★★★★★★★★」queen_VS(설정 방법에 따라)USER.

stringify 매크로S(...) 는매이이이이이이이이이이이이이이이 instead instead of of of of of of of.을 사용법

★★★★★★★★★★★★★★★★★▼USER##_VS becomes가 되다jack_VS (오류)queen_VSUSER.

나중에 stringify 매크로가 다음과 같이 사용되는 경우S(USER_VS)의 의 값USER_VS )jack_VS에서)는 스텝 「 「」의 「indirection」의 「indirection」에.S_(jack_VS)queen"queen".

「 」를 설정했을 USER로로 합니다.queen입니다."jack".

토큰 연결에 대해서는http://https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html 를 참조해 주세요.

토큰 문자열 변환에 대해서는http://https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification 를 참조해 주세요.

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

간단해 그냥 말할 수 있을 것 같아

#define NAME JACK    
#if NAME == queen 
#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

기본적으로 가변 길이의 정적 문자 배열 대신 수동으로 초기화된 고정 길이의 정적 문자 배열이 항상 끝의 늘 문자로 끝납니다.

#define USER "jack" // jack or queen
        
#ifdef USER \
  if (USER == "jack")#define USER_VS "queen" \
  else if(USER == "queen") #define USER_VS "jack"
#endif

언급URL : https://stackoverflow.com/questions/2335888/how-to-compare-strings-in-c-conditional-preprocessor-directives

반응형