10

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.

I have the following code:

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;

Those are all hardware settings. I create two instances of this struct (one for each part).

Now I need to keep an array of values in the background. E.g. I can read values from that device every second and I want to keep the last 100 values. I would like this data to be non-accessible from the "outside" of my component (only through special functions in my component).

I am unsure on how to proceed here. Do I really need to make the array part of my struct? What I thought of would be to do the following:

int32_t *adc_values; // <-- Add this to struct

int32_t *adc_value_buffer = malloc(sizeof(int32_t) * 100); // <-- Call in initialize function, this will never be freed on purpose

Yet, I will then be able to access my int32_t pointer from everywhere in my code (also from outside my component) which I do not like.

Is this the only way to do it? Do you know of a better way?

Thanks.

Tom L.
  • 932
  • 2
  • 9
  • 30
  • 2
    Standard Warning : Please [do not cast](http://stackoverflow.com/q/605845/2173917) the return value of `malloc()` and family in `C`. – Sourav Ghosh Mar 18 '15 at 10:16
  • Yes, you're right. I will change it. – Tom L. Mar 18 '15 at 10:17
  • Use a `void *` in the structure, cast it to the actual type when needed inside your source, and don't tell anyone about what it really is. – Some programmer dude Mar 18 '15 at 10:17
  • 3
    @JoachimPileborg Why void? Why not incomplete struct type? – n. m. could be an AI Mar 18 '15 at 10:20
  • You can also make the whole *structure* opaque, by not defining it in the header file, just *declare* the structure. Then define the structure in the source file. – Some programmer dude Mar 18 '15 at 10:20
  • @n.m. Yeah, just thought about it. :) – Some programmer dude Mar 18 '15 at 10:21
  • Can you give me an example? I need to set some values in this struct from outside (mostly hardware-oriented settings). – Tom L. Mar 18 '15 at 10:22
  • 1
    If you make the structure opaque, then simply use special functions to set fields in the structure. – Some programmer dude Mar 18 '15 at 10:23
  • Something like this? http://c-faq.com/struct/sd1.html – Tom L. Mar 18 '15 at 10:25
  • @TomL. Yes, exactly something like that. :) – Some programmer dude Mar 18 '15 at 10:27
  • 2
    use an opaque pointer to encapsulate your `struct` like [explained here](http://stackoverflow.com/questions/7553750/what-is-an-opaque-pointer-in-c) – Arjun Sreedharan Mar 18 '15 at 10:37
  • @JoachimPileborg: Can you put this into an answer so I can accept it? (because this is what I've been looking for) – Tom L. Mar 18 '15 at 11:41
  • [Here is an example for how to do the program design for such a case.](http://stackoverflow.com/questions/29034417/c-preprocessor-generate-macros-by-concatenation-and-stringification/29035658#29035658). If that's not what you want, the feature you are looking for is known as _opaque type_. – Lundin Mar 18 '15 at 11:58
  • Btw why do you need to use dynamic memory allocation if you always allocate a fixed size buffer? Including dynamic memory allocation in embedded systems can actually _reduce_ the total amount of RAM available to you, because the heap has to be stored somewhere, and it has to cover the worst case. If there are no other processes in the system, nor an OS, a heap doesn't really make any sense. – Lundin Mar 18 '15 at 12:01
  • @Lundin: the size of the buffer is fixed, but I can have any number from 1 to 4 of the same ICs on board (and each requires its own memory area); I would like my component to account for all of these possibilities. – Tom L. Mar 18 '15 at 12:16
  • 1
    @TomL. Then why don't you always reserve space for 4 ICs, since your program must always be able to handle the worst case anyhow? – Lundin Mar 18 '15 at 12:18
  • 1
    Btw I took the time to rewrite this particular code in the question into opaque type, see my answer below. – Lundin Mar 18 '15 at 12:20
  • @Lundin: Thinking about it, you might be right. I just thought of what happens in case I need 8, 10, 32, ..? Yet, that would leave me with a three-dimensional array (#ICs, #Channels, #Samples which is set during compile-time). But I wouldn't need to resort to malloc ... – Tom L. Mar 18 '15 at 12:29

5 Answers5

36

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.

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
2

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.

mafso
  • 5,433
  • 2
  • 19
  • 40
  • C uses incomplete type for private encapsulation. It is the very same thing as a complete private class. – Lundin Mar 18 '15 at 12:05
  • @Lundin: Some use it, some don't. I mentioned both ways. And pimpling differs from C++ `private` in that the members are invisible even for the compiler (and the debugger), so it's not exactly the same. – mafso Mar 18 '15 at 12:11
  • @Lundin: And C++ `private` still allows declaring instances of such types on the stack, what is impossible with pimpling as well. – mafso Mar 18 '15 at 12:18
  • No you can allocate it on the stack using functions such as [alloca](http://man7.org/linux/man-pages/man3/alloca.3.html), although that's a non-standard function. Another way is to allocate a private static memory pool, which is what you'd usually do on an embedded system where the heap cannot be used. – Lundin Mar 18 '15 at 12:22
  • @Lundin: `alloca` still needs the size. Of course, you could define a constant in the header representing the size, either by feeding an anonymous structure declaration identical to the private implementation to `sizeof`, or by hard-coding it (and statically asserting that this is indeed the size of the structure in the .c file). Given that they solve a non-issue (seriously, did you ever accidentally access a member of a `FILE` object?) and rely on non-standard `alloca`, I think they introduce too much clutter. – mafso Mar 18 '15 at 12:34
  • 1
    I agree with mafso. The question is about a deeply embedded system (Atmel). Anyone using the library will be injecting all the register symbols into their code every time they include 'atmegaxx.h' so trying to hide some data structure members in your library is really a minor issue - as he says in his answer. Leaving it 'public' with a useful comment will help everyone out when they inevitably need to modify the library or debug it. – Jon Mar 18 '15 at 13:36
1

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;
}
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • Wouldn't that be just the opposite of what I'm trying to achieve? My main is the function which should be free of any knowledge of the underlying data. – Tom L. Mar 18 '15 at 11:41
  • This won't give private encapsulation since the struct declaration is known to the whole program. You need to use opaque/incomplete type to achieve private encapsulation. – Lundin Mar 18 '15 at 12:04
  • @Lundin, an opaque type hides **all** the members of the struct, I don't know if this is what OP wants. – David Ranieri Mar 18 '15 at 13:21
0

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.

kkrambo
  • 6,643
  • 1
  • 17
  • 30
0

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.

srdjan.veljkovic
  • 2,468
  • 16
  • 24