Question

Dans notre code hérité, ainsi que notre code moderne, nous utilisons des macros pour exécuter des solutions astucieuses telles que des générations de code, etc. Nous utilisons à la fois # et ## opérateurs.

Je suis curieux de savoir comment les autres développeurs utilisent des macros pour faire des choses cool, s’ils les utilisent.

Était-ce utile?

La solution

En C, il est courant de définir des macros qui obtiennent l’argument mot pour mot, tout en définissant des fonctions permettant d’en obtenir l’adresse de manière transparente.

// 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));
}

Autres conseils

La macro la plus cool est la suivante: assert, include guards, __FILE__, __LINE__.
Évitez d’utiliser une autre macro dans votre code.

MODIFIER:
Utilisez des macros uniquement lorsque vous n’avez pas de solution légale.

Il existe également le langage Macro X qui peut être utile pour la génération de code DRY et simple:

On définit dans un en-tête gen.x une sorte de tableau utilisant une macro non encore définie :

/** 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 ..." );

Ensuite, il peut l'utiliser à différents endroits en le définissant pour chaque #include avec une définition généralement différente:

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
}

Vous pouvez consulter Boost.Preprocessor pour trouver de nombreuses utilisations intéressantes du préprocesseur ...

SHOW () pour le débogage:

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

La double évaluation pour développer l’argumentation des arguments: (par exemple, utilisez le numéro de ligne actuel et non "__ 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

Assertions statiques à la compilation.
Exemple:

#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 ]

Utilisé via:

typedef  int32_t  int4;

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

Initialisation d’une instance de la classe CodeLocation: (Stockage du fichier / ligne / fonction à partir du point d’invocation - vous pouvez * UNIQUEMENT le faire avec une macro ou en accédant directement aux macros __FILE __ / __ LINE __ / etc au point source. )

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

Utilisé ensuite par les macros MESSAGE / WARN / FAIL en tant que mécanisme d’impression d’emplacement source commode. Par exemple:

#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 / Unless macros. Vous pouvez transmettre n'importe quel jeton, y compris des opérateurs tels que '==', via une macro. Donc, construit comme:

ASSERT( foo, ==, bar )

Ou

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

Sont légaux. Les macros Assert / Unless peuvent automatiquement ajouter toutes sortes d'informations utiles utiles telles que CodeLocation, des traces de pile ou des exceptions / levée / sortie en douceur.

Rendre errno plus simple:

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

E.g. printf ("Échec de l'ouverture." ERRNO_FORMAT, ERRNO_ARGS);

L’un de mes trucs préférés est de passer des arguments à un nombre variable d’arguments, afin de pouvoir les utiliser ultérieurement lors de l’appel de fonctions de type printf. Pour ce faire, je spécifie que la macro n'a qu'un seul paramètre et l'utilise dans le corps de la macro without (), mais passe tous les paramètres à la macro entre (et)), de sorte que la liste ressemble à un seul argument. Par exemple,

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

La journalisation est un endroit où les macros sont particulièrement utilisées:

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


log_t errorlog;
...

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

Je attribue ce plaisir à Sean Barrett:

#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

Une méthode simple pour que le préprocesseur génère du code pour vous en fonction d'une structure de niveau supérieur que vous pouvez exprimer par le biais de macros.

Les macros se trouvent principalement dans mon propre framework de test. Par exemple, lorsque je veux affirmer qu’un code doit être lancé, j’utilise cette macro:

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

Et utilisez-le comme ceci:

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

Le seul autre endroit où je les utilise est dans les déclarations de classe. J'ai une macro:

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

que j'utilise pour spécifier qu'une classe ne peut pas être copiée (ou assignée):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

cela ne fait rien de spécial mais attire l'attention des gens et peut facilement être recherché.

Pour le code intégré, une astuce de embeddedgurus.com vous permet de gérer les valeurs binaires:

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

Ceci permet d'atteindre des objectifs similaires à ceux de la réponse précédente de @Ferruccio à propos de BOOST_BINARY, bien que légèrement élargi.

Voici le code (copier / coller, non testé, voir le lien pour plus de détails)

// 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))

J'aime les macros. Tellement de plaisir lors du débogage!

J'emballe souvent des éléments tels que le sonar de débogage dans une macro simple lui permettant d'être compilé en dehors des versions validées:

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

L'utilisation ultérieure correspond généralement à quelque chose comme:

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

Le langage do {} while (0) permet d'éviter les problèmes pouvant résulter d'une utilisation accidentelle de D (...) dans le seul contenu de une condition ou une boucle. Vous ne voulez pas que ce code veuille dire la mauvaise chose, après tout:

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

Si je pouvais faire en sorte que ce cas génère une erreur, je le ferais, mais le préprocesseur devrait être un compilateur complet lui-même pour indiquer que la macro D () était le seul contenu d'un corps de boucle. .

Je suis également un grand fan des assertions de compilation. Ma formulation est légèrement différente, mais ne présente aucun avantage réel par rapport aux autres que j'ai vues. La clé consiste à former une typedef nommée de manière unique qui renvoie une erreur si la condition affirmée est fausse et non autrement. En cassert.h nous avons:

/*! \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]

Et dans un fichier source, n'importe quel type de fichier serait légal:

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

Le message d'erreur résultant est souvent obscur, mais contient le fragment d'identifiant permettant de détecter la ligne incriminée par force brute.

J'ai été coupable d'utiliser le préprocesseur dans des endroits où l'écriture d'un utilitaire de génération de code aurait pu être la réponse préférée, un peu comme le code d'une autre réponse générant de nombreuses plaques chauffantes basées sur les parties uniques d'un membre d'enchère. prénom. Cela est particulièrement utile lorsque vous écrivez beaucoup de colle de dépêche de message à compiler en C.

Littéraux de structure avec des valeurs par défaut (non nulles) à l'aide de macros variadiques C99

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

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

using EXEMPLE (.name = "quot") utilise les valeurs par défaut, à l'exception du remplacement explicite de nom . Cette observation avec les mentions ultérieures du même membre est bien définie dans la norme.

On peut simplifier les choses répétitives, par exemple. listes d'énumération

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

... et plus tard faire un changement de cas sur une manière structurée

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

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

Remarque: Bien sûr, cela pourrait être fait avec un tableau de pointeurs de fonction, mais cela ouvre un peu plus de flexibilité pour ajouter des paramètres et également utiliser les extensions de chaîne avec le hachage unique.

... ou tester sur des chaînes pour obtenir la bonne valeur enum

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  );

Vous pouvez utiliser des macros pour définir la même fonctionnalité avec différents types de données. Par exemple:

#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;
}

Dans cet exemple, définit trois fonctions ( bits_str_uchar () , bits_str_uint () , bits_str_int () ) qui gèrent trois types de données différents ( caractère non signé , entier non signé , entier ). Cependant, tous renvoient une chaîne contenant les bits de la valeur transmise.

Lorsque vous implémentez un serveur COM, vous devez prendre en compte toutes les exceptions que votre code pourrait éventuellement émettre - le fait de laisser une exception via la limite de la méthode COM plantera souvent l'application appelante.

Les parenthèses de méthodes sont utiles pour cela. Il y a une parenthèse ouvrante qui est une macro contenant "try". et un crochet de fermeture contenant un ensemble de "captures", un encapsulage d'exceptions dans ErrorInfo et la production de HRESULT.

Dans le projet CrashRpt, il vous faut une astuce pour élargir les macros et définir:

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

La plupart (de tous?) les frameworks de test unitaire C ++ sont construits sur des macros. Nous utilisons UnitTest ++ . Consultez-le pour voir toutes sortes de macros sophistiquées.

La BOOST_BINARY effectue certaines tâches. Clé de la préprocesseur pour permettre à C ++ d’exprimer des constantes numériques en binaire Il est cependant limité à 0-255.

Les les macros de l'utilitaire pthreads sont particulièrement impressionnantes à mon humble avis.

Lorsque je travaille sur d'énormes structures imbriquées c / c ++, telles que celle utilisée pour 3GPP RRC / NBAP / RNSAP, je suis cette astuce pour que le code soit propre.

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

Cela facilitera la vie pendant la maintenance ... également pendant la conception et le code sera lisible.

Les convertir en une construction du langage pour améliorer la sécurité des types et la capacité de débogage.

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);

Sur les microcontrôleurs, il est courant de déboguer le code avec UART, car les points d'arrêt matériels présentent de nombreux inconvénients.

Il s'agit d'une macro simple qui s'est révélée très utile:

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

Exemple d'utilisation:

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

Flux reçu:

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

Oui, c'est brutal et dangereux. Il ne s'applique que jusqu'à ce que le bogue soit isolé. Cette macro ne fait donc aucun mal.

Souvent, je l'utilise. J'ai un en-tête debug.h défini comme suit:

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

et ensuite:

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

si vous voulez obtenir le message "Message de debug!", , compilez-le avec:

gcc -D DEBUG foo.c

Sinon, rien ne se passe. Gcc est un compilateur très intelligent. Si DEBUG n'est pas défini, le généré (0) (code mort) sera supprimé de votre code avec certaines optimisations.

Vous pouvez toujours faire plus:

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

Il y a quelques jours, le langage de programmation D offrait une fonctionnalité très similaire aussi.

Si vous pensez ce qui précède sans contexte, vous pouvez définir penser comme

#define in_debug if(1)
#define not_debug else

Et ensuite

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

Dans les macros, il est très facile de contrôler le flux , car il ne s'agit que d'une substitution de texte. Voici un exemple avec une boucle for:

#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;
} 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top