4

For example, can I do something like this so that only foo.c can modify the variable foo?:

foo.h:

extern const int foo;
void foo_init(void);
void foo_reinit(void);

foo.private.h:

int foo;
void foo_init(void);
void foo_reinit(void);

foo.c:

#include "foo.private.h"
void foo_init() { foo = 1; /* ... */ }
void foo_reinit() { foo++; /* ... */ }

bar.c:

#include <foo.h>
int main()
{
    foo_init(); printf("foo: %d\n", foo);
    foo_reinit(); printf("foo: %d\n", foo);
    return 0;
}

And so that the following would produce an error/warning:

baz.c:

#include <foo.h>
int main()
{
    foo_init(); printf("foo: %d\n", foo);
    foo = 0; /* ERROR/WARNING for modifying const variable */
    return 0;
}

Is this guaranteed to link correctly?

Matt
  • 21,026
  • 18
  • 63
  • 115
  • Since `void foo_init(void); void foo_reinit(void);` are private, you shouldn't be able to call them in foo.c anyway. – Don Larynx Feb 05 '15 at 21:23
  • @DonLarynx: they're documented in both headers. So is an object called `foo`, but it has different amounts of const-ness. – Jonathan Leffler Feb 05 '15 at 21:26
  • @DonLarynx: I don't understand your point. `foo.c` *implements* `foo_init()` and `foo_reinit()`. – Matt Feb 05 '15 at 21:26
  • Beside the type incompatibility, you have to use `extern` specifier before you object declaration in an header otherwise you will end up with multiple tentative definition in every translation unit the header is included. – ouah Feb 05 '15 at 21:46
  • @ouah: That was a typo and I have fixed it. – Matt Feb 05 '15 at 21:52
  • Now you have no definition for your `foo` object, only declarations: you to define it in one translation unit. – ouah Feb 05 '15 at 21:53
  • @ouah: I fixed it. Is it good now? – Matt Feb 05 '15 at 21:55
  • 1
    @Matt would have better in `foo.c` as you cannot guarantee `foo.private.h` to be included only in one translation unit. – ouah Feb 05 '15 at 21:58
  • No; the variable should be declared, not defined, in `foo.private.h`. The variable should be defined in `foo.c`: `int foo = 0;`. – Jonathan Leffler Feb 05 '15 at 21:58
  • @JonathanLeffler: The idea was that `foo.private.h` would only be included in `foo.c`, so it's the same thing. The reason I didn't just put it *in* `foo.c` was that it could actually be the same file as `foo.h` just with some `#ifdef` tricks. – Matt Feb 05 '15 at 22:02
  • There's no point in having a header file that is only included in one source file. The only point in having a header file is to share information between multiple source files. You may as well speed up your compilation by including the text from the header in the source file if the header is only going to be used in one source file. – Jonathan Leffler Feb 05 '15 at 22:04
  • We can't see what you don't show us, can we? – Jonathan Leffler Feb 05 '15 at 22:06
  • @JonathanLeffler: I only show what it is necessary for the question. Where I put various pieces of code is unrelated to this question and would only get in the way. – Matt Feb 05 '15 at 22:08
  • duplicate of http://stackoverflow.com/questions/8051969/c-accessing-a-non-const-through-const-declaration? – mafso Feb 05 '15 at 23:05

4 Answers4

5

No. If the declarations mismatch, the behavior of the program is undefined. In particular, since the translation units using the public header are declaring that the object is const qualified, the compiler can, when translating them, make an assumption that the pointed-to data will never change, so it could cache the value across calls to external functions, including the functions that change it.

If you just want protection against accidentally writing code that tries to modify the data, do this:

extern int foo;
#define foo (*(const int *)&foo)

Then you can #undef foo in the modules that are actually permitted to change it.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • But that's exposing too many details to the consumers of `foo.h`. – Matt Feb 05 '15 at 21:42
  • 1
    @Matt: just one more reason to avoid global variables, then. By far it is better not to have them. If you must, be consistent in declaring them. And define them just once, not in each file that uses one of your headers. – Jonathan Leffler Feb 05 '15 at 21:43
  • @JonathanLeffler: Isn't it only *defined* once (and only *declared* in each file that uses the header)? – Matt Feb 05 '15 at 21:49
  • 1
    @Matt: As the question _was_ written, `foo` was defined in the headers without an `extern`, and was therefore defined in each file that included either header. As the question _is_ defined, `foo` is declared in each header, but the it is declared differently in the headers, and therefore anything can happen when you link code compiled with one header with the code compiled with the other header. That might include 'it works magically the way you would like it to'; indeed, pragmatically, it is the likely result. However, there is no guarantee that you will get the sane behaviour you expect. – Jonathan Leffler Feb 05 '15 at 21:53
  • @JonathanLeffler: I think I just fixed that. But you're right that I mixed it up at first. – Matt Feb 05 '15 at 21:56
5

Can I declare a variable as const in the public header and not in the private header?

No, you cannot as it invokes undefined behavior.

(C11, 6.2.7p2) "All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined."

const int and int are two type incompatible types.

(C11, 6.7.3p10) "For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type"

ouah
  • 142,963
  • 15
  • 272
  • 331
  • So what *can* I do then, if I am to comply with the standard (and without exposing too many implementation details)? – Matt Feb 05 '15 at 21:47
  • 1
    @Matt an option is to declare `foo` with internal linkage and use a getter function to access it. – ouah Feb 05 '15 at 21:51
  • But that sacrifices performance (or *may* sacrifice performance). – Matt Feb 05 '15 at 21:53
  • @Matt: demonstrate that there's a measurable performance hit. Then, if there's really a problem, make the variable global and mutable, and tell people they're only allowed to read it. Gut feel: you won't be able to demonstrate there's a measurable performance hit. – Jonathan Leffler Feb 05 '15 at 21:54
  • @JonathanLeffler: For this I'm more interested in the theory than the practice. Of course I can find all kinds of workarounds that won't impact performance, but I would still want to know the right way to do this (or whether there is one). – Matt Feb 05 '15 at 21:59
2

How thin do you like your ice when you tread on it?

Yes, you can do it. You'll sort of get away with it. But the standard doesn't guarantee that your code will work as you expect. The same object should be declared consistently everywhere.

But the primary trouble is that you lose the cross-checking that you should require because your 'private' code should be using the public header to ensure that the declarations used externally match the implementations. That is, foo.c should have #include "foo.h" to ensure that what consumers are told to use matches what is provided by the implementation (in foo.c). When you don't do that, things will go wrong, sooner or later.

Also, remember that compilers hate being lied to. They find ways to get their own back when you lie to them. And your code is lying to the compiler.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • How so? When I read about `volatile const` (i.e. used together), it lead me to believe that `const` only means the data is read-only, and not that it is necessarily constant, so I thought I could make use of that as I did above. Also, the problem you point out with the public/private headers can be solved in a number of ways, that's not the issue I'm asking about. – Matt Feb 05 '15 at 21:29
  • Using `volatile const` means that the C code cannot modify the value in the so-qualified variable, but that the variable may change its value in ways that the compiler cannot predict. That being so, the compiler must follow the conceptual model for accessing the variable: it cannot cache a value that is read if the variable is referenced twice in an expression. For example: `int x = cv_var * 2 + cv_var / 2;` cannot be modified to `int x = (cv_var * 5) / 2;` because it must read `cv_var` twice, where `cv_var` is `const volatile int cv_var;`. – Jonathan Leffler Feb 05 '15 at 21:33
  • Yes, which shows that how one C file sees a variable is already not always guaranteed to be how that variable actually works. I.e. someone implementing a `volatile const` variable cannot also declare it for himself as `const`. – Matt Feb 05 '15 at 21:36
  • 1
    There is no `volatile` in the code you're showing, so I don't know how a discussion of `volatile const` is relevant to your question or my answer. And the problem with the different public/private headers is that they describe `foo` differently, so they're inaccurate. The compiler is at liberty to make your program wilfully abuse your system when it spots that you're lying to it. In case your mother didn't train you right, lying is not good. – Jonathan Leffler Feb 05 '15 at 21:37
  • You are misunderstanding what `volatile const` means. – Jonathan Leffler Feb 05 '15 at 21:38
  • It's you who took the `volatile const` thing on too big of a tangent. I only mentioned that while I was reading about it, I came to believe that `const` only indicates a variable is read-only, and not that it is necessarily constant. So I applied this in a different way. – Matt Feb 05 '15 at 21:41
  • In which case, you have taken undue liberties with what you read about `volatile const` and misapplied it to 'just `const`'. – Jonathan Leffler Feb 05 '15 at 21:42
  • If you want me to add `volatile` to my two declarations of `foo` above, I could do so. It wouldn't change my question. – Matt Feb 05 '15 at 21:45
  • And my answer would still remain unchanged; it is the same in principle as the other answers. Doing what you're doing is invoking undefined behaviour, and invoking undefined behaviour is a bad idea because the compiler is not constrained to diagnose that it is undefined and it is not constrained to do anything that you want. What your code in your question is doing is wrong, and unsafe. Everyone is saying so. Listen to the others if you won't listen to me. – Jonathan Leffler Feb 05 '15 at 21:47
  • Just to be clear, I *am* listening to you and to the others; I'm just maybe-a-little-too-aggressively squeezing more information out of you. My follow-up question then is what *can* I do. – Matt Feb 05 '15 at 21:51
  • I'm done...you've been told what to do. Be consistent; use getter and/or setter functions if you don't want to let people see the modifiable variable. Provide them with a constant pointer to constant data that they can use to access the variable: `extern const int * const pfoo;` initialized with `const int * const pfoo = &foo;`. But I'd rather use the functions. – Jonathan Leffler Feb 05 '15 at 21:57
1

No you can't, but as I understand your code, you can just make the foo static, as the function foo_init and foo_reinit, and expose a simple get_foo to other modules.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69