Ocultar miembros en una estructura C

8 minutos de lectura

avatar de usuario
Marlon

He estado leyendo sobre programación orientada a objetos en C, pero nunca me gustó cómo no puedes tener miembros de datos privados como en C++. Pero luego se me ocurrió que podías crear 2 estructuras. Uno se define en el archivo de encabezado y el otro se define en el archivo de origen.

// =========================================
// in somestruct.h
typedef struct {
  int _public_member;
} SomeStruct;

// =========================================
// in somestruct.c

#include "somestruct.h"

typedef struct {
  int _public_member;
  int _private_member;
} SomeStructSource;

SomeStruct *SomeStruct_Create()
{
  SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
  p->_private_member = 42;
  return (SomeStruct *)p;
}

Desde aquí, puede lanzar una estructura a la otra. ¿Se considera esto una mala práctica? ¿O se hace a menudo?

  • ¿Por qué hacerlo tan complicado? Si la herramienta no hace lo que necesita, use una diferente.

    – Romain Hippeau

    20 de abril de 2010 a las 1:45

  • Creo que esto violaría las reglas de alias de objetos, al menos en C99. Sé que lo haría en C++.

    –James McNellis

    20 de abril de 2010 a las 1:46

  • ¡Sería terrible cerrar esto! ¿Por qué la gente vota cerca de una pregunta tan válida? ¿Porque olvidar cómo hacer las cosas en C?

    –Heath Hunnicutt

    20 de abril de 2010 a las 1:47

  • No intentes ocultar las cosas. Solo use nombres para los miembros “privados” que dejen absolutamente claro que nadie debe tocarlos. Y si alguien los usa, repréndelo. O cambie los nombres para educarlos.

    – gnasher729

    3 de abril de 2014 a las 0:04

sizeof(SomeStruct) != sizeof(SomeStructSource). Esta voluntad hacer que alguien te encuentre y te asesine algún día.

  • Y cualquier jurado los dejaría ir después.

    – gnud

    20 de abril de 2010 a las 9:43

  • “Siempre codifica como si la persona que termina manteniendo tu código fuera un psicópata violento que sabe dónde vives”. (atribuido a Rick Osborne)

    -Dietrich Epp

    2 de noviembre de 2011 a las 14:10

avatar de usuario
nos

Personalmente, me gustaría más esto:

typedef struct {
  int _public_member;
  /*I know you wont listen, but don't ever touch this member.*/
  int _private_member;
} SomeStructSource;

Después de todo, es C, si la gente quiere meter la pata, debería permitírselo, no hay necesidad de esconder cosas, excepto:

Si lo que necesita es mantener la compatibilidad con ABI/API, hay 2 enfoques que son más comunes por lo que he visto.

  • No le dé a sus clientes acceso a la estructura, déles un identificador opaco (un vacío * con un nombre bonito), proporcione funciones de inicio/destrucción y acceso para todo. Esto asegura que pueda cambiar la estructura sin siquiera volver a compilar los clientes si está escribiendo una biblioteca.

  • proporcione un identificador opaco como parte de su estructura, que puede asignar como desee. Este enfoque incluso se usa en C++ para proporcionar compatibilidad con ABI.

p.ej

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };

  • Encuentro un diseño realmente malo que permite al cliente acceder a cualquier miembro de una estructura. Toda la estructura debe ser privada. El acceso a sus miembros debe hacerse a través de getters y setters.

    –Felipe Lavratti

    19 de abril de 2013 a las 12:42

  • @fanl hacerlo en C tiene muchas implicaciones, por ejemplo, para ocultar una estructura de esa manera, se vuelve bastante difícil asignarla en la pila o en línea como miembro de otra estructura. La forma fácil de salir de eso es asignar dinámicamente la estructura y solo exponer un vacío * o un identificador, y aunque hacerlo puede estar bien en algunos casos, hay muchos casos en los que las implicaciones son demasiado grandes y le impedirán aprovechar lo que te proporciona C.

    – nos

    19 de abril de 2013 a las 14:20


  • En mi humilde opinión, el segundo ejemplo dado aquí debería ser los respuesta, solo recuerde proporcionar una función destructora.

    – Luis

    3 oct 2014 a las 14:38

  • En situaciones críticas de rendimiento, evite el salto que void * implica y simplemente asigne los datos privados en línea: maldita sea la privacidad (en este caso, todo lo que puede hacer es anteponer un guión bajo).

    – Ingeniero

    11 de agosto de 2015 a las 11:42


  • Tu primera sugerencia es realmente genial. Lo que hacemos en nuestra empresa es struct S { int x; // PRIVATE_FIELD }; y luego tenemos nuestro propio analizador de código C que verifica los comentarios y si ve un comentario “PRIVATE_FIELD”, evita que los usuarios escriban S.x para ese campo.

    – mercurio0114

    8 oct 2020 a las 22:09


avatar de usuario
logan capaldo

Casi lo tienes, pero no has ido lo suficientemente lejos.

En el encabezado:

struct SomeStruct;
typedef struct SomeStruct *SomeThing;


SomeThing create_some_thing();
destroy_some_thing(SomeThing thing);
int get_public_member_some_thing(SomeThing thing);
void set_public_member_some_thing(SomeThing thing, int value);

En la C:

struct SomeStruct {
  int public_member;
  int private_member;
};

SomeThing create_some_thing()
{
    SomeThing thing = malloc(sizeof(*thing));
    thing->public_member = 0;
    thing->private_member = 0;
    return thing;
}

... etc ...

El punto es que aquí ahora los consumidores tienen no conocimiento de las partes internas de SomeStruct, y puede cambiarlo con impunidad, agregando y eliminando miembros a voluntad, incluso sin que los consumidores necesiten volver a compilar. Tampoco pueden munge miembros “accidentalmente” directamente, o asignar SomeStruct en la pila. Por supuesto, esto también puede verse como una desventaja.

  • Algunos consideran usar typedef ocultar punteros por ser una mala idea, particularmente porque es más obvio que SomeStruct * necesita ser liberado de alguna manera que SomeThing, que parece una variable de pila ordinaria. De hecho, todavía puede declarar struct SomeStruct; y, mientras no lo definas, la gente se verá obligada a usar SomeStruct * punteros sin poder desreferenciar sus miembros, teniendo así el mismo efecto sin ocultar el puntero.

    – Chris Lutz

    20 de abril de 2010 a las 2:19

avatar de usuario
Felipe Lavratti

No recomiendo usar el patrón de estructura pública. El patrón de diseño correcto, para programación orientada a objetos en C, es proporcionar funciones para acceder a todos los datos, sin permitir nunca el acceso público a los datos. Los datos de la clase deben declararse en la fuente, para que sean privados, y deben referenciarse de manera directa, donde Create y Destroy hace la asignación y libre de los datos. De esta manera el dilema público/privado no existirá más.

/*********** header.h ***********/
typedef struct sModuleData module_t' 
module_t *Module_Create();
void Module_Destroy(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);

/*********** source.c ***********/
struct sModuleData {
    /* private data */
};
module_t *Module_Create()
{
    module_t *inst = (module_t *)malloc(sizeof(struct sModuleData));
    /* ... */
    return inst;
}
void Module_Destroy(module_t *inst)
{
    /* ... */
    free(inst);
}

/* Other functions implementation */

Por otro lado, si no desea utilizar Malloc/Free (que puede ser una sobrecarga innecesaria en algunas situaciones), le sugiero que oculte la estructura en un archivo privado. Los miembros privados serán accesibles, pero eso depende de la participación del usuario.

/*********** privateTypes.h ***********/
/* All private, non forward, datatypes goes here */
struct sModuleData {
    /* private data */
};

/*********** header.h ***********/
#include "privateTypes.h"
typedef struct sModuleData module_t; 
void Module_Init(module_t *);
void Module_Deinit(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);

/*********** source.c ***********/
void Module_Init(module_t *inst)
{       
    /* perform initialization on the instance */        
}
void Module_Deinit(module_t *inst)
{
    /* perform deinitialization on the instance */  
}

/*********** main.c ***********/
int main()
{
    module_t mod_instance;
    module_Init(&mod_instance);
    /* and so on */
}

Nunca hagas eso. Si su API admite cualquier cosa que tome SomeStruct como parámetro (que espero que lo haga), entonces podrían asignar uno en una pila y pasarlo. Obtendría errores importantes al intentar acceder al miembro privado desde el compilador asigna para la clase de cliente no contiene espacio para ello.

La forma clásica de ocultar miembros en una estructura es convertirla en un vacío*. Es básicamente un identificador/cookie que solo conocen sus archivos de implementación. Casi todas las bibliotecas de C hacen esto para datos privados.

avatar de usuario
coste y flete

De hecho, a veces se usa algo similar al método que ha propuesto (por ejemplo, ver las diferentes variedades de struct sockaddr* en la API de sockets BSD), pero es casi imposible de usar sin violar las estrictas reglas de alias de C99.

No obstante, puedes hacerlo sin peligro:

somestruct.h:

struct SomeStructPrivate; /* Opaque type */

typedef struct {
  int _public_member;
  struct SomeStructPrivate *private;
} SomeStruct;

somestruct.c:

#include "somestruct.h"

struct SomeStructPrivate {
    int _member;
};

SomeStruct *SomeStruct_Create()
{
    SomeStruct *p = malloc(sizeof *p);
    p->private = malloc(sizeof *p->private);
    p->private->_member = 0xWHATEVER;
    return p;
}

Escribiría una estructura oculta y la referenciaría usando un puntero en la estructura pública. Por ejemplo, su .h podría tener:

typedef struct {
    int a, b;
    void *private;
} public_t;

Y tu .c:

typedef struct {
    int c, d;
} private_t;

Obviamente, no protege contra la aritmética de punteros y agrega un poco de sobrecarga para la asignación/desasignación, pero supongo que está más allá del alcance de la pregunta.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad