0

What kinds of C syntax does the C standard specify for Defined Behavior and Undefined Behavior when using const pointers to global data areas in order to communicate both to the C compiler and to a programmer that in some functions the global data areas may be modified and in the rest of the source the global data areas should be read only?

If one functionfunc2(), accesses a global memory area through a pointer T const * const pObj; there fore seen as read only yet the data area may be changed by another function, func(), which accesses the same global memory area through a pointer T * const pObj; and the function func() is invoked either directly by func2() or by a callee of func2() will any local copies of data from the global memory area, for instance temporary variables or values in registers, be refreshed after any function call? Is a function call a kind of reset point that triggers all cached, non-local data (whether marked as const or not) stored in registers or local temporary variables to be flushed and refreshed?

Working with older C source that has several memory resident data areas used to maintain state I would like for most of the source to see these areas as read only. However some functions will modify the data areas depending on various events.

What I thought to do was to have two types of pointers to these memory resident data areas both of which point to the same location but which are defined so that the memory area is treated as either read only (T const * const pObj;) or as read/writable (T * const pObj;).

What does the C standard have to say about this approach and how safe is it?

For instance in the following C source example across several files, I expect that func2() will work correctly since the state change happens with a call to func() before func2() is called.

The include file contains the following source lines defining some types as well as declaring the global variables which are defined elsewhere.

typedef struct { int state; /* other stuff */ } ET;  // Event type
typedef struct { ET state; /* other stuff */ } T;    // memory resident data type

extern T const * const pObjImmutable;  // readonly pointer to readonly memory
extern T * const pObjMutable;  // readonly pointer to read/writable memory
extern int  IsState (const ET state, const ET event);  // check for equivalence of state and event

Next there is a source file where the pointers declared in the include file are actually defined along with an API for manipulation and changes.

static T objThing;       // define the object but make static for file visibility only
T const * const pObjImmutable = &objThing;  // define global readonly pointer to readonly memory
T * const pObjMutable = &objThing;  // define global readonly pointer to read/writable memory

int  IsState (ET state, ET event)
{
   // check for equivalence of state and event ....
}  

Finally there is a source file where the global objects are actually being used.

void func (ET event)
{
    // make changes to objThing based on event
    pObjMutable->state = event;
}

void func2 (void)
{
    extern ET const stdStateOne, const stdStateTwo;

    // use current state of objThing to make decisions
    if (IsState (pObjImmutable->state, stdStateOne)) {
        //  do things for State One due to an event
    } else if (IsState (pObjImmutable->state, stdStateTwo)) {
        //  do things for State Two due to an event
    }
}

int main ()
{
    func (eventOne);    // initialize the state
    func2 ();           // do something based on current state
    func (eventTwo);    // change the state
    func2();            // do something based on current state using new state
}

However what does the C standard say if the function func2() calls a function which somewhere in the call tree a function is called which modifies the memory resident area? For example if the function func2() calls the function func() directly with a new event or if the function func2() calls a function which in turn calls func() with a new event?

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • TL;DR, but it is your task not to break the contract you give by qualifying an object `const`. If you break that contract, you invoke _undefined behaviour_. No further investigation necessary, prepare for nasal daemons. – too honest for this site Feb 07 '16 at 15:50
  • @Olaf, the object itself is not `const`. there are two pointers, one of which presents the object as `const` and the other which presents the object as it is, non-const. function interfaces often specify `const` as in `T const *p` to indicate they will not change the object pointed to however that does not mean that the original object specified when the function is called is `const`. If the object itself is `const` then obviously changing it by casting to non-const or what ever is undefined behavior. But using a `T const * const` pointer mean the object itself is `const`? – Richard Chambers Feb 07 '16 at 16:31
  • 1
    TL;DR, You can point all you want, with all combintions of pointers to const or non-const types, and you will get correct updated values. The only way you can invoke *undefined behavior*, is if you modify an object which was defined with the const qualifier. – 2501 Feb 07 '16 at 16:33
  • For example: You can define an object T without const. Point to it with a pointer to const T, pass that pointer to a function, assign that pointer to a pointer to T, modify the object, and the behavior stays defined. – 2501 Feb 07 '16 at 16:37
  • @2501, does this apply to global objects as well as global pointers to those objects? My intent here is to be able to have the compiler give me an error if I inadvertently change the globals in places where they are not supposed to be changed. This is a large source code base of multiple files and multiple projects. – Richard Chambers Feb 07 '16 at 16:40
  • Another example: Define an object T without const. Point to it with a global pointer to T and point to it with another local pointer to const T, pass that local pointer to a function, then modify the object to a new value using the global pointer, then read the object using the passed pointer( which is to const T), and the value will be the new one, and behavior stays defined. I.e. even if a pointer is to const T, C must assume it can be modified and must be reloaded in memory. – 2501 Feb 07 '16 at 16:41
  • In conclusion, you can use local pointers to const T, to make sure you don't modify the object is some functions, and at the same time you don't have to worry if the object is modified using a different pointer to (non-const) T. Well, you don't have to worry about undefined behavior that is, if the program logic stays sane is another question. – 2501 Feb 07 '16 at 16:46
  • `T const` qualifies an object of typt `T` as `const`. It is identical to the more preferred `const T`. – too honest for this site Feb 07 '16 at 16:54
  • In section 6.7.3 Type qualifiers, paragraph 10 on page 109 of [WG14/N1124 Committee Draft — May 6, 2005 ISO/IEC 9899:TC2](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf) there is an example of combining `const` with the `volatile` type qualifier to declare an object `extern const volatile int real_time_clock;` to mean "may be modifiable by hardware, but cannot be assigned to, incremented, or decremented." see also http://stackoverflow.com/questions/4592762/difference-between-const-const-volatile – Richard Chambers Feb 07 '16 at 22:51
  • It looks like [Modifying a const variable with the volatile keyword](http://stackoverflow.com/questions/18064722/modifying-a-const-variable-with-the-volatile-keyword) is a question along similar lines and it has a mention of the C standard. However there is not a definitive answer there, just an argument. Also that question seems to be more about a way to bypass `const` by using a pointer rather than the specific scenario I am presenting. See also discussion [When are const volatile objects necessary?](http://stackoverflow.com/questions/18223228/when-are-const-volatile-objects-necessary). – Richard Chambers Feb 08 '16 at 00:42

2 Answers2

1

This comment isn't quite right:

extern T const * const pObjImmutable;  // immutable pointer to immutable memory

The pointer could be pointing to mutable or immutable memory, we can't tell. All we can tell is that this pointer cannot be used to modify that memory. It's possible the target is mutable and may change between reads of this pointer; the only restriction is that the memory can not be written by this pointer.

a function is called which modifies the memory resident area?

I'm not sure exactly what you mean by this. If you had the following:

static const T constThing;

and then some code modified the bytes of storage of constThing, it causes undefined behaviour. But...

For example if the function func2() calls the function func() directly with a new event or if the function func2() calls a function which in turn calls func() with a new event?

If you are just talking about the non-const objThing then this is fine. pObjMutable is used to update objThing ; and for anyone looking at the object by any means (including via pObjImmutable), they will see the changes.

Accordingly, if you have some code like:

printf("%d\n", pObjImmutable->bar);
func();
printf("%d\n", pObjImmutable->bar);

the compiler cannot cache the result of pObjImmutable->bar for the second printf, because func() might have modified objThing.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • The way I read the standard is if unless a `const` is also qualified with `volatile` then the compiler is free to cache over a function call. This is actually the root of my question as I did not feel comfortable that I understood what was happening. I am not sure what the `static const T constThing;` is coming from. Where did you see that in my question or the answer? As for the immutable, that is something borrowed from functional programming I have been investigating and is really the wrong word for C source as you pointed out. – Richard Chambers Feb 15 '16 at 20:46
  • @RichardChambers you're misreading the standard, 2501's comments explain. "immutable" means "non-modifiable" (which is a term used in C). – M.M Feb 15 '16 at 21:06
  • 2501's comments do not explain to me anything other than you can work around `const` if you want to. Why does http://embeddedgurus.com/barr-code/2012/01/combining-cs-volatile-and-const-keywords/ discuss using `volatile` with `const`? Why does the working paper on page 109 provide an explicit example of using `volatile` with `const` to indicate a `const` object which may change? – Richard Chambers Feb 15 '16 at 21:20
  • @RichardChambers you will note that all of those examples refer to memory-mapped hardware or such. `volatile` means that the value stored in the variable might change due to factors external to the C program. For example, a real time clock keeping its time current. – M.M Feb 15 '16 at 21:23
  • also , the Question does not mention `volatile` at all so I don't know why you keep bringing it up, or think it has anything to do with the answer – M.M Feb 15 '16 at 21:26
  • Great answer. Since you read my comments, I think they are correct, but if you found any errors/discrepancies please point them out. – 2501 Feb 16 '16 at 09:05
0

Since the const qualifier indicates that the value of the variable will not change, the compiler is free to do what ever optimizations it wants to do including keeping values in registers or temporary values. If a variable or object marked as const does in fact change through some other action, this is considered Undefined Behavior.

So const T *p = &Thing; means that the address in the pointer p may change though the object or variable pointed to will not change (changing the variable results in undefined behavior). Using T * const p = &Thing; means that the pointer p will not change though the object or variable pointed to may change. Using T const * const p = &Thing; means that not only will the address in the pointer p not change but also the contents of the variable pointed to will not change either. (See What is the difference between const int*, const int * const, and int const *?

The result is that using extern T const * const p; in order to access a global variable that the pointer p is pointing to means that any functions called should not modify the contents of the object or variable whose address is in the pointer variable p. Doing so results in undefined behavior.

Using volatile qualifier with const

Additional investigation indicates that the appropriate definition of the global pointer which presents an object as a read only object which may actually change due to some other activity such as a hardware event or another thread or process would be the following.

T const volatile  * const pObjImmutable = &objThing;  // read only pointer to read only memory which may change somehow

In the include file declaring this global variable for other source files to access we would use:

extern T const volatile  * const pObjImmutable;

This use of the volatile type qualifier indicates that the object pointed to may change (see Why is volatile needed in C?) however the const qualifier indicates the object is not be modified through the pointer provided. This is from section 6.7.3 Type qualifiers, paragraph 10 on page 109 of WG14/N1124 Committee Draft — May 6, 2005 ISO/IEC 9899:TC2

The relevant sections, section 5 and section 6, from the document along with two notes, 113 and 114, are as follows.

5 If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined. 113)

6 An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously. 114) What constitutes an access to an object that has volatile-qualified type is implementation-defined.

113) This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program (such as an object at a memory-mapped input/output address).

114) A volatile declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously interrupting function. Actions on objects so declared shall not be ‘‘optimized out’’ by an implementation or reordered except as permitted by the rules for evaluating expressions.

Also an answer from stackoverflow Difference between const & const volatile mentions this article, Combining C’s volatile and const Keywords which states:

Though the essence of the volatile (“ever-changing”) and const (“read-only”) decorators may seem at first glance opposed, there are some times when it makes sense to use them both to declare one variable. The scenarios I’ve run across have involved pointers to memory-mapped hardware registers and shared memory areas.

See also the article How to Use C's volatile Keyword in which has this to say about the proper use of the volatile qualifier.

A variable should be declared volatile whenever its value could change unexpectedly. In practice, only three types of variables could change:

  1. Memory-mapped peripheral registers

  2. Global variables modified by an interrupt service routine

  3. Global variables accessed by multiple tasks within a multi-threaded application

Note of Warning re Multi-threaded Applications

One note of warning is that this construct does not provide synchronization so should be used cautiously in multi-threaded applications with multiple threads referencing and modifying the global data object referenced via the pointers.

Using the volatile key word provides only a notification to the compiler that the data object may change due to some external event. There is no synchronization of the data object between multiple threads. There is no prevention of a data object being in an inconsistent state due to a partial update before a thread is swapped out. The data object could be in an inconsistent state at the point a time slice completes or operating system API is called by a thread which is updating the data object and the update is incomplete before the thread is put on the wait list.

This would also seem to be a consideration for complex data objects using shared memory being updated by some piece of hardware.

Andre Alexandrescu wrote an article in Dr. Dobbs (2001 before C++11) about the use of the volatile qualifier in multi-thread applications, volatile: The Multithreaded Programmer's Best Friend which describes the use of volatile and its effect on compiler optimization along with locks and mutexes. Also see Volatile in C++11 whose answer describes compiler optimization and volatile for C++11.

Community
  • 1
  • 1
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
  • Second paragraph is completely wrong, and is contradictory to everything I said the in the comments. – 2501 Feb 15 '16 at 08:28
  • @2501, thank you for pointing out the mistake I made in the meaning of `const T *p` in the second paragraph. I have corrected that and added the meaning of `T * const p` as well. This then leads into the `T const * const p` meaning which is the core of the answer. – Richard Chambers Feb 15 '16 at 14:25
  • Ok. *This frees the compiler up for any optimizations it wishes to do.* This is still misleading, pointers to const don't help with compiler optimization because the dereferenced value must be reloaded every time. – 2501 Feb 15 '16 at 14:29
  • @2501, I have removed that last sentence about compiler optimization. I can see where it could be misleading and it is not really necessary for the sense of the second paragraph which is really about the three syntax variations of `const` the question is really about. – Richard Chambers Feb 15 '16 at 15:05
  • *though the object or variable pointed to will not change.* The variable may change, just not trough this specific pointer. – 2501 Feb 15 '16 at 15:37
  • I just noticed the third paragraph is completely wrong. As I have been saying all along, the fact that a pointer is pointing to a variable doesn't change the fact that the variable if not defined with const can be changed, even if the pointer is a pointer to const and points to that specific variable. – 2501 Feb 15 '16 at 15:41
  • @2501, not sure that I understand your concerns about para three. From my reading of the C Standard along with the various articles mentioned, the third paragraph seems to be correct. This has to do with global variables and what is undefined behavior. The reason for including the `volatile` keyword is to make the possibility of the object pointed to being changed in some fashion a defined behavior. – Richard Chambers Feb 15 '16 at 16:13
  • @2501, reviewing the question and my answer, I have made changes to both. First of all the question should represent that this is a multi-file source with declarations in an include file that are used in some other file that may have no visibility to the file where the objects and pointers are defined. Secondly, I made a mistake in my use of the `volatile` qualifier in the answer where I was qualifying the pointer as `volatile` rather than the object pointed to. – Richard Chambers Feb 15 '16 at 16:51
  • `extern T const * const p;` may point to an object T that wasn't defined with const. Anyone can modify that object. As I have said multiple times: `int a; const int* const p = &a; int* const m = &a , printf("%d\n" , *p ); *m = 123; printf("%d\n" , *p);` The value of a changed. The fact that p is pointing to a doesn't matter. `m` or `&a` can be passed to a function where the value of a is changed using the pointer argument. Therefore third paragraph is not correct. Second is still incorrect. First is correct only if a variable itself defined with const, not a pointer to a const variable. – 2501 Feb 15 '16 at 18:22
  • @2501, the fact that a programmer can work around `const` is not really the point of this question or the answer. The point of this question and answer is what C syntax can be used to communicate to other programmers as well as the compiler that a particular global variable is to only be changed by specific pieces of code and every where else the variable is not to be modified and to communicate this in a way that allows the C compiler to perform some checks and while maintaining Defined Behavior. Particular C syntax has particular meanings or semantics even if the meaning can be subverted. – Richard Chambers Feb 15 '16 at 19:23
  • You didn't address any of my arguments. What the point of this question/answer is is irrelevant, statements made in your answer are wrong and the answer is is misleading. – 2501 Feb 15 '16 at 20:15
  • 1
    The first 3 paragraphs of this answer are completely wrong, and the section on `volatile` contains bogus advice (`volatile` should NOT be used for objects just because they are modified by multiple threads; it's only appropriate for objects that change due to factors external to the program, such as memory-mapped hardware) – M.M Feb 15 '16 at 20:40