2009-09-28 9 views
5

Estoy escribiendo un gran programa de C para uso integrado. Cada módulo en este programa tiene una función init() (como un constructor) para configurar sus variables estáticas.¿Cómo puedo verificar que se hayan llamado todas mis funciones init?

El problema es que tengo que recordar llamar a todas estas funciones de inicio desde main(). También debo recordar devolverlos si los he comentado por alguna razón.

¿Hay algo inteligente que hacer para asegurarme de que se llamen todas estas funciones? Algo parecido a poner una macro en cada función de inicio que, cuando llamas a una función check_inited() más tarde, envía una advertencia a STDOUT si no se llaman todas las funciones.

Podría incrementar un contador, pero tendría que mantener el número correcto de funciones init en alguna parte y también es propenso a error.

¿Pensamientos?

La siguiente es la solución que se me decidí por, con la colaboración de varias personas en este hilo

Mi objetivo es asegurarse de que todas mis funciones init en realidad están siendo llamados. Quiero hacer esto sin mantener listas o recuentos de módulos en varios archivos. No puedo llamarlos automáticamente al como sugirió Nick D porque deben llamarse en un orden determinado.

Para lograr esto, una macro incluida en cada módulo usa el atributo gcc constructor en agregue el nombre de la función init a una lista global.

Otra macro incluida en el cuerpo de la función init actualiza la lista global para hacer que un tenga en cuenta que realmente se llamó a la función.

Finalmente, se llama a una función de verificación en main() después de que se completen todas las operaciones.

Notas:

  1. he elegido para copiar las cadenas en una matriz. Esto no es estrictamente necesario porque los nombres de función pasados ​​siempre serán cadenas estáticas en el uso normal. Si la memoria era corta , podría simplemente almacenar un puntero a la cadena que se pasó.

  2. Mi biblioteca reutilizable de funciones de utilidad se llama "nx_lib". Por lo tanto, todas las designaciones 'nxl'.

  3. Este no es el código más eficiente del mundo, pero solo se llama tiempo de arranque, por lo que no me importa.

  4. Hay dos líneas de código que deben agregarse a cada módulo. Si se omite cualquiera de los dos, , la función de verificación le avisará.

  5. es posible que pueda hacer que la función del constructor sea estática, lo que evitaría la necesidad de darle un nombre único en todo el proyecto.

  6. este código solo se probó ligeramente y es muy tarde, por favor revise cuidadosamente antes de confiar en él.

Gracias a:

pierr que me introdujo en el atributo constructor.

Nick D por demostrar el truco del preprocesador ## y por proporcionarme el marco.

tod frye para un enfoque inteligente basado en un vinculador que funcionará con muchos compiladores.

Todos los demás para ayudar y compartir cositas útiles.

nx_lib_public.h

Este es el fragmento relevante de mi archivo de cabecera biblioteca

#define NX_FUNC_RUN_CHECK_NAME_SIZE 20 

typedef struct _nxl_function_element{ 
    char func[NX_FUNC_RUN_CHECK_NAME_SIZE]; 
    BOOL called; 
} nxl_function_element; 

void nxl_func_run_check_add(char *func_name); 
BOOL nxl_func_run_check(void); 
void nxl_func_run_check_hit(char *func_name); 

#define NXL_FUNC_RUN_CHECK_ADD(function_name) \ 
    void cons_ ## function_name() __attribute__((constructor)); \ 
    void cons_ ## function_name() { nxl_func_run_check_add(#function_name); } 

nxl_func_run_check.c

Este es el código que se llama libary añadir los nombres de funciones y comprobar más tarde.

#define MAX_CHECKED_FUNCTIONS 100 

static nxl_function_element m_functions[MAX_CHECKED_FUNCTIONS]; 
static int     m_func_cnt = 0; 


// call automatically before main runs to register a function name. 
void nxl_func_run_check_add(char *func_name) 
{ 
    // fail and complain if no more room. 
    if (m_func_cnt >= MAX_CHECKED_FUNCTIONS) { 
    print ("nxl_func_run_check_add failed, out of space\r\n"); 
    return; 
    } 

    strncpy (m_functions[m_func_cnt].func, func_name, 
      NX_FUNC_RUN_CHECK_NAME_SIZE); 

    m_functions[m_func_cnt].func[NX_FUNC_RUN_CHECK_NAME_SIZE-1] = 0; 

    m_functions[m_func_cnt++].called = FALSE; 
} 

// call from inside the init function 
void nxl_func_run_check_hit(char *func_name) 
{ 
    int i; 

    for (i=0; i< m_func_cnt; i++) { 
    if (! strncmp(m_functions[i].func, func_name, 
        NX_FUNC_RUN_CHECK_NAME_SIZE)) { 
     m_functions[i].called = TRUE; 
     return; 
    } 
    } 

    print("nxl_func_run_check_hit(): error, unregistered function was hit\r\n"); 
} 

// checks that all registered functions were called 
BOOL nxl_func_run_check(void) { 
    int i; 
    BOOL success=TRUE; 

    for (i=0; i< m_func_cnt; i++) { 
    if (m_functions[i].called == FALSE) { 
     success = FALSE; 
     xil_printf("nxl_func_run_check error: %s() not called\r\n", 
       m_functions[i].func); 
    } 
    } 
    return success; 
} 

solo.c

Este es un ejemplo de un módulo que necesita la inicialización

#include "nx_lib_public.h" 

NXL_FUNC_RUN_CHECK_ADD(solo_init) 
void solo_init(void) 
{ 
    nxl_func_run_check_hit((char *) __func__); 

    /* do module initialization here */ 
} 
+0

.. y si olvida poner la macro en una función init particular? Entonces vuelves al punto uno. –

+0

Bueno, sí, pero al menos eso es algo que puedo hacer mientras escribo la función, es más fácil recordar hacer eso que sincronizar algo en otro archivo. – NXT

+1

¿No sería mejor escribir el error en stderr? –

Respuesta

1

No sé cómo los siguientes ve feo, pero puedo enviar todos modos :-)

(La idea básica consiste en registrar los punteros de función, al igual que lo hace atexit función.
Por supuesto atexit aplicación es diferente)

En el módulo principal que puede tener algo como esto:

typedef int (*function_t)(void); 

static function_t vfunctions[100]; // we can store max 100 function pointers 
static int   vcnt = 0; // count the registered function pointers 

int add2init(function_t f) 
{ 
    // todo: error checks 
    vfunctions[vcnt++] = f; 
    return 0; 
} 
... 

int main(void) { 
... 
// iterate vfunctions[] and call the functions 
... 
} 

...y en algún otro módulo:

typedef int (*function_t)(void); 
extern int add2init(function_t f); 
#define M_add2init(function_name) static int int_ ## function_name = add2init(function_name) 

int foo(void) 
{ 
    printf("foo\n"); 
    return 0; 
} 
M_add2init(foo); // <--- register foo function 
+0

Bien, creo que lo entiendo. muy inteligente. Pero, ¿puede C invocar una función en un inicializador estático? Intenté una prueba simple y recibí un error de gcc: el elemento inicializador no es constante. Desearía poder poner el código en un comentario para poder mostrarle lo que probé. – NXT

+0

@NXT, ¿no funciona en C? Aaah, intentaré encontrar una solución si no encuentra una mejor solución a su problema. –

+0

@Nick D, estoy a punto de publicar una solución usando el atributo 'constructor'. Pero si usted sabe de otro acercamiento de la parte superior de su cabeza, me encantaría escucharlo. – NXT

1

Por qué no escribir un script de post-procesamiento que hacer la revisión para usted. Luego ejecute ese script como parte de su proceso de compilación ... O mejor aún, conviértalo en una de sus pruebas. Estás escribiendo pruebas, ¿verdad? :)

Por ejemplo, si cada uno de sus módulos tiene un archivo de encabezado, modX.c. Y si la firma de su función init() es "void init()" ...

Haga que su script grep pase por todos sus archivos .h y cree una lista de nombres de módulos que deben ser init() ed . Luego haga que la secuencia de comandos compruebe que init() efectivamente se llama en cada módulo en main().

1

Splint (y probablemente otras variantes de Lint) pueden dar una advertencia sobre las funciones que se definen pero no se llaman.

Es interesante que la mayoría de los compiladores le adviertan sobre las variables no utilizadas, pero no sobre las funciones no utilizadas.

+0

Es bastante difícil detectar funciones no utilizadas. Los punteros de función pueden dificultar la detección de una función. –

+0

GCC le advierte sobre las funciones estáticas no utilizadas, lo cual es útil. – NXT

1

Si su único módulo representa "clase" entidad y tiene ejemplo constructor, puede utilizar después de la construcción:

static inline void init(void) { ... } 
static int initialized = 0; 
#define INIT if (__predict_false(!initialized)) { init(); initialized = 1; } 
struct Foo * 
foo_create(void) 
{ 
    INIT; 
    ... 
} 

donde "__predict_false" es la rama de predicción de la insinuación de su compilador . Cuando se crea el primer objeto, el módulo se inicializa automáticamente (por una vez).

3

Puede usar la extensión __attribute__((constructor)) si gcc está bien para su proyecto.

#include <stdio.h> 
void func1() __attribute__((constructor)); 
void func2() __attribute__((constructor)); 

void func1() 
{ 
    printf("%s\n",__func__); 
} 

void func2() 
{ 
    printf("%s\n",__func__); 
} 

int main() 
{ 
    printf("main\n"); 
    return 0; 
} 

//the output 
func2 
func1 
main 
+0

Sí, GCC es lo que estoy usando. Esto, combinado con la idea de Nick D, es probablemente la mejor respuesta. Lo estoy intentando ahora. – NXT

1

puede hacer algo en esta línea con una sección enlazadora. cada vez que defina una función init, coloque un puntero en una sección del enlazador solo para los punteros de la función init. entonces, al menos, puede averiguar cuántas funciones init se han compilado.

y si no importa en qué orden se llaman las funciones init, y todas tienen el mismo prototipo, puede llamarlas todas en un bucle desde main.

los detalles exactos eluden mi memoria, pero funciona soemthing como esto :: en el archivo de módulo ...

//this is the syntax in GCC..(or would be if the underscores came through in this text editor) 
initFuncPtr thisInit __attribute((section(.myinits)))__= &moduleInit; 

void moduleInit(void) 
{ 
// so init here 
} 

esto coloca un puntero a la función init del módulo en la sección .myinits, pero deja el código en la sección .code. entonces la sección .myinits no es más que punteros. puede pensar en esto como una matriz de longitud variable a la que los archivos de módulo pueden agregar.

luego puede acceder a la sección de inicio y final de la dirección principal. e ir de allí.

si todas las funciones init tienen el mismo prototipo, puede simplemente iterar sobre esta sección, llamándolas a todas.

esto, en efecto, es la creación de su propio sistema constructor estático en C.

si se está haciendo un gran proyecto y el enlazador no está presente al menos con todas las funciones, que puede tener un problema ...

+0

Muy agradable. ¿Conoces la sintaxis para acceder a las direcciones de inicio y finalización del segmento desde el código C? Si no lo sabes por la parte superior de tu cabeza, lo buscaré yo mismo. – NXT

+0

IIRC, cada sección tiene al menos un comienzo y un final, por lo que extern unsigned int myinits_start y extern unsigned int myinits_end debe obtener la dirección de inicio y finalización. puede que tenga que retocar la secuencia de comandos del vinculador para agregar la sección y agregar estos símbolos ... –

0

¿Puedo responder a mi pregunta?

Mi idea era que cada función agregara su nombre a una lista global de funciones, como la solución de Nick D's.

Luego correría a través de la tabla de símbolos producida por -gstab, y buscaría cualquier función llamada init_ * que no se haya llamado.

Esta es una aplicación incrustada, así que tengo la imagen de elfo a mano en la memoria flash.

Sin embargo no me gusta esta idea porque significa que siempre tengo que incluir información en el binario de depuración.

+0

Puede responder a su pregunta. Incluso puede aceptarlo como correcto, si espera dos días, pero no obtendrá ninguna reputación. –

1

grande tiempo de ejecución no es un problema

Puede implementar concebible una especie de "máquina de estados" para cada módulo, en el que las acciones de una función dependen del estado del módulo está en. Este estado se puede establecer en BEFORE_INIT o INITIALIZED.

Por ejemplo, digamos que tenemos el módulo A con funciones foo y barra.

La lógica real de las funciones (es decir, lo que en realidad hacen) podría ser declarada como tal:

void foo_logic(); 
void bar_logic(); 

O lo que es la firma.

Entonces, las funciones reales del módulo (es decir, la función real declarado foo()) llevará a cabo una comprobación en tiempo de ejecución de la condición del módulo, y decidir qué hacer:

void foo() { 
     if (module_state == BEFORE_INIT) { 
      handle_not_initialized_error(); 
     } 
     foo_logic(); 
} 

Esta lógica se repite para todas las funciones.

Algunas cosas para tomar nota:

  1. Esto, obviamente, un cargo enorme en cuanto al rendimiento, por lo que es probablemente no es una buena idea (he publicado de todos modos porque usted ha dicho tiempo de ejecución no es un problema) .
  2. Esta no es una máquina de estado real, ya que solo hay dos estados que se comprueban utilizando un if básico, sin algún tipo de lógica general inteligente.
  3. Este tipo de "patrón de diseño" funciona muy bien cuando se utilizan tareas/subprocesos separados, y las funciones a las que llama se llaman realmente utilizando algún tipo de IPC.
  4. Una máquina de estado puede implementarse muy bien en C++, podría valer la pena leerla. El mismo tipo de idea se puede codificar en C con arreglos de indicadores de función, pero es casi seguro que no vale la pena.
+0

Lo siento, quise decir que el tiempo de ejecución no es un problema para la función init(), es un problema en todos lados. Pero gracias por publicarlo de todos modos. – NXT

Cuestiones relacionadas