2012-04-03 11 views
7

En my open-source plain C code yo uso esta estructura sencilla de leer y analizar los datos de un búfer de cadena:Aplicada de pato a escribir en C plano

typedef struct lts_LoadState 
{ 
    const unsigned char * pos; 
    size_t unread; 
} lts_LoadState; 

se accede a la memoria intermedia con this simple API:

/* Initialize buffer */ 
void ltsLS_init(lts_LoadState * ls,const unsigned char * data, size_t len); 

/* Do we have something to read? (Actually a macro.) */ 
BOOL ltsLS_good(ls); 

/* How much do we have to read? (Actually a macro.) */ 
size_t ltsLS_unread(ls); 

/* Eat given number of characters, return pointer to beginning of eaten data */ 
const unsigned char * ltsLS_eat(lts_LoadState * ls, size_t len); 

Nota : ltsLS_unread se puede reemplazar con return (ltsLS_good(ls)) ? SIZE_MAX : 0 sin romper la implementación actual.

Este código se utiliza para load some data in a custom format from a string buffer. (This puede haber una mejor ilustración.)


ahora tengo que datos de carga no desde un búfer de cadena, pero a partir de un puntero FILE.

Odiaría copiar y pegar la implementación, y desea reutilizar el código existente en su lugar. (Estoy de acuerdo con la refactorización/adaptación, por supuesto.)

Esto es un material de libro de texto en C++, pero cómo hacerlo en C simple sin incurrir en gastos generales de tiempo de ejecución?


Aquí es una función de ejemplo que utiliza la API de lts_LoadState y que no se va a copiar y pegar (pero puede ser cambiado, por supuesto, para apoyar tanto búfer de cadena y FILE *):

static int ltsLS_readline(
    lts_LoadState * ls, 
    const unsigned char ** dest, 
    size_t * len 
) 
{ 
    const unsigned char * origin = ls->pos; 
    unsigned char last = 0; 
    size_t read = 0; 

    while (ltsLS_good(ls)) 
    { 
    if (ltsLS_unread(ls) > 0) 
    { 
     unsigned char b = *ls->pos; /* OK, this should be ltsLS_eat_char macro. */ 
     ++ls->pos; 
     --ls->unread; 

     if (b == '\n') 
     { 
     *dest = origin; 
     *len = (last == '\r') ? read - 1 : read; 

     return LUATEXTS_ESUCCESS; 
     } 

     last = b; 
     ++read; 
    } 
    else 
    { 
     ls->unread = 0; 
     ls->pos = NULL; 
    } 
    } 

    return LUATEXTS_ECLIPPED; 
} 
+0

Estoy pensando en generar código C con algún script, por lo que "copiar y pegar" se realiza automáticamente. Esta es probablemente la forma más fácil. Pero estoy buscando la solución "adecuada" sin generación de código (el preprocesador está bien, por supuesto). –

+0

Terminé usando archivos mapeados en memoria - de esta manera puedo reutilizar exactamente el mismo 'lts_LoadState' y sus funciones de acceso. –

Respuesta

3

Parece que quiere variables de función, que pasaría como parámetro. C puede hacerlo, pero la sintaxis no es muy bonita.

Probablemente exista un poco de tiempo de ejecución, pero no mucho.

How do you pass a function as a parameter in C?

+0

Bueno, no estoy seguro de que la sobrecarga del tiempo de ejecución sea despreciable en este caso, a menos que GCC esté jugando inteligente ... lo cual es, por supuesto, bastante posible. Pero eso es fácil de comparar. De todos modos, ¿hay alguna manera de hacer lo que quiero sin punteros de función, pero, tal vez, algún código de preprocesador? –

+0

Estoy seguro de que sí, aunque las macros CPP pueden ser algo largas. Además, si lo hace con macros en lugar de punteros a funciones, es posible que las macros no sean más rápidas, especialmente si salta mucho, ya que las macros pueden desbordar un caché de la CPU. – user1277476

+0

No puedo imaginar lo que quiere decir con una macro de preprocesador C desbordando un caché de CPU. –

2

odio para abrir esta copia de seguridad, pero esto es algo que yo estaba pensando en hoy y no creo que esto tiene una gran respuesta todavía.

Creo que para implementar la tipificación de pato en C lo que está buscando es una tabla global. Cada estructura (objeto) debe tener el vtable como primer elemento en la estructura. Básicamente, siempre que haya un comportamiento al que desee acceder mediante el tipado de patos, lo agregará a esta tabla virtual mundial; luego puedes llamarlo sin importar qué objeto se pase a tu función, podrías lanzar el objeto a la mesa, mirar a la ubicación donde debería estar el comportamiento, verificar que no sea nulo y llamarlo.

//Would be declared in some global.h or similar 
struct global_v_table_t = 
{ 
    char* (*toString)(void); 
    //... other functions being accessed through duck typing go here 
} 

//-------------------- 
//In some other files: 
//Then we create some objects: 
struct bicycle_t 
{ 
    struct global_v_table; 
    void (*ride)(void); 
}; 

//When we initialise a bicycle 
bicycle_t * bicycyle_init(void) 
{ 
    bicycle_t * bike = malloc(sizeof(bicycle_t)); 
    //Req'd basically for every object within the project: 
    //Either do this or call calloc() instead of malloc(): 
    globalVtableInit((global_v_table_init)bike);//NULL the vtable 
    //Set the behaviours that this object exhibits: 
    bike->global_v_table.toString = BIKE_toString;  
} 


static char * bikeString = "I'm a bike!"; 
char * BIKE_toString(void) 
{ 
    return bikeString; 
} 

//---------------- 

//Now anyone can ask that an object provide it's toString: 

//The example uses an error logging function: 

void logError(void * obj) 
{ 
    char * (toStringMethod)(void) = ((global_v_table *)obj)->toString; 
    if (NULL != toStringMethod) 
    {//As long as the object implements the toString behaviour: 
     printf(toStringMethod()); //Print the object's toString. 
    } 
} 

//Will tidy this code up a bit later but this is what I'm thinking. 
//Hopefully is at least partly understandable. The obvious drawback 
//to this implementation is that for every object you get this massive 
//v_table which is full of mostly NULL's for each object as it scales. 
//If you want to make C behave like other languages though you have 
//to expect some sort of penalty I guess... 
0

que tenían una necesidad similar en mi intérprete de PostScript para el operador token a trabajar el mismo si la lectura de un FILE* o una cadena. Parece que ha hecho el primer paso, al menos en parte, de separar la lógica de análisis del acceso a datos a través de un par get/unget. Si puede escribir versiones de cadenas que coincidan con los prototipos de las funciones de la biblioteca FILE*, eso simplifica la implementación.

Para la mía, tenía un punto de entrada principal que toma indicadores de función para los accesadores get/unget.

int toke (Xpost_Context *ctx, 
     Xpost_Object *src, 
     int (*next)(Xpost_Context *ctx, Xpost_Object *src), 
     void (*back)(Xpost_Context *ctx, int c, Xpost_Object *src), 
     Xpost_Object *retval); 

Los controles normales de ejecución del operador invocan una función de interfaz adecuada según el tipo. Entonces, la versión del archivo llama al toke e implementa las dos acciones en términos de bajo nivel.

/* file token token true 
       false 
    read token from file */ 
static 
int Fnext(Xpost_Context *ctx, 
      Xpost_Object *F) 
{ 
    return xpost_file_getc(xpost_file_get_file_pointer(ctx->lo, *F)); 
} 
static 
void Fback(Xpost_Context *ctx, 
      int c, 
      Xpost_Object *F) 
{ 
    (void)ungetc(c, xpost_file_get_file_pointer(ctx->lo, *F)); 
} 
static 
int Ftoken (Xpost_Context *ctx, 
      Xpost_Object F) 
{ 
    Xpost_Object t; 
    int ret; 
    if (!xpost_file_get_status(ctx->lo, F)) 
     return ioerror; 
    ret = toke(ctx, &F, Fnext, Fback, &t); 
    if (ret) 
     return ret; 
    if (xpost_object_get_type(t) != nulltype) { 
     xpost_stack_push(ctx->lo, ctx->os, t); 
     xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(1)); 
    } else { 
     xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(0)); 
    } 
    return 0; 
} 

Y la versión de cadena utiliza implementaciones de cadena para las dos acciones.

/* string token substring token true 
        false 
    read token from string */ 
static 
int Snext(Xpost_Context *ctx, 
      Xpost_Object *S) 
{ 
    int ret; 
    if (S->comp_.sz == 0) return EOF; 
    ret = xpost_string_get_pointer(ctx, *S)[0]; 
    ++S->comp_.off; 
    --S->comp_.sz; 
    return ret; 
} 
static 
void Sback(Xpost_Context *ctx, 
      int c, 
      Xpost_Object *S) 
{ 
    --S->comp_.off; 
    ++S->comp_.sz; 
    xpost_string_get_pointer(ctx, *S)[0] = c; 
} 
static 
int Stoken (Xpost_Context *ctx, 
      Xpost_Object S) 
{ 
    Xpost_Object t; 
    int ret; 

    ret = toke(ctx, &S, Snext, Sback, &t); 
    if (ret) 
     return ret; 
    if (xpost_object_get_type(t) != nulltype) { 
     xpost_stack_push(ctx->lo, ctx->os, S); 
     xpost_stack_push(ctx->lo, ctx->os, t); 
     xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(1)); 
    } else { 
     xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(0)); 
    } 
    return 0; 
} 

Esto es de la xpost postscript interpreter en el archivo src/lib/xpost_op_token.c.

Cuestiones relacionadas