2

Consider a simple, re-usable library. It has a object for the current state, and a callback function to feed it input.

typedef struct Context_S Context_T;

typedef size_t (*GetBytes_T) (Context_T * ctx, uint8_t * bytes, size_t max);

struct Context_S {
    GetBytes_T byteFunc;
    void * extra;
    // more elements
};

void Init(Context_T * ctx, GetBytes_T func);
int GetNext(Context_T * ctx); // Calls callback when needs more bytes

User might need some extra data for callback (like file pointer). Library provides functions to have 1 extra pointer:

void SetExtra(Context_T * ctx, void * ext); // May be called after init
void * GetExtra(Context_T const * ctx); // May be called in callback

However, if user extra data is constant, it would require him to cast constness away before setting the data. I could change the functions to take/return const, but this would require extra cast in callback, if data should not be constant.

void SetExtra(Context_T * ctx, void const * ext);
void const * GetExtra(Context_T const * ctx);

Third alternative would be to hide cast inside the function calls:

void SetExtra(Context_T * ctx, void const * ext);
void * GetExtra(Context_T const * ctx);

Is it good idea to hide cast in this case?

I'm trying to find balance with usability and type safety. But since we are using void* pointers, lot of safety is gone already.

Or am I overlooking something worthy of consideration?

user694733
  • 15,208
  • 2
  • 42
  • 68
  • I think that writing to data that had const casted away is undefined behaviour – RedX Oct 07 '13 at 13:29
  • appears to me primly opinion-based, as the result depends anyway of the treatment of the implementation and its casts. – dhein Oct 07 '13 at 13:30
  • 2
    @RedX: Attempting to modify objects that were defined to be `const` is undefined behavior. Attempting to modify objects that were not to be defined to be `const` is not per se undefined behavior, even if the modification is through a pointer derived from a pointer that had a “pointer to const something” type. That is, it is permissible in C to cast away `const` and use the resulting pointer to modify an object. – Eric Postpischil Oct 07 '13 at 13:31
  • My library won't dereference extra pointer, actual pointer usage is users responsibility. – user694733 Oct 07 '13 at 13:33
  • If you wanted to make it clear that matching constness is the caller's responsibility, your `extra` could be a union of a `void *` and a `const void *`. The caller would have to select the correct member of the union when putting something into it and taking something out of it. –  Oct 07 '13 at 13:43
  • @haccks: The behavior of that code is not defined by the C standard. – Eric Postpischil Oct 07 '13 at 13:43
  • @haccks `int main(int, char**){ char const * not_r = "is in rom"; *not_r = 'a'; // this may or not crash depending on where the compiler placed the string };` – RedX Oct 07 '13 at 13:44
  • @EricPostpischil; Not defined or undefined? – haccks Oct 07 '13 at 13:45
  • @haccks: They are the same thing. The C standard makes no specification about the behavior. – Eric Postpischil Oct 07 '13 at 13:45
  • @EricPostpischil; It means that it is not allowed to do this? – haccks Oct 07 '13 at 13:49
  • @haccks: The C standard itself does not prohibit things. Many things are “allowed”. You are “allowed” to shoot yourself in the foot. You are “allowed” to write code that has behavior not defined by the C standard. A C implementation is “allowed“ to define the behavior itself or to leave it undefined. The actual question is: If you are trying to write a correct program, should you do this? The answer is definitely **no**, except in extraordinary circumstances where you have a guarantee from the C implementation (essentially the compiler) that it is supported and you have a great need for it. – Eric Postpischil Oct 07 '13 at 13:52
  • @EricPostpischil; Read this[FAQ](http://c-faq.com/ansi/constmismatch.html) – haccks Oct 07 '13 at 13:57
  • @haccks: How is that relevant? It is about pointers-to-pointers, which is not the topic here. – Eric Postpischil Oct 07 '13 at 13:59
  • @EricPostpischil; True. But there is a clue in that answer. We can do. Also I read this in this [article](https://www.google.co.in/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fwww.dansaks.com%2Farticles%2F1998-08%2520What%2520const%2520Really%2520Means.pdf&ei=e79SUo2TOsnhrAe1-oGAAQ&usg=AFQjCNGBy1g9C8y0houo1ZfyHgq2dfwqYg&sig2=qXPfq4kSGF7nvwMlJHbkNg&bvm=bv.53537100,d.bmk) (k pdf). And yes it is not mentioned in the standard. – haccks Oct 07 '13 at 14:07
  • @haccks: Neither that web page nor that article are saying modifying data defined to be `const` is supported or defined by the C standard. – Eric Postpischil Oct 07 '13 at 14:08
  • @EricPostpischil; Yes I agreed. – haccks Oct 07 '13 at 14:23

2 Answers2

5

The C standard library has similar problems. Notoriously, the strchr function accepts a const char * parameter and returns a char * value that points into the given string.

This is a deficiency in the C language: Its provisions for const do not support all the ways in which const might be reasonably used.

It is not unreasonable to follow the example of the C standard: Accept a pointer to const and, when giving it back to the calling software, provide a pointer to non-const, as in your third example.

Another alternative is to define two sets of routines, SetExtra and GetExtra that use non-const, and SetExtraConst and GetExtraConst that use const. These could be enforced at run-time with an extra bit that records whether the set context was const or non-const. However, even without enforcement, they could be helpful because they could make errors more visible in the calling code: Somebody reading the code could see that SetExtraConst is used to set the data and GetExtra (non-const) is used to get the data. (This might not help if the calling code is somewhat convoluted and uses const data in some cases and non-const data in others, but it is better to catch more errors than fewer.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 2
    `strchr`'s constness problem has an easier solution: just return the offset from the start of the string. I like to think that's what `strchr` would have done it hadn't been invented before `const`. –  Oct 07 '13 at 13:47
  • +1 Having a run-time bit check for constness is a really nice idea. – user694733 Oct 07 '13 at 13:49
1

For a standard "hack away" functional program design, it is quite simple:

  • If the function modifies the contents of a pointer parameter, the pointer should not be const.
  • If the function does not modify the contents of a pointer parameter, the pointer should always be const.

But in your case, it would rather seem that you are doing a proper object-oriented design, where your code module is the only one who knows what Context_T is and what it contains. (I take it the typedef on the first row is actually in the h file?)

If so, you cannot and should not make the pointer const. Especially not if you are implementing true OO encapsulation using incomplete type ("opaque" type), because in that case the caller can't modify the contents anyhow: the "const correctness" becomes superfluous.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Contents of the `Context_T` are not meant to be used directly without functions, but I cannot use incomplete type because user must be able to allocate it in stack if needed (embedded system). However, constness of the `Context_T` is not really the issue here, but the constness of the void * extra data pointer in function calls. – user694733 Oct 08 '13 at 06:24
  • @user694733 On embedded systems, you can let the code module allocate the memory on the stack, keeping track of where by using static private variables. Essentially you build up a private memory pool. Which of course gives some other problems to solve in turn :) Implementing incomplete type on embedded systems is a rather classic problem with no obvious best solution. Interesting reading and suggestions on that topic [can be found here](http://stackoverflow.com/questions/4440476/static-allocation-of-opaque-data-types). – Lundin Oct 08 '13 at 09:38
  • @user694733 As for the void*, it is just a poor man's (poor programmer's?) incomplete type . You can replace it with a struct which acts as an abstract base class in OO. And even implement that struct differently in different modules, which is a good way to implement inheritance/polymorphism in C. – Lundin Oct 08 '13 at 09:43
  • Using the abstract base class is a good idea, I have to test how it behaves in practice. And thanks for the link, there is definitely some interesting options for encapsulation. – user694733 Oct 08 '13 at 09:57