Data encapsulation in C

后端 未结 5 979
醉酒成梦
醉酒成梦 2020-12-24 10:05

I am currently working on an embedded system and I have a component on a board which appears two times. I would like to have one .c and one .h file for the component.

<
相关标签:
5条回答
  • 2020-12-24 10:20

    You could make a portion of your structure private like this.

    object.h

    struct object_public {
        uint32_t public_item1;
        uint32_t public_item2;
    };
    

    object.c

    struct object {
        struct object_public public;
        uint32_t private_item1;
        uint32_t *private_ptr;
    }
    

    A pointer to an object can be cast to a pointer to object_public because object_public is the first item in struct object. So the code outside of object.c will reference the object through a pointer to object_public. While the code within object.c references the object through a pointer to object. Only the code within object.c will know about the private members.

    The program should not define or allocate an instance object_public because that instance won't have the private stuff appended to it.

    The technique of including a struct as the first item in another struct is really a way for implementing single inheritance in C. I don't recall ever using it like this for encapsulation. But I thought I would throw the idea out there.

    0 讨论(0)
  • 2020-12-24 10:21

    It's common that structures in C are defined completely in the header, although they're totally opaque (FILE, for example), or only have some of their fields specified in the documentation.

    C lacks private to prevent accidental access, but I consider this a minor problem: If a field isn't mentioned in the spec, why should someone try to access it? Have you ever accidentally accessed a member of a FILE? (It's probably better not to do things like having a published member foo and a non-published fooo which can easily be accessed by a small typo.) Some use conventions like giving them "unusual" names, for example, having a trailing underscore on private members.

    Another way is the PIMPL idiom: Forward-declare the structure as an incomplete type and provide the complete declaration in the implementation file only. This may complicate debugging, and may have performance penalties due to less possibilities for inlining and an additional indirection, though this may be solvable with link-time optimization. A combination of both is also possible, declaring the public fields in the header along with a pointer to an incomplete structure type holding the private fields.

    0 讨论(0)
  • 2020-12-24 10:29

    I would like this data to be non-accessible from the "outside" of my component (only through special functions in my component).

    You can do it in this way (a big malloc including the data):

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    
    typedef struct {
        uint32_t pin_reset;
        uint32_t pin_drdy;
        uint32_t pin_start;
        volatile avr32_spi_t *spi_module;
        uint8_t cs_id;  
    } ads1248_options_t;
    
    void fn(ads1248_options_t *x)
    {
        int32_t *values = (int32_t *)(x + 1);
        /* values are not accesible via a member of the struct */
    
        values[0] = 10;
        printf("%d\n", values[0]);
    }
    
    int main(void)
    {
        ads1248_options_t *x = malloc(sizeof(*x) + (sizeof(int32_t) * 100));
    
        fn(x);
        free(x);
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-24 10:30

    You can:

    1. Make your whole ads1248_options_t an opaque type (as already discussed in other answers)
    2. Make just the adc_values member an opaque type, like:

       // in the header(.h)
      typedef struct adc_values adc_values_t;
      
       // in the code (.c)
      struct adc_values { 
          int32_t *values;
      };
      
    3. Have a static array of array of values "parallel" to your ads1248_options_t and provide functions to access them. Like:

      // in the header (.h)
      int32_t get_adc_value(int id, int value_idx);
      
      // in the code (.c)
      static int32_t values[MAX_ADS][MAX_VALUES];
      // or
      static int32_t *values[MAX_ADS]; // malloc()-ate members somewhere
      
      int32_t get_adc_value(int id, int value_idx) {
          return values[id][value_idx]
      }
      

      If the user doesn't know the index to use, keep an index (id) in your ads1248_options_t.

    4. Instead of a static array, you may provide some other way of allocating the value arrays "in parallel", but, again, need a way to identify which array belongs to which ADC, where its id is the simplest solution.

    0 讨论(0)
  • 2020-12-24 10:33

    For the specific case of writing hardware drivers for a microcontroller, which this appears to be, please consider doing like this.

    Otherwise, use opaque/incomplete type. You'd be surprised to learn how shockingly few C programmers there are who know how to actually implement 100% private encapsulation of custom types. This is why there's some persistent myth about C lacking the OO feature known as private encapsulation. This myth originates from lack of C knowledge and nothing else.

    This is how it goes:

    ads1248.h

    typedef struct ads1248_options_t ads1248_options_t; // incomplete/opaque type
    
    ads1248_options_t* ads1248_init (parameters); // a "constructor"
    void ads1248_destroy (ads1248_options_t* ads); // a "destructor"
    

    ads1248.c

    #include "ads1248.h"
    
    struct ads1248_options_t {
        uint32_t pin_reset;
        uint32_t pin_drdy;
        uint32_t pin_start;
        volatile avr32_spi_t *spi_module;
        uint8_t cs_id;  
    };
    
    ads1248_options_t* ads1248_init (parameters)
    {
      ads1248_options_t* ads = malloc(sizeof(ads1248_options_t));
      // do things with ads based on parameters
      return ads;
    }
    
    void ads1248_destroy (ads1248_options_t* ads)
    {
      free(ads);
    }
    

    main.c

    #include "ads1248.h"
    
    int main()
    {
      ads1248_options_t* ads = ads1248_init(parameters);
      ...
      ads1248_destroy(ads);
    }
    

    Now the code in main cannot access any of the struct members, all members are 100% private. It can only create a pointer to a struct object, not an instance of it. Works exactly like abstract base classes in C++, if you are familiar with that. The only difference is that you'll have to call the init/destroy functions manually, rather than using true constructors/destructors.

    0 讨论(0)
提交回复
热议问题