9

I would like to give a module variable a read-only access for client modules. Several solutions:

1. The most common one:

// module_a.c
static int a;

int get_a(void)
{
    return a;
}

// module_a.h
int get_a(void);

This makes one function per variable to share, one function call (I am thinking both execution time and readability), and one copy for every read. Assuming no optimizing linker.

2. Another solution:

// module_a.c
static int _a;
const int * const a = &_a;

// module_a.h
extern const int * const a;

// client_module.c
int read_variable = *a;
*a = 5;  // error: variable is read-only

I like that, besides the fact that the client needs to read the content of a pointer. Also, every read-only variable needs its extern const pointer to const.

3. A third solution, inspired by the second one, is to hide the variables behind a struct and an extern pointer to struct. The notation module_name->a is more readable in the client module, in my opinion.

4. I could create an inline definition for the get_a(void) function. It would still look like a function call in the client module, but the optimization should take place.

My questions:

  • Is there a best way to make variables modified in a module accessible as read-only in other modules? Best in what aspect?

  • Which solutions above would you accept or refuse to use, and why?

I am aware that this is microoptimization - I might not implement it - but I am still interested in the possibility, and above all in the knowing.

Gauthier
  • 40,309
  • 11
  • 63
  • 97
  • Why the use of volatile in your second proposition? – AProgrammer May 18 '11 at 12:05
  • I was close to asking that myself. I was thinking if `_a` was modified on interrupt (this is imported from test code), but that has nothing to do with the question so I'll remove it. – Gauthier May 18 '11 at 12:12

3 Answers3

5

Concerning option #4, I'm not sure you can make it inline if the variable isn't accessible outside the implementation file. I wouldn't count options #2 and #3 as truly read-only. The pointer can have the constness cast away and be modified (const is just a compiler "warning", nothing concrete). Only option #1 is read-only because it returns a copy.

Ioan
  • 2,382
  • 18
  • 32
  • 2
    If your users are casting away your `const`, then why don't they just poke around your `static` variables too by scanning the debugger's symbol table? There is no "concrete" read-only in C. – Dietrich Epp May 18 '11 at 12:22
  • 1
    I fully agree with this answer, #1 is the proper way to do private encapsulation in C. I wouldn't be too concerned about inlining, in most programs, inlining is just a pre-mature optimization. And in >99% of all applications, you _don't need_ to have such extreme realtime performance that the few CPU ticks saved by inlining actually matters. I work solely with embedded realtime systems and can only come up with one single case throughout my career where I actually _needed_ to use inlining. So if you are just doing some casual desktop programming, forget that you ever heard about inlining. – Lundin May 18 '11 at 12:45
  • I agree with Ioan. #1 is the best way because it lets you to change implementation details regarding the _read only_ variable, while mantaining backward compatibility with your clients. – Guillermo Calvo May 18 '11 at 13:13
  • @Lundin — I have seen cases where inlining functions have sped up whole-program speed by significant factors (500% or more). But I'm under no delusion about how the `inline` keyword works. The reason I use it is so small accessor functions can go in header files without generating any compiler warnings or linker errors. The compiler can then choose to inline the function or not as it sees fit. – Dietrich Epp May 18 '11 at 13:14
  • @Dietrich It should be noted that the compiler is free to inline or not, no matter if the keyword is there. Of course inlining will lead to faster programs, particularly if there is a lot of searching and sorting (generally not the case in realtime systems). But there is no point in optimizing for speed that nobody requires. If the program is 500% faster, but it makes no difference to the spec or the user, then you have gained nothing. Also, inlining will drastically increase the program size, which may be an issue. There is no general right or wrong, it is all project-specific. – Lundin May 18 '11 at 13:57
  • @Dietrich, I wasn't trying to get into the whole program protection debate, much bigger topic. I was mostly thinking about casting away const to avoid compiler warnings. Not a good idea in general, but there are functions that weren't prototyped properly and don't actually modify their parameters, and that it's easy to lose track when doing that. – Ioan May 18 '11 at 15:16
  • @Lundin - inlining `int get_a(void){ return a; }` will likely **reduce** the program size drastically. That is one of the reasons for the speedup. – Bo Persson May 18 '11 at 17:43
  • @Bo Yes, if you only inline "sets" and "gets", and never any other kind of function. – Lundin May 19 '11 at 06:25
2

For speed identical to variable access, you can define an extern variable inside an inline function:

static inline int get_a(void)
{
    extern int a_var;
    return a_var;
}

This is simple and clear to read. The other options seem unnecessarily convoluted.

Edit: I'm assuming that you use prefixes for your names, since you write C. So it will actually be:

extern int my_project_a;

This prevents a client from accidentally making a variable with the same name. However, what if a client makes a variable with the same name on purpose? In this situation, you have already lost, because the client is either 1) actively trying to sabotage your library or 2) incompetent beyond reasonable accommodation. In situation #1, there is nothing you can do to stop the programmer. In situation #2, the program will be broken anyway.

Try running nm /lib/libc.so or equivalent on your system. You'll see that most libc implementations have several variables that are not defined in header files. On my system this includes things like __host_byaddr_cache. It's not the responsibility of the C library implementors to babysit me and prevent me from running:

extern void *__host_byaddr_cache;
__host_byaddr_cache = NULL;

If you start down the path of thinking that you have to force clients to treat your variable as read-only, you are heading down the path of fruitless paranoia. The static keyword is really just a convenience to keep objects out of the global namespace, it is not and never was a security measure to prevent external access.

The only way to enforce read-only variables is to manage the client code — either by sandboxing it in a VM or by algorithmically verifying that it can't modify your variable.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • Then `a_var` may not be `static` in the C file, and any client may also access the variable directly by declaring it `extern`. Also I don't understand the function being static, do you really mean this as an inline definition (in the h file)? – Gauthier May 18 '11 at 12:23
  • If your clients declare it `extern` -- you'll just have to live with that. Assume that your clients are adults and won't actively try to sabotage your library. The `static inline` is how you write inline functions in header files in C. – Dietrich Epp May 18 '11 at 12:25
  • (for the record, I meant client as client module, sorry if I was unclear). – Gauthier May 18 '11 at 12:30
  • Yes, assume that there is an adult person programming the module that won't try to sabotage your library. No matter what you do, you can't stop them from succeeding. In C++, they could just `#define private public` (on some compilers) and access your private member variables. – Dietrich Epp May 18 '11 at 12:33
  • @Dietrich The clients could be beginner programmers, using extern just because they were taught to do so by some crappy C book/tutorial (there are countless). Or worse, the clients could _accidentally_ extern your global variables simply because the project is large and the global namespace is cluttered. So you cannot simply dismiss such access as "sabotage". – Lundin May 18 '11 at 12:38
  • Yes, which is why you use project prefixes. Instead of `a`, call it `my_prefix_a`. This prevents accidents. – Dietrich Epp May 18 '11 at 12:41
  • Source prefixes are great practice, and they will indeed minimize accidental access, and in the best of worlds all C programmers would use them. Sadly, that is not the case. And you still have the case where a beginner or quack intentionally tries to access the variable. – Lundin May 18 '11 at 12:53
  • The beginner would have to be smart enough to read the header file and copy the variable definition out, but dumb enough to not know this is a bad idea. And you can't stop a quack no matter what you do. Just focus on helping your clients, don't treat them like morons or criminals. – Dietrich Epp May 18 '11 at 13:02
1
  1. The most common one:

There's a reason why it's the most common one. It's the best one.

I don't regard the performance hit to be significant enough to be worth worrying about in most situations.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Care to give reasons for avoiding the other constructs? – Gauthier May 23 '11 at 06:49
  • @Gauthier: Using a function encapsulates the implementation. You can choose to use a different variable or even calculate the value on the fly without breaking the API. – JeremyP May 23 '11 at 08:27
  • Just a note for the "in most situations" part: The most common way enforces the compiler to generate function calls (only whole program optimizations could inline this). On some architectures a function call might have significant overheads (AVR-8 is one such architecture), so following it for an often used property could become a significant performance hit. It would be nice (for such cases) if there was a way to expose something read only, but I can't think of any good method for this either. – Jubatian Mar 22 '18 at 13:36