5

I'm developing a hardware abstraction library for an embedded product using GCC C. Within the library there is a variable that should be read-only to the application that links the library, but can be modified from within the compilation unit that defines it.

Is there a standard, acceptable way to declare the integer (in the library header file) that will allow the application to read the value in the variable, but tell the compiler to generate an error if any attempt is made to generate code that writes back to it? For example, if I were to declare a function as:

extern void foo(int const bar);

... then the caller is permitted to pass a local variable:

int bar = 0;
foo(bar);

... but if the function declaration attempts to write to bar:

void foo(int const bar)
{
    bar = 99;
}

... then the compiler duely reports an error: assignment of read-only location 'bar'.

The syntax of placing the const before the name does not appear to apply to variables in the same way that it does to function parameters, and the two lines below seem to be effectively equivalent:

extern const int x;
extern int const y;

... because defining y as int y; results in an error: conflicting type qualifiers for 'y', as I would expect to have seen had x been defined as int x;.

I know that I can get around the problem by declaring and defining an accessor function that returns the value (which can only be used as an r-value).

I've read a number of related questions here, but none that I've found provide a definitive answer for C (as opposed to C++ or C#):

C — Accessing a non-const through const declaration

Mixing extern and const

Please can someone point in me the direction of an example of how I might achieve it, or confirm my suspicion that it is not syntactically achievable?

Community
  • 1
  • 1
Evil Dog Pie
  • 2,300
  • 2
  • 23
  • 46
  • Don't include the header file where you declare the `const` variable in the translation-unit where you define the variable? Or have a preprocessor macro that you `#define` in the translation-unit where you define the variable, and have a conditional check for that in the header file? – Some programmer dude May 14 '14 at 13:08
  • @JoachimPileborg The problem with both of these is that the embedded compiler will expect to find a const value in the Microcontroller's Flash memory, but the real variable will be stored in the internal RAM. I.e. if it evens gets past the linker, the two references will be to two completely different physical memory locations. – Evil Dog Pie May 14 '14 at 13:13
  • "*declaring and defining an accessor function*" is not a "*get around*", but rather *best practice*. Why would you not just do that? See Jack Ganssle's article [A Pox on Globals](http://www.embedded.com/electronics-blogs/break-points/4025723/A-pox-on-globals?token=130b067b6f970f15f2b5db809a00137a) for some suitable education. Critically the linker may make decisions about locating consts in ROM or MMU protected read-only RAM that may conspire against your plan. – Clifford May 15 '14 at 08:06
  • @Clifford That's an interesting article, with an equally intersting response by Phil Gillaspy, the essence of which is "A variable should be modified in one and only one place in the code." Global variables do cause problems. It would be nice if C provided a syntactic way of achieving the `int MyVar {get; private set;}` pattern available in C#. – Evil Dog Pie May 15 '14 at 08:42

2 Answers2

9

Within the library there is a variable that should be read-only to the application that links the library, but can be modified from within the compilation unit that defines it.

The solution is to use object-oriented design, namely something called private encapsulation.

module.h

int module_get_x (void);

module.c

static int x;

int module_get_x (void)
{
  return x;
}

main.c

#include "module.h"

int main(void)
{
  int value = module_get_x();
}

If the variable must be read-write inside the module, then this is the only proper way to do this. All other ways are just some flavour of spaghetti programming.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • This is the strategy that I've adopted (as hinted in the O/P), as it allows the `module_get_x` function to examine the microcontroller configuration settings dynamically and provides for a more flexible method for mocking the library in my application layer unit tests. – Evil Dog Pie May 14 '14 at 13:43
3

You can use pointer to get past the issue.

module.c

static int readwrite = 0;
int const * const readonly = &readwrite;

module.h

extern int const * const readonly;

If there is no reason to expose address of the variable, you should prefer Lundins getter approach to have proper encapsulation.

Community
  • 1
  • 1
user694733
  • 15,208
  • 2
  • 42
  • 68
  • Because doing icky things like this leads to spaghetti programming. Instead, use proper program design to solve the problem. – Lundin May 14 '14 at 13:30
  • @Lundin Fair enough, I agree that getter is a proper choice here. Edited answer. – user694733 May 14 '14 at 13:32
  • And well, there is really never any reason to expose the address of a private variable. Performance is a non-issue that can be solved with proper optimizer settings. – Lundin May 14 '14 at 13:34
  • This has the same problem as `#define`ing the const away, in that the the 'readwrite' variable will be located in RAM, whereas the `readonly` pointer will address Flash, even if the assignment were to include a const cast to get rid of the compiler error `int const * const readonly = (const * const)&readwrite;`. – Evil Dog Pie May 14 '14 at 13:37
  • @MikeofSST That sounds like a issue with compiler, which may be solvable with compiler pragmas. From pure C point of view there should not be a problem, since you can assign address of non-const variable to pointer to `const` (you can pass a non-const to `memcmp` for example). – user694733 May 14 '14 at 13:43
  • It's a side-effect of the processor architecture (in combination with my choice to place const values in Program memory for other reasons) rather than being a compiler 'issue'. But you are correct that it is exposed in this way by the compiler. You should see some of the hoops I have to jump through to use the address of an entity on the stack! – Evil Dog Pie May 14 '14 at 13:50
  • @MikeofSST I think you may be misunderstanding where these values will end up. `readonly` will be a `const` pointer, likely located in flash, pointing to `readwrite`, located in volatile memory (RAM), but as `const`. The value `readonly` will live in flash, but it can point to RAM, and because `readonly`'s type is `int const * const` that means the compiler will yell at you if you try to change the value `readonly` points to. You will always be able to cast it away. – rjp May 14 '14 at 14:07
  • @RJP The problem is that the target processor has different instructions for accessing RAM and Flash, therefore if a pointer is declared as pointing to an entity that is stored in Flash then the compiler will generate one set of instructions to de-reference it, whereas if the entity that is addressed is in RAM (non-const) then different instructions are used. Hence it is the const-ness of the object, not the pointer that is the problem with this soultion. – Evil Dog Pie May 14 '14 at 14:22
  • @MikeofSST Yep. That's an odd one. I think the only way to go is to use Lundin's suggestion. I'd say that the compiler should be smart enough to generate the proper instructions based on the value, but with the extern definition, it won't know the value and probably will just assume Flash, like you had suggested. – rjp May 14 '14 at 14:26
  • @RJP It's a Microchip dsPIC33E (for information) with 24 bit Flash addresses and 16 bit (for most) RAM addresses - yes, it gets real complicated addressing some of the RAM! Lundin's is the approach I took, but there's some interesting suggestions here. I'm waiting to see if anyone can think of a way to use `volatile` to do it. :-) – Evil Dog Pie May 14 '14 at 14:29