name enum ejemplo c enums constants c-preprocessor

ejemplo - ¿Es razonable usar enums en lugar de#defines para las constantes de tiempo de compilación en C?



union c (6)

Estoy volviendo a un desarrollo de C después de trabajar en C ++ por un tiempo. Me he dado cuenta de que las macros deben evitarse cuando no sean necesarias para que el compilador haga más trabajo para usted en tiempo de compilación. Por lo tanto, para valores constantes, en C ++ utilizaría variables const estáticas o clases de enumeración C ++ 11 para el buen alcance. En C, las constantes estáticas no son realmente constantes en tiempo de compilación, y las enumeraciones pueden (¿o no pueden?) Comportarse de manera ligeramente diferente.

Entonces, ¿es razonable preferir el uso de enumeraciones para constantes en lugar de #defines?

Para referencia, aquí hay una excelente lista de pros y contras de enums, #defines y static consts en C ++ .


¿Es razonable preferir usar enumeraciones para constantes en lugar de # define?

Si te gusta. Las enumeraciones se comportan como enteros.

Pero todavía preferiría constantes, en lugar de enums y macros. Las constantes proporcionan seguridad de tipo y pueden ser de cualquier tipo. Las enumeraciones solo pueden ser enteros y las macros no respetan la seguridad de tipos.

Por ejemplo :

const int MY_CONSTANT = 7;

en lugar de

#define MY_CONSTANT 7

o

enum { MY_CONSTANT = 7 };

Por cierto, mi respuesta estaba relacionada con C ++. No estoy seguro de si se aplica a C.


A const int MY_CONSTANT = 7; tomará el almacenamiento; una enumeración o #define no lo hace.

Con un #define puede usar cualquier valor (entero), por ejemplo #define IO_PORT 0xb3

Con una enumeración, dejas que el compilador asigne los números, lo que puede ser mucho más fácil si los valores no importan mucho:

enum { MENU_CHOICE_START = 1, MENU_CHOICE_NEXT, ... };


He estado trabajando en sistemas embebidos durante más de una docena de años y uso C principalmente. Mis comentarios son específicos para este campo. Hay tres formas de crear constantes que tienen implicaciones específicas para este tipo de aplicaciones.

1) #define: el preprocesador de C resuelve las macros antes de presentar el código al compilador de C. Cuando observa los archivos de encabezados proporcionados por los fabricantes de procesadores, generalmente tienen miles de macros que definen el acceso a los registros del procesador. Invoca un subconjunto de ellos en su código y se convierten en accesos de memoria en su código fuente de C. El resto desaparece y no se presentan al compilador de C.

Los valores definidos como macros se convierten en literales en C. Como tales, no producen ningún almacenamiento de datos. No hay una ubicación de memoria de datos asociada con la definición.

Las macros se pueden utilizar en la compilación condicional. Si desea eliminar el código en función de la configuración de características, debe utilizar definiciones de macro. Por ejemplo:

#if HEARTBEAT_TIMER_MS > 0 StartHeartBeatTimer(HEARTBEAT_TIMER_MS); #endif

2) Enumeraciones: al igual que las definiciones de macros, las enumeraciones no producen almacenamiento de datos. Se convierten en literales. A diferencia de las definiciones de macro, el preprocesador no las elimina. Son construcciones en lenguaje C y aparecerán en código fuente preprocesado. No se pueden utilizar para eliminar el código a través de la compilación condicional. No se pueden probar su existencia en tiempo de compilación o en tiempo de ejecución. Los valores solo pueden estar involucrados en condicionales de tiempo de ejecución como literales.

Las enumeraciones sin referencia no existirán en absoluto en el código compilado. Por otro lado, los compiladores pueden proporcionar advertencias si los valores enumerados no se manejan en una instrucción switch. Si el propósito de la constante es producir un valor que deba manejarse de manera lógica, solo una enumeración puede proporcionar el grado de seguridad que conlleva el uso de declaraciones de cambio.

Las enumeraciones también tienen una función de incremento automático, por lo tanto, si el propósito de la constante se usa como un índice constante en una matriz, siempre iré con una enumeración para evitar las ranuras no utilizadas. De hecho, la enumeración en sí misma puede producir una constante que representa una cantidad de elementos que se pueden usar en una declaración de matriz.

Dado que las enumeraciones son construcciones en lenguaje C, se evalúan definitivamente en el momento del compilador. Por ejemplo:

#define CONFIG_BIT_POS 0 #define CONFIG_BIT_MASK (1 << CONFIG_BIT_POS)

CONFIG_BIT_MASK es un sustituto de texto para (1 << CONFIG_BIT_POS). Cuando (1 << CONFIG_BIT_POS) se presenta al compilador de C, puede o no producir el literal 1.

enum { CONFIG_BIT_POS = 0, CONFIG_BIT_MASK = (1 << CONFIG_BIT_POS) };

En este caso, CONFIG_BIT_MASK se evalúa y se convierte en el valor literal 1.

Finalmente, agregaría que las definiciones de macro se pueden combinar para producir otros símbolos de código, pero no se pueden usar para crear otras definiciones de macro. Eso significa que si el nombre de la constante debe derivarse, solo puede ser una enumeración creada por una combinación de símbolos de macros o expansión de macros, como con macros de lista (macros X).

3) const: esta es una construcción en lenguaje C que hace que un valor de datos sea de solo lectura. En aplicaciones integradas, esto tiene un papel importante cuando se aplica a datos estáticos o globales: mueve los datos de la RAM a la ROM (normalmente, flash). (No tiene este efecto en las variables locales o automáticas porque se crean en la pila o en registros en tiempo de ejecución). Los compiladores de C pueden optimizarlo, pero ciertamente esto puede evitarse, por lo que aparte de esta advertencia, los datos const realmente toman almacenamiento en memoria de solo lectura en tiempo de ejecución. Eso significa que tiene tipo, que define ese almacenamiento en una ubicación conocida. Puede ser el argumento de sizeof (). Puede ser leído en tiempo de ejecución por una aplicación externa o un depurador.

Estos comentarios están dirigidos a aplicaciones integradas. Obviamente, con una aplicación de escritorio, todo está en la memoria RAM y mucho de esto realmente no se aplica. En ese contexto, const tiene más sentido.


La respuesta de TL; DR es que a menudo no importa nada si usas #define o enum .

Sin embargo, hay algunas diferencias sutiles.

El principal problema con las enumeraciones es que no puedes cambiar el tipo. Si usa constantes de enumeración como enum { FALSE, TRUE }; , entonces esas constantes siempre serán de tipo int .

Esto podría ser problemático si necesita constantes sin signo, o constantes de un tamaño diferente al tamaño de sizeof(int) . Los enteros con signo pueden causar errores sutiles si necesita realizar operaciones a nivel de bits, porque mezclar aquellos con números negativos no tiene ningún sentido en el 99% de los casos.

Sin embargo, con macros, puede especificar cualquier tipo:

#define X 0 // int type #define X 0u // unsigned int type #define X 0ul // unsigned long type #define X ((uint8_t)0) // uint8_t type

La desventaja es que no tiene la opción de definir realmente un tipo con macros, lo que podría hacer con enumeraciones. Las enumeraciones dan un poco más de seguridad de tipo, pero solo si las escribes: typedef enum {FALSE, TRUE} BOOL; . C no tiene mucha seguridad de tipos, pero los buenos compiladores o las herramientas de análisis estático externo pueden detectar y advertir sobre problemas de tipos al intentar convertir a / desde el tipo de enumeración por accidente.

Otra rareza con eso, sin embargo, es que "BOOL" es una variable de enumeración. Las variables de enumeración, a diferencia de las constantes de enumeración, no tienen garantía de a qué tipo de entero corresponden. Usted simplemente sabe que será una clase de tipo entero lo suficientemente grande para ajustarse a todos los valores de las constantes de enumeración correspondientes. Esto podría ser algo muy malo en caso de que el tamaño de la enumeración sea importante.

Y, por supuesto, los enums tienen la ventaja de que puede declararlos en el ámbito local, por lo que no debe desordenar innecesariamente el espacio de nombres global cuando no lo necesita.


Me limitaría a utilizar las características para su propósito.

Un parámetro simbólico, que toma un valor discreto entre un conjunto de alternativas, debe representarse como un miembro de la enumeración.

Un parámetro numérico, como el tamaño de la matriz o la tolerancia numérica, debe representarse como una variable constante. Desafortunadamente, C no tiene una construcción adecuada para declarar una constante de compilación (como la que tenía Pascal), y tiendo a decir que un símbolo definido es igualmente aceptable. Ahora incluso opto de manera poco ortodoxa por los símbolos definidos utilizando el mismo esquema de carcasa que otros identificadores.

Las enumeraciones con valores asignados explícitamente, como las máscaras binarias, son aún más interesantes. A riesgo de parecer exigente, consideraría utilizar constantes declaradas, como

#define IdleMask 1 #define WaitingMask 2 #define BusyMask (IdleMask | WaitingMask) enum Modes { Idle= IdleMask, Waiting= WaitingMask, Busy= BusyMask };

Dicho esto, no me importaría tanto facilitar la tarea del compilador cuando vea la facilidad con la que manejan los monstruosos fragmentos de código que reciben diariamente.


La ventaja de usar enum { FOO=34 }; En #define FOO 34 es que las macros están preprocesadas, por lo que en principio el compilador realmente no las ve (en la práctica, el compilador las ve; GCC reciente tiene una infraestructura sofisticada para proporcionar a partir de la expansión de macros un poco de árbol de sintaxis abstracta interna). viniendo).

En particular, es mucho más probable que el depurador sepa sobre FOO partir de la enum { FOO=34 }; que de #define FOO 34 (pero, de nuevo, esto no siempre es cierto en la práctica; a veces, el depurador es lo suficientemente inteligente como para poder expandir macros ...).

Por eso, prefiero enum { FOO=34 }; sobre #define FOO 34

Y también hay una ventaja de mecanografía. Podría obtener más advertencias del compilador utilizando enum color_en { WHITE, BLACK }; enum color_en color; enum color_en { WHITE, BLACK }; enum color_en color; que usar bool isblack;

Por cierto, static const int FOO=37; generalmente es conocido por el depurador pero el compilador puede optimizarlo (de modo que no se use una ubicación de memoria para él; podría ser solo un operando inmediato dentro de alguna instrucción en el código de la máquina ).