programing

매크로에서 사용할 수 있는 트릭에는 어떤 것이 있습니까?

muds 2023. 11. 7. 21:09
반응형

매크로에서 사용할 수 있는 트릭에는 어떤 것이 있습니까?

우리의 레거시 코드와 현대 코드에서 우리는 매크로를 사용하여 코드 생성 등의 훌륭한 솔루션을 수행합니다.그리고 우리는 둘 다 사용합니다.#그리고.##오퍼레이터들.

다른 개발자들이 매크로를 사용해서 멋진 일을 할 수 있는 방법이 궁금합니다.

C에서, 말 그대로의 인수를 얻는 것을 하는 매크로를 정의하는 것이 일반적이며, 동시에 그 주소를 투명하게 얻을 수 있도록 함수를 정의합니다.

// could evaluate at compile time if __builtin_sin gets
// special treatment by the compiler
#define sin(x) __builtin_sin(x)

// parentheses avoid substitution by the macro
double (sin)(double arg) {
    return sin(arg); // uses the macro
}

int main() {
    // uses the macro
    printf("%f\n", sin(3.14));

    // uses the function
    double (*x)(double) = &sin;

    // uses the function
    printf("%f\n", (sin)(3.14));
}

DRY 및 간단한 코드 생성에 유용할 수 있는 X Macro 관용구도 있습니다.

하나는 아직 정의되지 않은 매크로를 사용하여 헤더 gen.x에서 일종의 테이블을 정의합니다.

/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */
GENX( int , "y" , 1 , "number of ..." );
GENX( float , "z" , 6.3 , "this value sets ..." );
GENX( std::string , "name" , "myname" , "name of ..." );

그런 다음 각 #에 대해 정의하는 다른 위치에서 사용할 수 있습니다. 일반적으로 다른 정의를 포함합니다.

class X
{
public :

     void setDefaults()
     {
#define GENX( type , member , value , help )\
         member = value ;
#include "gen.x"
#undef GENX
     }

     void help( std::ostream & o )
     {
#define GENX( type , member , value , help )\
          o << #member << " : " << help << '\n' ;
#include "gen.x"
#undef GENX
     }

private :

#define GENX( type , member , value , help )\
     type member ;
#include "gen.x"
#undef GENX
}

디버깅을 위해 SHOW():

#define SHOW(X) cout << # X " = " << (X) << endl

인수를 확장하기 위한 이중 평가 트릭: (예: "_LINE__"이 아닌 실제 라인 번호를 사용합니다.

    /* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */
#define CONCATENATE(      x,y)  CONCATENATE_AGAIN(x,y)
#define CONCATENATE_AGAIN(x,y)  x ## y

정적 컴파일 시간 주장입니다.
예:

#define CONCATENATE_4(      a,b,c,d)  CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d)  a ## b ## c ## d

    /* Creates a typedef that's legal/illegal depending on EXPRESSION.       *
     * Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*".              *
     * (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT)                     \
  typedef char CONCATENATE_4( static_assert____,      IDENTIFIER_TEXT,  \
                              ____failed_at_line____, __LINE__ )        \
            [ (EXPRESSION) ? 1 : -1 ]

다음을 통해 사용:

typedef  int32_t  int4;

STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );

클래스 CodeLocation 인스턴스를 초기화하는 중: (파일/라인/함수를 호출하는 시점부터 저장 -- 이 작업은 매크로를 사용하거나 __FILE__/_에 직접 액세스하여 *ONLY*할 수 있습니다.소스 포인트의 LINE__/etc 매크로.)

        /* Note:  Windows may have __FUNCTION__.  C99 defines __func__. */
#define CURRENT_CODE_LOCATION()  \
           CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )

이후 MESSAGE/WARn/FAIL 매크로에서 편리한 소스-위치 인쇄 메커니즘으로 사용됩니다.예를 들어,

#define WARN_IF_NAN(X)                                      \
  do                                                        \
  {                                                         \
    if ( isnan(X) != 0 )                                    \
      WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" );  \
    if ( isinf(X) != 0 )                                    \
      WARN( # X " is INF (Floating Point INFINITY)" );      \
  } while ( false )

매크로가 없으면 주장합니다.'=='와 같은 연산자를 포함한 모든 토큰을 매크로를 통해 전달할 수 있습니다.구성 요소는 다음과 같습니다.

ASSERT( foo, ==, bar )

아니면

UNLESS( foo, >=, 0, value=0; return false; );

합법적입니다.Assert/Macro가 자동으로 CodeLocation, 스택 트레이스, 예외/코어덤핑/세출과 같은 모든 종류의 유용한 정보를 추가할 수 없는 한.


오류를 더 간단하게 만들기:

#define ERRNO_FORMAT  "errno= %d (\"%s\")"
#define ERRNO_ARGS    errno, strerror(errno)
#define ERRNO_STREAM  "errno= " << errno << " (\"" << strerror(errno) << "\") "

예: printf( "열지 못했습니다." ERNO_FORMAT, ERNO_ARGS );

가장 멋진 매크로는 주장하기, 보호자 포함하기, __FILE__, __LINE__입니다.
코드에 다른 매크로를 사용하지 마십시오.

편집:
매크로는 법률적 해결책이 없는 경우에만 사용합니다.

Boost를 보실 수 있습니다.전처리기의 흥미로운 용도를 찾기 위한 전처리기...

내가 가장 좋아하는 트릭 중 하나는 변수 개수의 인수를 매크로에 전달하는 방법인데, 나중에 printf와 같은 함수를 호출할 때 사용됩니다.이렇게 하려면 매크로에 매개 변수가 하나만 있다고 지정하고 () 없이 매크로 본문에서 사용하되 ( 및 )의 모든 매개 변수를 매크로에 전달하므로 목록이 하나의 인수처럼 보입니다.예를들면,

#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));

재미있는 일을 해준 숀 배럿에게 감사드립니다.

#ifndef blah
    #define blah(x) // something fun
    #include __FILE__
    #undef blah
#endif

#ifndef blah
    #define blah(x) // something else that is also fun
    #include __FILE__
    #undef blah
#endif

#ifdef blah
    blah(foo)
    blah(bar)
#endif

매크로를 통해 표현할 수 있는 상위 레벨 구조를 기반으로 전처리기가 코드를 생성하도록 하는 해킹 방식입니다.

내장 코드의 경우 embeddedgurus.com 의 멋진 트릭을 사용하면 이진 값을 처리할 수 있습니다.

B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93

이는 BOOST_BINARY에 대한 @Ferruccio의 이전 응답과 유사한 목표를 달성하는 것이지만, 약간의 확장이 이루어졌습니다.

코드는 다음과 같습니다(붙여넣기, 테스트 안 함, 자세한 내용은 링크 참조).

// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
  +((x&0x000000F0LU)?2:0) \
  +((x&0x00000F00LU)?4:0) \
  +((x&0x0000F000LU)?8:0) \
  +((x&0x000F0000LU)?16:0) \
  +((x&0x00F00000LU)?32:0) \
  +((x&0x0F000000LU)?64:0) \
  +((x&0xF0000000LU)?128:0)

// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
  (((unsigned long)B8(dmsb)<<24) \
  + ((unsigned long)B8(db2)<<16) \
  + ((unsigned long)B8(db3)<<8) \
  + B8(dlsb))

매크로를 좋아합니다.디버깅할 때 너무 재미있어요!

기본값이 0이 아닌 구조 리터럴(0이 아닌), C99 가변 매크로 사용

struct Example {
   int from;
   int to;
   const char *name;
}

#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})

사용.EXAMPLE(.name="test")의 명시적 재정의를 제외하고 기본값을 사용합니다.name. 이 섀도잉은 나중에 같은 멤버에 대한 언급과 함께 표준에 잘 정의되어 있습니다.

로깅은 매크로가 특히 자주 사용되는 한 곳입니다.

#define LOG(log) \
  if (!log.enabled()) {} \
  else log.getStream() << __FILE__ << "@" << __LINE__ << ": "


log_t errorlog;
...

LOG(errorlog) << "This doesn't look good:" << somedata;

디버그 음파 탐지기와 같은 것들을 릴리스 빌드에서 컴파일할 수 있게 해주는 간단한 매크로로 랩핑하는 경우가 많습니다.

#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif

나중에 사용하는 방법은 일반적으로 다음과 같습니다.

D(printf("level %d, condition %s\n", level, condition));

do{}while(0)관용구는 실수로 인해 발생할 수 있는 문제들을 피하기 위해 있습니다.D(...)조건이나 고리의 유일한 내용.결국 이런 코드가 잘못된 것을 의미하는 것은 원하지 않으실 겁니다.

for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);

만약 내가 그 경우에 오류를 던지게 할 수 있다면, 나는 그럴 것이지만, 그 전처리기는 다음을 말하기 위해 완전한 컴파일러 그 자체여야 할 것입니다.D()매크로는 루프 본체의 유일한 내용이었습니다.

저는 또한 컴파일 타임 주장의 열렬한 팬입니다.저의 제형은 약간 다르지만, 제가 본 다른 사람들에 비해 실질적인 장점은 없습니다.핵심은 주장된 조건이 거짓이면 오류를 던지는 고유한 이름의 typedef를 구성하는 것입니다.캐서트.h 우리는 다음을 가지고 있습니다.

/*! \brief Compile-time assertion.
 *
 *  Note that the cassert() macro generates no code, and hence need not
 *  be restricted to debug builds.  It does have the side-effect of
 *  declaring a type name with typedef.  For this reason, a unique
 *  number or string of legal identifier characters must be included
 *  with each invocation to avoid the attempt to redeclare a type.
 *
 *  A failed assertion will attempt to define a type that is an array
 *  of -1 integers, which will throw an error in any standards
 *  compliant compiler. The exact error is implementation defined, but
 *  since the defined type name includes the string "ASSERTION" it
 *  should trigger curiosity enough to lead the user to the assertion
 *  itself.
 *
 *  Because a typedef is used, cassert() may be used inside a function,
 *  class or struct definition as well as at file scope.
 */
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]

그리고 어떤 소스 파일에서는 어디서든 유형 디프가 합법적일 수 있습니다.

#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...

결과적인 오류 메시지는 종종 모호하지만 무차별 대입으로 공격 행을 탐지할 수 있도록 식별자 조각을 포함합니다.

저는 코드 생성 유틸리티를 작성하는 것이 선호하는 답이 될 수 있는 곳에서 전처리기를 사용한 죄를 지었습니다. 열거형 구성원의 이름에 따라 많은 보일러 플레이트를 생성하는 또 다른 답의 코드와 매우 유사합니다.그것은 C로 컴파일될 많은 메시지 디스패치 글루를 쓸 때 특히 편리합니다.

매크로를 주로 사용하는 곳은 자체 테스트 프레임워크입니다.예를 들어, 어떤 코드가 던져야 한다고 주장하고 싶을 때는 다음 매크로를 사용합니다.

#define MUST_THROW( expr )                       
  try {                                
    (expr);                              
    (myth_suite_).Fail( #expr +                    
            std::string( " should throw but didn't" ) );  
  }                                  
  catch( ... ) {                            
  }                                  

그리고 이렇게 사용합니다.

MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );

내가 그것들을 사용하는 유일한 다른 장소는 학급 선언문입니다.매크로가 있습니다.

#define CANNOT_COPY( cls )              \
  private:                              \
    cls( const cls & );                 \
    void operator=( const cls & )       \

클래스를 복사(또는 할당)할 수 없도록 지정하는 데 사용하는 항목:

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

이것은 특별한 것을 하지 않지만 사람들의 관심을 끌며 쉽게 검색될 수 있습니다.

사람은 반복되는 일을 단순화할 수 있습니다.열거형 목록

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

...그리고 나중에 구조화된 방식으로 스위치 케이스를 수행합니다.

#define TEST( _v ) \
    case k ## _v ## Enum: \
      CallFunction ## _v(); \
      break;

switch (c) {
    TEST( One   );
    TEST( Two   );
    TEST( Three );
    TEST( Four  );
}

참고: 함수 포인터 배열을 사용하여 수행할 수도 있지만 매개 변수를 추가하고 단일 해시와 함께 문자열 확장을 사용할 수 있는 유연성을 조금 더 확보할 수 있습니다.

...또는 문자열을 테스트하여 올바른 열거값을 가져옵니다.

int value = -1;
char *str = getstr();

#define TEST( _v ) \
    if (!strcmp(# _v, str)) \
        value = k ## _v ## Enum

TEST( One   );
TEST( Two   );
TEST( Three );
TEST( Four  );

CrashRpt 프로젝트에서 매크로를 넓히고 다음을 정의하는 요령이 필요합니다.

#define WIDEN2(x) L ## x 
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

매크로를 사용하여 서로 다른 데이터 유형으로 동일한 기능을 정의할 수 있습니다.예를 들어,

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#define DEFINE_BITS_STR(name, type)               \
char *bits_str_##name(type value)                 \
{                                                 \
    int len = sizeof(type) * CHAR_BIT;            \
    char *result;                                 \
    type n;                                       \
    int i;                                        \
                                                  \
    result = (char *)calloc(len+1, sizeof(type)); \
    if(result == NULL)                            \
        return NULL;                              \
                                                  \
    memset(result, '0', len);                     \
    result[len] = 0x00;                           \
                                                  \
    n = value;                                    \
    i = len;                                      \
    while(n)                                      \
    {                                             \
        if(n & 1)                                 \
            result[i] = '1';                      \
                                                  \
        n >>= 1;                                  \
        --i;                                      \
    }                                             \
                                                  \
    return result;                                \
}

DEFINE_BITS_STR(uchar, unsigned char)
DEFINE_BITS_STR(uint, unsigned int)
DEFINE_BITS_STR(int, unsigned int)

int main()
{
    unsigned char value1 = 134;
    unsigned int value2 = 232899;
    int value3 = 255;
    char *ret;

    ret = bits_str_uchar(value1);
    printf("%d: %s\n", value1, ret);

    ret = bits_str_uint(value2);
    printf("%d: %s\n", value2, ret);

    ret = bits_str_int(value3);
    printf("%d: %s\n", value3, ret);

    return 1;
}

이 예제에서는 세 가지 함수를 정의합니다(bits_str_uchar(),bits_str_uint(),bits_str_int()세 가지 다른 데이터 유형을 처리하는 ( ).unsigned char,unsigned int,int). 그러나 모두 전달된 값의 비트가 포함된 문자열을 반환합니다.

COM 서버를 구현할 때 코드에서 발생할 수 있는 모든 예외를 처리해야 합니다. COM 메서드 경계를 통해 예외를 허용하면 호출 응용 프로그램이 자주 중단됩니다.

메서드 괄호는 이 작업에 유용합니다."try"를 포함하는 매크로인 오프닝 브래킷과 "catch"를 포함하는 클로징 브래킷이 있으며 ErrorInfo로 예외를 래핑하고 HRESULT를 생성합니다.

마이크로 컨트롤러에서는 하드웨어 중단점이 많은 단점을 가지고 있기 때문에 UART를 사용하여 코드를 디버그하는 것이 일반적입니다.

이는 매우 유용한 것으로 입증된 간단한 매크로입니다.

#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X\n", #value, value);\
                         puts_UART((uint16_t *) uartTxBuf)

사용 예:

for (i=0; i < 4; i++)
{
    DEBUG_OUT(i);
    DEBUG_OUT(i % 3);
}

받은 스트림:

i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000

네, 조잡하고 안전하지 못합니다.버그가 분리될 때까지만 적용되므로 이 매크로는 해가 되지 않습니다.

대부분 (모두?)C++ Unit Testing 프레임워크는 매크로를 기반으로 합니다.UnitTest++를 사용합니다.모든 종류의 화려한 매크로를 보기 위해서 그것을 확인해 보세요.

BOOST_BINARY 매크로는 C++에게 숫자 상수를 이진법으로 표현할 수 있는 기능을 제공하기 위해 몇 가지 수준의 사전 프로세서 트릭을 수행합니다.단, 0-255로 제한됩니다.

pthreads 유틸리티 매크로는 특히 인상적인 IMHO입니다.

3GPP RRC/NBAP/RNSAP에 사용되는 것과 같은 거대한 c/c++ 중첩 구조를 작업할 때, 나는 코드를 깨끗하게 보이기 위해 이 방법을 따릅니다.

struct leve1_1
{
  int data;

  struct level2
  {
    int data;

    struct level3
    {
      int data;
    } level_3_data;

  } level_2_data;

} level_1_data;

level_1_data.data = 100;

#define LEVEL_2 leve1_1_data.level_2_data
LEVEL_2.data = 200;

#define LEVEL_3 LEVEL_2.level_3_data
LEVEL_3.data = 300;

#undef LEVEL_2
#undef LEVEL_3

이를 통해 유지보수 시간 동안 생활이 한결 수월해질 것입니다.또한 디자인 시간과 코드는 읽을 수 있을 것입니다.

타입 안전성 및 디버깅 능력을 향상시키기 위해 언어의 컨스트럭트로 변환합니다.

void _zero_or_die(int v, const char* filename, int line)
{
    if (v != 0)
    {
       fprintf(stderr, "error %s:%d\n", filename, line);
       exit(1);
    }
}

#define ZERO_OR_DIE_ for (int _i=1; _i == 1; _zero_or_die(_i, __FILE__, __LINE__)) _i=



ZERO_OR_DIE_   pipe(fd);
ZERO_OR_DIE_   close(0);
ZERO_OR_DIE_   sigaction(SIGSEGV, &sigact, NULL);
ZERO_OR_DIE_   pthread_mutex_lock(&mt);
ZERO_OR_DIE_   pthread_create(&pt, NULL, func, NULL);

저는 이걸 자주 써요.저는.debug.h헤더는 다음과 같이 정의합니다.

#ifndef DEBUG_H
#define DEBUG_H
    #ifdef DEBUG
    #define debuf if(1)
    #else
    #define debug if(0)
    #endif
#endif

그 다음:

debug {
   printf("message from debug!");
}

당신이 원한다면"message from debug!"message, 컴파일:

gcc -D DEBUG foo.c

그렇지 않으면 아무 일도 일어나지 않습니다.Gcc는 매우 똑똑한 컴파일러입니다. 만약DEBUG정의되지 않았고, 생성된if(0)(dead code)는 일부 최적화 기능을 설정한 상태에서 코드에서 제거됩니다.

더 많은 작업을 수행할 수 있습니다.

debug 
{
   pritnf("I'm in debug mode!\n");
} 
else 
{
  printf("I'm not in debug mode\n");
}

얼마 전에 D 프로그래밍 언어도 매우 유사한 기능을 제공하는 것을 보았습니다.

만약 당신이 문맥 없이 위의 것을 생각한다면, 당신은 생각을 다음과 같이 정의할 수 있습니다.

#define in_debug if(1)
#define not_debug else

그리고 나서.

in_debug {
  printf("I'm in debug mode!");
}
not_debug {
  printf("Not in debug mode!");
}

매크로에서는 텍스트 대체에 불과하기 때문에 제어 흐름을 수행하는 것이 매우 쉽습니다.다음은 for loop의 예입니다.

#include <stdio.h>

#define loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 

언급URL : https://stackoverflow.com/questions/650461/what-are-some-tricks-i-can-use-with-macros

반응형