not - Buenas prácticas de programación para definiciones de macros(#define) en C
directivas de preprocesamiento c++ (11)
Por ejemplo, nunca definas una macro como esta:
#define DANGER 60 + 2
Esto puede ser potencialmente peligroso cuando hacemos una operación como esta:
int wrong_value = DANGER * 2; // Expecting 124
En su lugar, defina así porque no sabe cómo puede usarlo el usuario de la macro:
#define HARMLESS (60 + 2)
El ejemplo es trivial, pero eso explica mi pregunta. ¿Hay algún conjunto de pautas o mejores prácticas que recomiendas al escribir una macro?
¡Gracias por tu tiempo!
Para macros de líneas múltiples, use a do { } while (0)
:
#define foo(x) do { /
(x)++; /
printf("%d", x); /
} while(0)
Si hubieras hecho
#define foo(x) { /
(x)++; /
printf("%d", x); /
}
en lugar,
if (xyz)
foo(y);
else
foo(z);
habría fallado
Además, tenga cuidado al introducir variables temporales en macros:
#define foo(t) do { /
int x = (t); /
printf("%d/n", x); /
} while(0)
int x = 42;
foo(x);
imprimirá 0
y no 42
.
Si tiene una expresión complicada que necesita devolver un valor, puede usar el operador de coma:
#define allocate(foo, len) (foo->tmp = foo->head, foo->head += len, foo->tmp)
Al hacer una macro que es ejecutar su argumento y comportarse como una expresión, esto es idiomático:
#define DOIT(x) do { x } while(0)
Esta forma tiene las siguientes ventajas:
- Necesita un punto y coma de terminación
- Funciona con anidamiento y llaves, por ejemplo con if / else
En la expansión, ponga paréntesis alrededor de los argumentos, de modo que si pasan una expresión obtendrá el comportamiento deseado.
#define LESS_THAN(X,Y) (((X) < (Y) ? (X) : (Y))
Mira cómo odio esto:
void bar(void) {
if(some_cond) {
#define BAZ ...
/* some code */
#undef BAZ
}
}
Ponlos siempre así:
void bar(void) {
if(some_cond) {
#define BAZ ...
/* some code */
#undef BAZ
}
}
Respuesta a las macros MAX / MIN, tomadas de los cortes de GCC en el kernel de Linux :
#define min(x, y) ({ /
typeof(x) _min1 = (x); /
typeof(y) _min2 = (y); /
(void) (&_min1 == &_min2); /
_min1 < _min2 ? _min1 : _min2; })
Use paréntesis alrededor de la macro completa y alrededor de cada argumento al que se hace referencia en la lista de expansión:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
Evite escribir macros que evalúen sus argumentos varias veces. Tales macros no se comportarán como se espera cuando los argumentos tengan efectos secundarios:
MAX(a++, b);
Evaluará a++
dos veces si a
es mayor que b
.
Utilice los nombres MAYÚSCULAS para las macros para dejar en claro que se trata de una macro y no de una función, de modo que las diferencias se pueden considerar en consecuencia (otra buena práctica general no es pasar argumentos que tengan efectos secundarios para las funciones).
No use macros para cambiar el nombre a tipos como este:
#define pint int *
porque no se comportará como se espera cuando alguien escriba
pint a, b;
Use typedefs en su lugar.
use valores estáticos de const en lugar de macros para valores constantes, integrales u otros. El compilador a menudo puede optimizarlos, y siguen siendo ciudadanos de primera clase en el sistema de tipos de idioma.
static const int DANGER = 60 + 2;
No solo debes poner parens en torno a los argumentos, debes poner parens alrededor de la expresión devuelta.
#define MIN(a,b) a < b ? a : b // WRONG
int i = MIN(1,2); // works
int i = MIN(1,1+1); // breaks
#define MIN(a,b) (a) < (b) ? (a) : (b) // STILL WRONG
int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // breaks
#define MIN(a,b) ((a) < (b) ? (a) : (b)) // GOOD
int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // works
Sin embargo, MIN(3,i++)
todavía está roto ...
La mejor regla es usar #defines solo cuando NO FUNCIONA OTROS ENFOQUES. Sé que estás preguntando por C en lugar de C ++, pero aún así tenlo en cuenta.
Define tus macros.
Tus #defines
deben coincidir con #undef
. Esto evita que el preprocesador se obstruya y afecte a fragmentos de código involuntarios.
Si eres cuidadoso y experto, puedes cumplir con el código DRY (No repetir) usando macros como simples generadores de código. Tienes que explicarle a otros programadores lo que estás haciendo, pero puede guardar una gran cantidad de código. Por ejemplo, la técnica de macro de lista:
// define a list of variables, error messages, opcodes
// or anything that you have to write multiple things about
#define VARLIST /
DEFVAR(int, A, 1) /
DEFVAR(double, B, 2) /
DEFVAR(int, C, 3) /
// declare the variables
#define DEFVAR(typ, name, val) typ name = (val);
VARLIST
#undef DEFVAR
// write a routine to set a variable by name
void SetVar(string varname, double value){
if (0);
#define DEFVAR(typ, name, val) else if (varname == #name) name = value;
VARLIST
#undef DEFVAR
else printf("unrecognized variable %s/n", varname);
}
// write a routine to get a variable''s value, given its name
// .. you do it ..
Ahora, si desea agregar una nueva variable, eliminar una o renombrar una, se trata de una edición de 1 línea.
Utilice nombres bastante únicos para sus macros, ya que tienen alcance global y pueden chocar con cualquier cosa, por lo que:
#define MAX 10
podría chocar fácilmente con otro código, así que:
#define MYPROJECT_MAX 10
o algo aún más único, sería mejor.
He visto casos en los que un choque de este tipo no produjo un error de compilación, pero generó un código ligeramente incorrecto, por lo que puede ser bastante insidioso.