c++ - refrescar - ¿Cuáles son algunos trucos que puedo usar con macros?
refrescar hoja excel vba (25)
A menudo envuelvo cosas como el sonar de depuración en una simple macro que permite que se compile a partir de compilaciones de lanzamiento:
#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif
El uso posterior suele ser algo así como:
D(printf("level %d, condition %s/n", level, condition));
La expresión do{}while(0)
está ahí para evitar problemas que podrían resultar de hacer accidentalmente un uso de D(...)
el único contenido de un bucle condicional. Después de todo, no quiere que un código como este signifique lo incorrecto:
for(i=1;i<10;++i) D(printf("x[%d]=%f/n",i,x[i]));
SomeReallyExpensiveFunction(x);
Si pudiera hacer que ese caso arrojara un error, lo haría, pero el preprocesador tendría que ser un compilador completo para decir que la macro D()
era el único contenido de un cuerpo de bucle.
También soy un gran admirador de las afirmaciones en tiempo de compilación. Mi formulación es ligeramente diferente, pero no tiene ventajas reales sobre otras que he visto. La clave es formar un typedef con un nombre único que arroje un error si la condición afirmada es falsa, y no de otro modo. En cassert.h tenemos:
/*! /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]
Y en algún archivo fuente, en cualquier lugar, un typedef sería legal:
#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...
El mensaje de error resultante a menudo es oscuro, pero contendrá el fragmento de identificador que permite descubrir la línea ofensiva mediante la fuerza bruta.
He sido culpable de usar el preprocesador en lugares donde escribir la utilidad de generación de código podría haber sido la respuesta preferida, al igual que el código en otra respuesta que generó muchas placas de calderas basadas en las partes únicas del nombre de un miembro enum. Esto es especialmente útil cuando se escribe mucho pegamento de envío de mensajes para compilar en C.
En nuestro código heredado, así como en nuestro código moderno, usamos macros para realizar soluciones ingeniosas como generaciones de códigos, etc. Y utilizamos los operadores #
y ##
.
Tengo curiosidad de cómo otros desarrolladores usan macros para hacer cosas interesantes, si es que las usan.
A menudo uso esto. Tengo un encabezado debug.h
definir como el siguiente:
#ifndef DEBUG_H
#define DEBUG_H
#ifdef DEBUG
#define debuf if(1)
#else
#define debug if(0)
#endif
#endif
y entonces:
debug {
printf("message from debug!");
}
si quieres obtener "message from debug!"
mensaje, compilar con:
gcc -D DEBUG foo.c
De lo contrario, no pasa nada. Gcc es un compilador muy inteligente. Si no se define DEBUG
, el código generado if(0)
(código muerto) se eliminará de su código con algunas optimizaciones activadas.
Aún puedes hacer más:
debug
{
pritnf("I''m in debug mode!/n");
}
else
{
printf("I''m not in debug mode/n");
}
Hace algunos días vi que el lenguaje de programación D proporciona una característica muy similar también.
Si piensas lo anterior sin contexto, puedes definir think como
#define in_debug if(1)
#define not_debug else
Y entonces
in_debug {
printf("I''m in debug mode!");
}
not_debug {
printf("Not in debug mode!");
}
Construya literales con valores predeterminados (que no son cero), usando macros variadas C99
struct Example {
int from;
int to;
const char *name;
}
#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
El uso de EXAMPLE(.name="test")
utiliza los valores predeterminados, excepto la anulación explícita del name
. Este sombreado con menciones posteriores del mismo miembro está bien definido en el estándar.
Convirtiéndolos en una construcción del lenguaje para mejorar la seguridad del tipo y la capacidad de depuración.
Cuando implementa un servidor COM, debe ocuparse de todas las excepciones que su código pueda arrojar, permitiendo que una excepción a través del límite del método COM bloquee la aplicación que realiza la llamada.
Los corchetes de métodos son útiles para esto. Hay un corchete de apertura que es una macro que contiene "prueba" y un corchete de cierre que contiene un conjunto de "catch", envolviendo excepciones en ErrorInfo y produciendo HRESULT.
Cuando trabajo en enormes estructuras anidadas c / c ++ como la utilizada para 3GPP RRC / NBAP / RNSAP, sigo este truco para que el código parezca limpio.
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
Esto facilitará la vida durante el tiempo de mantenimiento. También en tiempo de diseño y código será legible.
Desde el proyecto CrashRpt, necesita un truco para ampliar macros y define:
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
El lugar principal donde uso las macros está en mi propio marco de prueba. Por ejemplo, cuando quiero afirmar que debe arrojar algún código, utilizo esta macro:
#define MUST_THROW( expr )
try {
(expr);
(myth_suite_).Fail( #expr +
std::string( " should throw but didn''t" ) );
}
catch( ... ) {
}
Y úsalo así:
MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );
El único otro lugar donde los uso es en declaraciones de clase. Tengo una macro:
#define CANNOT_COPY( cls ) /
private: /
cls( const cls & ); /
void operator=( const cls & ) /
que utilizo para especificar que una clase no se puede copiar (o asignar):
class BankAccount {
CANNOT_COPY( BankAccount );
....
};
esto no hace nada especial, pero atrae la atención de las personas y puede buscarse fácilmente.
El registro es un lugar donde las macros son particularmente utilizadas:
#define LOG(log) /
if (!log.enabled()) {} /
else log.getStream() << __FILE__ << "@" << __LINE__ << ": "
log_t errorlog;
...
LOG(errorlog) << "This doesn''t look good:" << somedata;
En C, es común definir macros que hacen algunas cosas para obtener el argumento literal, y al mismo tiempo definir funciones para poder obtener la dirección de forma 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));
}
En las macros, es muy fácil hacer un flujo de control porque es solo una sustitución de texto. Aquí hay un ejemplo con un bucle 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;
}
En los microcontroladores es común depurar el código usando UART, ya que los puntos de corte de hardware tienen muchos inconvenientes.
Esta es una macro simple que ha demostrado ser muy útil:
#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X/n", #value, value);/
puts_UART((uint16_t *) uartTxBuf)
Ejemplo de uso:
for (i=0; i < 4; i++)
{
DEBUG_OUT(i);
DEBUG_OUT(i % 3);
}
Recorrido recibido:
i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000
Sí, es crudo e inseguro. Solo se aplica hasta que el error esté aislado, por lo que esta macro no daña.
La macro BOOST_BINARY realiza algunos trucos de preprocesador inteligente para dar a C ++ la capacidad de expresar constantes numéricas en binario. Sin embargo, está limitado a 0-255.
La macro más genial es: afirmar, incluir guardias, __FILE__, __LINE__.
Evite usar otras macro en su código.
EDITAR:
Use macros solo cuando no tenga una solución legal sin ellos.
La mayoría (¿todos?) De los marcos de prueba de unidades de C ++ se basan en macros. Usamos UnitTest++ . Compruébelo para ver todo tipo de macros de fantasía.
Las macros de utilidad pthreads son particularmente impresionantes en mi humilde opinión.
Le doy el crédito a Sean Barrett por este divertido:
#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
Una forma ingeniosa de obtener el preprocesador para generar código para usted en función de una estructura de nivel superior que puede expresar a través de macros.
Para el código incrustado, un buen truco de embeddedgurus.com permite manejar valores binarios:
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
Esto logra objetivos similares a la respuesta anterior de @Ferruccio sobre BOOST_BINARY, aunque un poco expandida.
Aquí está el código (copy''n pegado, no probado, ver enlace para más detalles)
// 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))
Me gustan los macros ¡Tan divertido cuando se depura!
Puede usar macros para definir la misma funcionalidad con diferentes tipos de datos. Por ejemplo:
#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;
}
En este ejemplo, se definen tres funciones ( bits_str_uchar()
, bits_str_uint()
, bits_str_int()
) que manejan tres tipos de datos diferentes ( unsigned char
unsigned int
, unsigned int
, int
). Sin embargo, todos devuelven una cadena que contiene los bits del valor pasado.
Puedes echarle un vistazo a Boost.Preprocessor para encontrar muchos usos interesantes del preprocesador ...
SHOW () para la depuración:
#define SHOW(X) cout << # X " = " << (X) << endl
La doble evaluación para expandir el truco de los argumentos: (Por ejemplo, use el número de línea real y no "__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
Afirmaciones estáticas en tiempo de compilación.
P.ej:
#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 ]
Utilizado a través de:
typedef int32_t int4;
STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
Inicializando una instancia de la clase CodeLocation: (Almacenando archivo / línea / función desde el punto de invocación, esto * SOLO * puede hacerse con una macro o accediendo directamente a las macros __FILE __ / __ LINE / en el punto de origen).
/* Note: Windows may have __FUNCTION__. C99 defines __func__. */
#define CURRENT_CODE_LOCATION() /
CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
Posteriormente utilizado por macros de MESSAGE / WARN / FAIL como un mecanismo de impresión de ubicación de origen conveniente. Por ejemplo:
#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 / A menos que macros. Puede pasar cualquier token, incluidos operadores como ''=='', a través de una macro. Entonces construcciones como:
ASSERT( foo, ==, bar )
O
UNLESS( foo, >=, 0, value=0; return false; );
Son legales Assert / A menos que las macros puedan agregar automáticamente toda clase de información útil como CodeLocation, stack traces o lanzar excepciones / coredumping / exit con gracia.
Haciendo errno simplier:
#define ERRNO_FORMAT "errno= %d (/"%s/")"
#define ERRNO_ARGS errno, strerror(errno)
#define ERRNO_STREAM "errno= " << errno << " (/"" << strerror(errno) << "/") "
Por ejemplo, printf ("Error al abrir". ERRNO_FORMAT, ERRNO_ARGS);
También está el idioma X Macro que puede ser útil para la generación de código DRY y simple:
Uno define en un encabezado tipo de tabla gen.xa utilizando una macro aún no definida :
/** 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 ..." );
Luego puede usarlo en diferentes lugares definiéndolo para cada #include con una definición usualmente diferente:
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
}
Uno de mis trucos favoritos es una manera de pasar una cantidad variable de argumentos a las macros, para usarlas más adelante al llamar a las funciones tipo printf, por ejemplo. Para hacer esto, especifico que la macro tiene solo un parámetro y lo uso en el cuerpo de la macro sin (), pero paso todos los parámetros a la macro en ((y)), por lo que la lista parece un solo argumento. Por ejemplo,
#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s/n", "Help", "me"));
Uno puede simplificar cosas repetitivas para ie. listas enum
enum {
kOneEnum,
kTwoEnum,
kThreeEnum,
kFourEnum
};
... y luego hacer un cambio de caso de una manera estructurada
#define TEST( _v ) /
case k ## _v ## Enum: /
CallFunction ## _v(); /
break;
switch (c) {
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
}
Nota: Claro que esto podría hacerse con una matriz de punteros de funciones, pero esto abre un poco más de flexibilidad para agregar parámetros y también usa las expansiones de cadenas con el hash único.
... o para probar en cadenas para obtener el valor de enumeración correcto
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 );
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);