Asignación estática de tipos de datos opacos


Muy a menudo malloc() no está permitido cuando se programa para sistemas embebidos. La mayoría de las veces soy bastante capaz de lidiar con esto, pero una cosa me irrita: me impide usar los llamados "tipos opacos" para habilitar la ocultación de datos. Normalmente haría algo como esto:

// In file module.h
typedef struct handle_t handle_t;

handle_t *create_handle();
void operation_on_handle(handle_t *handle, int an_argument);
void another_operation_on_handle(handle_t *handle, char etcetera);
void close_handle(handle_t *handle);


// In file module.c
struct handle_t {
    int foo;
    void *something;
    int another_implementation_detail;
};

handle_t *create_handle() {
    handle_t *handle = malloc(sizeof(struct handle_t));
    // other initialization
    return handle;
}

Aquí tienes: create_handle() realiza un malloc() para crear una 'instancia'. Una construcción usada a menudo para evitar tener que malloc() es cambiar el prototipo de create_handle () como esto:

void create_handle(handle_t *handle);

Y luego la persona que llama podría crear el identificador de esta manera:

// In file caller.c
void i_am_the_caller() {
    handle_t a_handle;    // Allocate a handle on the stack instead of malloc()
    create_handle(&a_handle);
    // ... a_handle is ready to go!
}

Pero desafortunadamente este código es obviamente inválido, ¡el tamaño de handle_t no se conoce!

Realmente nunca encontré una solución para resolver esto de una manera adecuada. Me gustaría mucho saber si alguien tiene una forma adecuada de hacer esto, o tal vez un enfoque completamente diferente para habilitar la ocultación de datos en C (no usar globales estáticos en el módulo.c por supuesto, uno debe ser capaz de crear varias instancias).

Author: Bart, 2010-12-14

9 answers

Puede utilizar la función _alloca. Creo que no es exactamente estándar, pero por lo que sé, casi todos los compiladores comunes lo implementan. Cuando lo usa como argumento predeterminado, asigna fuera de la pila de la persona que llama.

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

También podría usar algún tipo de semi-montón de objetos - si tiene un número máximo de objetos actualmente disponibles, entonces podría asignar toda la memoria para ellos estáticamente, y solo bit-shift para cuáles están actualmente en uso.

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

Mi un poco de cambio está un poco apagado, ha pasado mucho tiempo desde que lo hice, pero espero que entiendas el punto.

 15
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 15:19:54

Una forma sería añadir algo como

#define MODULE_HANDLE_SIZE (4711)

Al encabezado público module.h. Dado que eso crea un requisito preocupante de mantener esto sincronizado con el tamaño real, la línea es, por supuesto, mejor autogenerada por el proceso de compilación.

La otra opción es, por supuesto, exponer realmente la estructura, pero documentarla como opaca y prohibiendo el acceso a través de cualquier otro medio que no sea la API definida. Esto se puede hacer más claro haciendo algo como:

#include "module_private.h"

typedef struct
{
  handle_private_t private;
} handle_t;

Aquí, la declaración real del identificador del módulo se ha movido a un encabezado separado, para que sea menos visible. Un tipo declarado en ese encabezado simplemente se envuelve en el nombre typedef deseado, asegurándose de indicar que es privado.

Las funciones dentro del módulo que toman handle_t * pueden acceder de forma segura a private como un valor handle_private_t, ya que es el primer miembro de la estructura pública.

 8
Author: unwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-01 15:29:46

Una solución si para crear un grupo estático de struct handle_t objetos, y proporcionar entonces como necesario. Hay muchas maneras de lograr eso, pero un simple ejemplo ilustrativo sigue:

// In file module.c
struct handle_t 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_t handle_pool[MAX_HANDLES] ;

handle_t* create_handle() 
{
    int h ;
    handle_t* handle = 0 ;
    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = &handle_pool[h] ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t* handle ) 
{
    handle->in_use = 0 ;
}

Hay formas más rápidas de encontrar un manejador no utilizado, por ejemplo, podría mantener un índice estático que aumenta cada vez que se asigna un manejador y' envuelve ' cuando alcanza MAX_HANDLES; esto sería más rápido para la situación típica donde se asignan varios manejadores antes de liberar cualquier una. Sin embargo, para un pequeño número de asas, esta búsqueda de fuerza bruta es probablemente adecuada.

Por supuesto, el controlador en sí ya no necesita ser un puntero, sino que podría ser un simple índice en el grupo oculto. Esto mejoraría la ocultación de datos y la protección del pool frente al acceso externo.

Así que el encabezado tendría:

typedef int handle_t ;

Y el código cambiaría como sigue:

// In file module.c
struct handle_s 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_s handle_pool[MAX_HANDLES] ;

handle_t create_handle() 
{
    int h ;
    handle_t handle = -1 ;
    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = h ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t handle ) 
{
    handle_pool[handle].in_use = 0 ;
}

Porque el identificador devuelto ya no es un puntero a los datos internos, y el usuario malicioso no puede obtener acceso a él a través del controlador.

Tenga en cuenta que es posible que necesite agregar algunos mecanismos de seguridad de subprocesos si está obteniendo controladores en múltiples subprocesos.

 5
Author: Clifford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 17:15:39

Desafortunadamente, creo que la forma típica de lidiar con este problema es simplemente haciendo que el programador trate el objeto como opaco - la implementación de la estructura completa está en el encabezado y disponible, es solo responsabilidad del programador no usar los internos directamente, solo a través de las API definidas para el objeto.

Si esto no es lo suficientemente bueno, algunas opciones podrían ser:

  • use C++ como una 'mejor C' y declare las partes internas de la estructura como private.
  • ejecute algún tipo de preprocesador en las cabeceras para que se declaren las partes internas de la estructura, pero con nombres inutilizables. El encabezado original, con buenos nombres, estará disponible para la implementación de las API que gestionan la estructura. Nunca he visto esta técnica utilizada - es solo una idea de la parte superior de mi cabeza que podría ser posible, pero parece mucho más problemas de lo que vale la pena.
  • haga que su código que usa punteros opacos declare la asignación estática objects as extern (ie., globals) Luego tienen un módulo especial que tiene acceso a la definición completa del objeto en realidad declarar estos objetos. Dado que solo el módulo "especial" tiene acceso a la definición completa, el uso normal del objeto opaco sigue siendo opaco. Sin embargo, ahora tienes que confiar en tus programadores para no abusar del hecho de que los objetos son globales. También ha aumentado el cambio de colisiones de nombres, por lo que deben gestionarse (probablemente no es un gran problema, excepto que podría ocurrir sin querer-ouch!).

Creo que en general, confiar en que sus programadores sigan las reglas para el uso de estos objetos podría ser la mejor solución (aunque usar un subconjunto de C++ tampoco es malo en mi opinión). Dependiendo de sus programadores para seguir las reglas de no utilizar los internos de la estructura no es perfecto, pero es una solución viable que es de uso común.

 3
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 15:52:51

Me enfrenté a un problema similar al implementar una estructura de datos en la que el encabezado de la estructura de datos, que es opaco, contiene todos los diversos datos que deben transferirse de una operación a otra.

Dado que la reinicialización podría causar una fuga de memoria, quería asegurarme de que la implementación de la estructura de datos en sí nunca sobrescriba un punto para acumular memoria asignada.

Lo que hice es lo siguiente:

/** 
 * In order to allow the client to place the data structure header on the
 * stack we need data structure header size. [1/4]
**/
#define CT_HEADER_SIZE  ( (sizeof(void*) * 2)           \
                        + (sizeof(int) * 2)             \
                        + (sizeof(unsigned long) * 1)   \
                        )

/**
 * After the size has been produced, a type which is a size *alias* of the
 * header can be created. [2/4] 
**/        
struct header { char h_sz[CT_HEADER_SIZE]; };
typedef struct header data_structure_header;

/* In all the public interfaces the size alias is used. [3/4] */
bool ds_init_new(data_structure_header *ds /* , ...*/);

En la implementación archivo:

struct imp_header {
    void *ptr1, 
         *ptr2;
    int  i, 
         max;
    unsigned long total;
};

/* implementation proper */
static bool imp_init_new(struct imp_header *head /* , ...*/)
{
    return false; 
}

/* public interface */
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{
    int i;

    /* only accept a zero init'ed header */
    for(i = 0; i < CT_HEADER_SIZE; ++i) {
        if(ds->h_sz[i] != 0) {
            return false;
        }
    }

    /* just in case we forgot something */
    assert(sizeof(data_structure_header) == sizeof(struct imp_header));

    /* Explicit conversion is used from the public interface to the
     * implementation proper.  [4/4]
     */
    return imp_init_new( (struct imp_header *)ds /* , ...*/); 
}

Lado del cliente:

int foo() 
{
    data_structure_header ds = { 0 };

    ds_init_new(&ds /*, ...*/);
}
 1
Author: koby m.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-23 17:31:56

Estoy un poco confundido por qué dices que no puedes usar malloc(). Obviamente, en un sistema embebido tiene memoria limitada y la solución habitual es tener su propio administrador de memoria que mallocs un gran grupo de memoria y luego asigna trozos de esto según sea necesario. He visto varias implementaciones diferentes de esta idea en mi tiempo.

Para responder a su pregunta, ¿por qué no simplemente asigna estáticamente una matriz de tamaño fijo de ellos en el módulo?c añadir una bandera "en uso", y luego tener create_handle() simplemente devuelve el puntero al primer elemento.

Como una extensión de esta idea, el "handle" podría ser un índice entero en lugar del puntero real, lo que evita cualquier posibilidad de que el usuario intente abusar de él al enviarlo a su propia definición del objeto.

 0
Author: AlastairG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 15:24:19

La solución menos sombría que he visto a esto ha sido proporcionar una estructura opaca para el uso de la persona que llama, que es lo suficientemente grande, además de tal vez un poco, junto con una mención de los tipos utilizados en la estructura real, para garantizar que la estructura opaca se alineará lo suficientemente bien en comparación con la real:

struct Thing {
    union {
        char data[16];
        uint32_t b;
        uint8_t a;
    } opaque;
};
typedef struct Thing Thing;

Luego las funciones toman un puntero a uno de esos:

void InitThing(Thing *thing);
void DoThingy(Thing *thing,float whatever);

Internamente, no expuesto como parte de la API, hay una estructura que tiene los verdaderos internos:

struct RealThing {
    uint32_t private1,private2,private3;
    uint8_t private4;
};
typedef struct RealThing RealThing;

(Esto uno solo tiene uint32_t' and uint8_t' that esa es la razón de la aparición de estos dos tipos en la unión anterior.)

Más probablemente una afirmación en tiempo de compilación para asegurarse de que el tamaño de RealThing no exceda el de Thing:

typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];

Luego cada función en la biblioteca hace un cast en su argumento cuando va a usarlo:

void InitThing(Thing *thing) {
    RealThing *t=(RealThing *)thing;

    /* stuff with *t */
}

Con esto en su lugar, el llamante puede crear objetos del tamaño correcto en la pila, y llamar a funciones contra ellos, la estructura sigue siendo opaca, y hay algunos comprobando que la versión opaca es lo suficientemente grande.

Un problema potencial es que los campos podrían insertarse en la estructura real que significa que requiere una alineación que la estructura opaca no, y esto no necesariamente tropezará con la comprobación de tamaño. Muchos de estos cambios cambiarán el tamaño de la estructura, por lo que quedarán atrapados, pero no todos. No estoy seguro de ninguna solución a esto.

Alternativamente, si tiene una (s) cabecera (s) pública (es) especial (es) que la biblioteca nunca se incluye a sí misma, entonces probablemente puede (sujeto a pruebas con los compiladores que soporta...) simplemente escribe tus prototipos públicos con un tipo y los internos con el otro. Sin embargo, todavía sería una buena idea estructurar los encabezados para que la biblioteca vea la estructura Thing de cara al público de alguna manera, para que se pueda verificar su tamaño.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 23:25:24

Es simple, simplemente ponga las estructuras en un privateTypes.h archivo de cabecera. Ya no será opaco, aún así, será privado para el programador, ya que está dentro de un archivo private.

Un ejemplo aquí: Ocultar miembros en una estructura C

 0
Author: Felipe Lavratti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:09:20

Esta es una pregunta antigua, pero ya que también me está mordiendo, quería proporcionar aquí una posible respuesta (que estoy usando).

Así que aquí hay un ejemplo:

// file.h
typedef struct { size_t space[3]; } publicType;
int doSomething(publicType* object);

// file.c
typedef struct { unsigned var1; int var2; size_t var3; } privateType;

int doSomething(publicType* object)
{
    privateType* obPtr  = (privateType*) object;
    (...)
}

Ventajas : publicType se puede asignar en la pila.

Tenga en cuenta que el tipo subyacente correcto debe seleccionarse para garantizar una alineación adecuada (es decir, no use char). Nótese también que sizeof(publicType) >= sizeof(privateType). Sugiero una afirmación estática para asegurarse de que esta condición siempre está marcada. Como nota final, si usted cree que su estructura puede evolucionar más adelante, no dude en hacer el tipo público un poco más grande, para mantener espacio para futuras expansiones sin romper ABI.

Desventaja : La conversión de tipo público a privado puede desencadenar advertencias estrictas de alias.

Descubrí más tarde que este método tiene similitudes con struct sockaddr dentro del socket BSD, que cumple básicamente con el mismo problema con advertencias de alias estrictas.

 0
Author: Cyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-06 00:44:20