2010-08-08 16 views
5

Como se ha discutido en varias preguntas recientes, declarar const -variables calificadas en C (en oposición a const variables en C++, o punteros a const en C) generalmente sirve para muy poco propósito. Lo más importante es que no se pueden usar en las expresiones constantes .¿Cuáles son algunos usos prácticos para las variables const const en C?

Dicho esto, ¿cuáles son algunos usos legítimos de const variables calificadas en C? Puedo pensar en algunos que han aparecido recientemente en el código con el que he trabajado, pero seguramente debe haber otros. Aquí está mi lista:

  • usando sus direcciones como valores especiales centinela para un puntero, así como nunca comparar igual a cualquier otro puntero. Por ejemplo: char *sentinel(void) { static const char s; return &s; } o simplemente const char sentinel[1]; Dado que solo nos importa la dirección y en realidad no importaría si el objeto se escribió, el único beneficio de const es que los compiladores generalmente lo almacenan en la memoria de solo lectura respaldada por mmap de el archivo ejecutable o una copia de la página cero.

  • Utilizando const variables calificadas para exportar valores de una biblioteca (especialmente bibliotecas compartidas), cuando los valores podrían cambiar con las nuevas versiones de la biblioteca. En tales casos, simplemente usar #define en el encabezado de la interfaz de la biblioteca no sería un buen enfoque porque haría que la aplicación dependa de los valores de las constantes en la versión particular de la biblioteca con la que se creó.

  • estrechamente relacionado con el uso previo, a veces desea exponer objetos predefinidos de una biblioteca para la aplicación (los ejemplos por excelencia siendo stdin, stdout y stderr de la librería estándar). Con ese ejemplo, extern FILE __stdin; #define stdin (&__stdin) sería una implementación muy mala debido a la forma en que la mayoría de los sistemas implementan bibliotecas compartidas; generalmente requieren que se copie el objeto completo (aquí, FILE) a una dirección determinada cuando la aplicación está vinculada e introducen una dependencia en el tamaño del objeto (el programa se romperá si la biblioteca se reconstruye y el tamaño del objeto cambia). El uso de un puntero const (no puntero a const) aquí corrige todos los problemas: extern FILE *const stdin;, donde el puntero const se inicializa para apuntar al objeto predefinido (que probablemente se declare static) en algún lugar interno de la biblioteca.

  • Tablas de búsqueda para funciones matemáticas, propiedades de caracteres, etc. Este es el obvio que olvidé incluir originalmente, probablemente porque estaba pensando en variables individuales de tipo aritmético/puntero, ya que ahí fue donde surgió el tema de pregunta arriba. Gracias a Aidan por haberme hecho recordar.

  • Como una variante en tablas de búsqueda, implementación de máquinas de estados. Aidan proporcionó un ejemplo detallado como respuesta. He encontrado que el mismo concepto también suele ser muy útil sin ningún puntero a la función, si puede codificar el comportamiento/las transiciones de cada estado en términos de unos pocos parámetros numéricos.

Alguien más tiene algunos usos inteligentes y prácticas para las variables const Calificado en C?

Respuesta

6

const se usa con bastante frecuencia en la programación integrada para mapear en las clavijas GPIO de un microcontrolador.Por ejemplo:

 
typedef unsigned char const volatile * const tInPort; 
typedef unsigned char    * const tOutPort; 

tInPort my_input = (tInPort)0x00FA; 
tOutPort my_output = (tOutPort)0x00FC; 

Ambos impiden que el programador que modifique accidentalmente el propio puntero que podría ser desastroso en algunos casos. La declaración tInPort también evita que el programador cambie el valor de entrada.

El uso de volatile impide que el compilador suponga que el valor más reciente para my_input existirá en la caché. Entonces, cualquier lectura del my_input irá directamente al bus y, por lo tanto, siempre leerá desde los pines IO del dispositivo.

+1

Mientras esto funciona, no veo la ventaja sobre '#define my_inport (* (unsigned char const volátil *) 0x00FA)' y luego solo puedo hacer 'c = my_inport;' Mi versión probablemente se compilaría para código más pequeño/más eficiente porque es una expresión constante, mientras que la versión con las variables 'const' en realidad podría cargar la dirección indirectamente cada vez que se usa. –

+4

Este es básicamente el argumento antiguo de las constantes '# define' contra las constantes' const'. En general, ninguno es mejor o peor que el otro. Es solo que uno es un constructo de lenguaje y uno es un comando de preprocesador. A menudo se pasa por alto que un lenguaje de programación no es más que un conjunto de instrucciones para que un programa interprete. En el caso de C, el programa es un compilador cuyo trabajo es producir un código de ensamblaje optimizado. Entonces, usar construcciones de lenguaje debería proporcionarle más información y permitirle producir un resultado más relevante. Sin embargo, esto se pierde con '# define's. – DuFace

1
  • Una variable const es útil cuando el tipo no es uno que tiene literales utilizables, es decir, que no sea un número nada. Para los punteros, ya da un ejemplo (stdin y co) donde podría usar #define, pero obtendría un valor l que podría asignarse fácilmente. Otro ejemplo son los tipos struct y union, para los cuales no hay literales asignables (solo los inicializadores). Considere, por ejemplo, una aplicación C89 razonable de los números complejos:

    typedef struct {double Re; double Im;} Complex; 
    const Complex Complex_0 = {0, 0}; 
    const Complex Complex_I = {0, 1}; /* etc. */ 
    
  • A veces sólo necesita tener un objeto almacenado y no un literal, porque es necesario para pasar los datos a una función polimórfica que espera un void* y una size_t. He aquí un ejemplo de la cryptoki API (también conocido como PKCS # 11): muchas funciones requieren una lista de argumentos pasados ​​como una matriz de CK_ATTRIBUTE, que se define básicamente como

    typedef struct { 
        CK_ATTRIBUTE_TYPE type; 
        void *pValue; 
        unsigned long ulValueLen; 
    } CK_ATTRIBUTE; 
    typedef unsigned char CK_BBOOL; 
    

    por lo que en su aplicación, por un valioso booleano- atributo que necesita para pasar un puntero a un byte que contiene 0 ó 1:

    CK_BBOOL ck_false = 0; 
    CK_ATTRIBUTE template[] = { 
        {CKA_PRIVATE, &ck_false, sizeof(ck_false)}, 
    ... }; 
    
+0

@R ..: pero las expresiones constantes ('# define'd, supongo?) Solo son posibles en casos muy específicos. – Gilles

+0

He mencionado los únicos casos en los que puedo pensar que '# define 'no funcionaría mejor en la pregunta. ¿Puedes pensar en algunos de los que me estoy perdiendo? Eso es lo que estoy buscando. –

+0

@R ..: ok, entiendo tu enfoque mejor ahora, he reescrito mi respuesta con ejemplos. – Gilles

1

const puede ser útil para algunos casos en que se utilizan los datos de código directa de una manera específica. Por ejemplo, aquí hay un patrón de uso al escribir las máquinas de estado:

typedef enum { STATE1, STATE2, STATE3 } FsmState; 
struct { 
    FsmState State; 
    int (*Callback)(void *Arg); 
} const FsmCallbacks[] = { 
    { STATE1, State1Callback }, 
    { STATE2, State2Callback }, 
    { STATE3, State3Callback } 
}; 

int dispatch(FsmState State, void *Arg) { 
    int Index; 
    for(Index = 0; Index < sizeof(FsmCallbacks)/sizeof(FsmCallbacks[0]); Index++) 
     if(FsmCallbacks[Index].State == State) 
      return (*FsmCallbacks[Index].Callback)(Arg); 
} 

Esto es análogo a algo como:

int dispatch(FsmState State, void *Arg) { 
    switch(State) { 
     case STATE1: 
      return State1Callback(Arg); 
     case STATE2: 
      return State2Callback(Arg); 
     case STATE3: 
      return State3Callback(Arg); 
    } 
} 

pero es más fácil para mí mantener, especialmente en los casos en que hay un comportamiento más complicado asociado con los estados. Por ejemplo, si queríamos tener un mecanismo de interrupción específica de cada estado, se cambiaría la definición de estructura:

struct { 
    FsmState State; 
    int (*Callback)(void *Arg); 
    void (*Abort)(void *Arg); 
} const FsmCallbacks[] = {...}; 

y yo no necesitamos modificar tanto los abort y dispatch rutinas para el nuevo estado. Uso const para evitar que la tabla cambie en tiempo de ejecución.

+0

Sí, dejé fuera la gran: estructuras de datos 'const' para máquinas de estado, tablas de búsqueda, etc. Duh ... –

2

Por ejemplo:

void memset_type_thing(char *first, char *const last, const char value) { 
    while (first != last) *(first++) = value; 
} 

El hecho de que last no puede ser parte de una expresión-constante es ni aquí ni allá. const es parte del sistema de tipo, utilizado para indicar una variable cuyo valor no cambiará. No hay ninguna razón para modificar el valor de last en mi función, así que lo declaro const.

no podía molestar a declarar que const, pero luego no pude molesto en el uso de un lenguaje de tipos estáticos en absoluto ;-)

+0

¿Por qué no declaraste' value' como 'const' también, por la misma razón? – caf

+0

@caf: se olvidó, gracias. Especialmente, me gustaría declarar 'last' const a la defensiva, porque es del mismo tipo que' first', que sí planeo modificar. Dado que 'value' es de un tipo diferente, hay un beneficio levemente menor en hacerlo const. Que, una vez más, es del mismo tipo que '* first', que I * am * modifica. Tienes razón, no hay razón para no ser más que el código adicional.Lo mismo se aplica a las variables automáticas sin parámetros: si las haces const, entonces cualquiera que lea el código tiene menos estado potencialmente mutable del que preocuparse, y puede usar ese bit de cerebro para otra cosa. –

0

Pueden ser utilizados para periféricos o registros mapeados en memoria que no puede ser cambiado por código de usuario , solo algunos mecanismos internos del microprocesador. P.ej. en el PIC32MX, ciertos registros que indican el estado del programa están calificados const volatile - para que pueda leerlos, y el compilador no intentará optimizar, por ejemplo, los accesos repetidos, pero su código no puede escribir en ellos.

(no hace ningún código a mano, por lo que no puede citar un buen ejemplo en este momento.)

2

PC-lint warning 429 se sigue de la expectativa de que un puntero local para un objeto asignado debe ser consumido

  • copiándolo a otro puntero o
  • pasándolo a una función de "sucio" (esto debería despojar a la propiedad "custodia" del puntero) o
  • liberándolo o
  • pasándolo por la persona que llama a través de una declaración de devolución o un parámetro de pase por puntero.

Por "sucio" me refiero a una función cuyo parámetro de puntero correspondiente tiene un tipo de base no const. La descripción de la advertencia absuelve las funciones de la biblioteca como strcpy() de la etiqueta "sucia", aparentemente porque ninguna de esas funciones de la biblioteca toma posesión del objeto puntiagudo.

Por lo tanto, cuando se usan herramientas de análisis estático como PC-lint, el calificador const de parámetros de funciones llamadas mantiene las regiones de memoria asignadas localmente.

Cuestiones relacionadas