17

Unlike C++, C has no notion of a const_cast. That is, there is no valid way to convert a const-qualified pointer to an unqualified pointer:

void const * p;
void * q = p;    // not good

First off: Is this cast actually undefined behaviour?

In any event, GCC warns about this. To make "clean" code that requires a const-cast (i.e. where I can guarantee that I won't mutate the contents, but all I have is a mutable pointer), I have seen the following "conversion" trick:

typedef union constcaster_
{
    void * mp;
    void const * cp;
} constcaster;

Usage: u.cp = p; q = u.mp;.

What are the C language rules on casting away constness through such a union? My knowledge of C is only very patchy, but I've heard that C is far more lenient about union access than C++, so while I have a bad feeling about this construction, I would like an argument from the standard (C99 I suppose, though if this has changed in C11 it'll be good to know).

timrau
  • 22,578
  • 4
  • 51
  • 64
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084

4 Answers4

10

It's implementation defined, see C99 6.5.2.3/5:

if the value of a member of a union object is used when the most recent store to the object was to a different member, the behavior is implementation-defined.

Update: @AaronMcDaid commented that this might be well-defined after all.

The standard specified the following 6.2.5/27:

Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.27)

27) The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

And (6.7.2.1/14):

A pointer to a union object, suitably converted, points to each of its members (or if a member is a bitfield, then to the unit in which it resides), and vice versa.

One might conclude that, in this particular case, there is only room for exactly one way to access the elements in the union.

Lindydancer
  • 25,428
  • 4
  • 49
  • 68
  • 1
    Nice! So I'm not guaranteed that this will actually do what I think? – Kerrek SB Jan 12 '12 at 14:39
  • 1
    Well, "implementatio defined" means that compiler provider has to specify what happens. However, you can't rely on it working for *all* compilers. – Lindydancer Jan 12 '12 at 14:46
  • Is there any guarantee as to the location in memory of the members of a union? With `constcaster u;`, surely `&(u.mp) == &(u.cp)`? Or is this allowed this change from compiler to compiler? If we did have this guarantee, and some other guarantee that const and non-const are represented the same say, then surely it would be well-defined in this particular case? – Aaron McDaid Jan 12 '12 at 15:00
  • @AaronMcDaid: I believe that all union members are guaranteed to have the same address... I'm pretty sure in C++ it follows directly from "A union behaves like a struct containing only the active member", and C must have something similar... – Kerrek SB Jan 12 '12 at 15:05
  • While this isn't as detailed as Kenny's answer, I'll accept this since it's straight to the point, and Kenny doesn't need the rep ;-) – Kerrek SB Jan 12 '12 at 15:10
  • Do you mean §6.7.2.1/14? (/16 in n1570). – kennytm Jan 12 '12 at 15:32
  • @Lindydancer: thtanks -- hmm, "interchangeability" seems to imply that the union trick *is* correct, non? – Kerrek SB Jan 12 '12 at 16:05
  • Yes, one could conclude that. The way I see it, there is no room for an implementation to do something else. Again, this only applies to this particular use of an union, all other still fall into the "implementation defined" category. Anyway, for all practical purposes, it will work, even if it's not possible to to prove it using language-lawyer-logic. ;) – Lindydancer Jan 12 '12 at 16:10
3

My understanding it that the UB can arise only if you try to modify a const-declared object.

So the following code is not UB:

int x = 0;
const int *cp = &x;
int *p = (int*)cp;
*p = 1; /* OK: x is not a const object */

But this is UB:

const int cx = 0;
const int *cp = &cx;
int *p = (int*)cp;
*p = 1; /* UB: cx is const */

The use of a union instead of a cast should not make any difference here.

From the C99 specs (6.7.3 Type qualifiers):

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.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • 1
    You may assume that I never mutate anything. That is, I have a non-mutating operation that needs to deal with a legacy mutable pointer. I understand that violating constness is UB -- my question is mainly about the pointer juggling. In C++ I would just say `void * q = const_cast(p);`... – Kerrek SB Jan 12 '12 at 14:25
  • @KerrekSB Isn't `const_cast` just syntactic sugar for `(void*)` (maybe with some additional checks but the same behaviour)? – Christian Rau Jan 12 '12 at 14:31
  • 2
    @ChristianRau: No, it's part of the language, and the type system. If you will, the entire type system is just syntactic sugar over the assembler, so the distinction is important here... – Kerrek SB Jan 12 '12 at 14:32
  • @KerrekSB I know it's part of the language, but it is just a type-safer way to write `(void*)`. But Ok, I see we're moving in standard territory here, where saying "should behave as" isn't enough. – Christian Rau Jan 12 '12 at 14:34
  • @ChristianRau: The important part of const_cast _is_ the additional checks. – Mooing Duck Jan 14 '12 at 18:53
  • @MooingDuck Yes, but the behaviour (if the checks are successful) is still the same as `(void*)`. And those checks don't prevent you from modifying the cast storage, don't they. – Christian Rau Jan 15 '12 at 00:08
3

The initialization certainly won't cause UB. The conversion between qualified pointer types is explicitly allowed in §6.3.2.3/2 (n1570 (C11)). It's the use of content in that pointer afterwards that cause UB (see @rodrigo's answer).

However, you need an explicit cast to convert a void* to a const void*, because the constraint of simple assignment still require all qualifier on the LHS appear on the RHS.

§6.7.9/11: ... The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.

§6.5.16.1/1: (Simple Assignment / Contraints)

  • ... both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
  • ... one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

I don't know why gcc just gives a warning though.


And for the union trick, yes it's not UB, but still the result is probably unspecified.

§6.5.2.3/3 fn 95: If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning"). This might be a trap representation.

§6.2.6.1/7: When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values. (* Note: see also §6.5.2.3/6 for an exception, but it doesn't apply here)


The corresponding sections in n1124 (C99) are

  • C11 §6.3.2.3/2 = C99 §6.3.2.3/2
  • C11 §6.7.9/11 = C99 §6.7.8/11
  • C11 §6.5.16.1/1 = C99 §6.5.16.1/1
  • C11 §6.5.2.3/3 fn 95 = missing ("type punning" doesn't appear in C99)
  • C11 §6.2.6.1/7 = C99 §6.2.6.1/7
Community
  • 1
  • 1
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Great, thanks! So do you think the best way to write C code is to have an explicit cast when necessary and pass `-Wno-cast-qual` to GCC? – Kerrek SB Jan 12 '12 at 14:52
  • @KerrekSB: I certainly favor stricter warnings on implicit casts which may cause problem in the future ☺, and probably better to use `#pragma GCC diagnostic` to minimize the region where the warning is disabled. – kennytm Jan 12 '12 at 14:58
  • Even though this is the best answer, would you support my accepting a lower-rep answer? It's about reputation-motivation-utility, you see :-) – Kerrek SB Jan 12 '12 at 15:01
  • This also deserves many extra +1's for the C11-to-C99 mapping! – Kerrek SB Jan 12 '12 at 15:11
0

Don't cast it at all. It's a pointer to const which means that attempting to modify the data is not allowed and in many implementations will cause the program to crash if the pointer points to unmodifiable memory. Even if you know the memmory can be modified, there may be other pointers to it that do not expect it to change e.g. if it is part of the storage of a logically immutable string.

The warning is there for good reason.

If you need to modify the content of a const pointer, the portable safe way to do it is first to copy the memory it points to and then modify that.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • From his comments I think he is aware of these implications and wants to pass it to a legacy function, accepting a non-const pointer and not modifying the data (whatever the reasons were to make this take a non-const in the first place). And by the way, what's the answer to the actual question? – Christian Rau Jan 12 '12 at 14:37
  • @Christian Rau: if you know the lagacy function does not modify the data, then an explicit cast would be OK as in KennyTM's comment on the original question. – JeremyP Jan 12 '12 at 14:41
  • 1
    @Kerrek SB C allows you to pass a non const pointer to a const parameter. `const char* p = q;` where q is char* is fine. – JeremyP Jan 12 '12 at 14:43
  • 1
    @KerrekSB You need const_cast to go from non-const to const? – Random832 Jan 12 '12 at 14:44
  • @JeremyP Ah, so you see the *non-answerness* (or maybe *commentness*) of your *answer*. – Christian Rau Jan 12 '12 at 14:44
  • @Random832: Oh, I described it wrong. The larger array is const, but parts of it form a sort of mutable cache area that needs to be modified - but the original function only looks at the const part of the array. I think. Sorry for that. Never mind, this is just one of multiple situations. I delete the original comment to not cause further confusion. My question stands. – Kerrek SB Jan 12 '12 at 14:48
  • 1
    @Christian: No. My answer is the correct general answer to the question given. **Don't** cast const to non-const and here's why etc. – JeremyP Jan 12 '12 at 14:58
  • @JeremyP Well the specific question about the union-style cast isn't answered in any way. It's just another "HELP, CONST-CORRECTNESS IN DANGER!"-answer, although the OP had a perfectly valid question. Reminds me of the never ending micro-optimization discussions, which are of a similar nature. Whereas the general advice is good, it's just a comment and no answer. – Christian Rau Jan 12 '12 at 15:17
  • Though let it not be said that Jeremy had no point: After expunging the unholy union from the code (not my own code, by the way), a subsequent cleanup revealed that lots of the cast issues could be fixed by fixing function signatures. I'm hoping that the ultimate need for qualifyer-casts will be very small. – Kerrek SB Jan 12 '12 at 15:44
  • There's no reason why (for example) the `arg` parameter to `pthread_create` shouldn't be a pointer-to-const converted to `void*`, and then converted back to the correct type before use in the thread's start routine. In a context like that, casting a const pointer to `void*` is no better or worse than casting any pointer to `void*`. You're going to have to convert it back to the correct type before use, and that correct type may involve const. Functions like `memcpy` mean allowing *implicit* const-discarding conversion to `void*` would be risky, but they aren't the only places `void*` is used. – Steve Jessop Jan 12 '12 at 16:20
  • 1
    @Christian: How is "don't do it" not an answer to the union style cast question? – JeremyP Jan 13 '12 at 09:04
  • @Steve Jessop: your argument about `pthread_create` only works if you already know the implementation of the thread start function. – JeremyP Jan 13 '12 at 09:12
  • @JeremyP: you've used `pthread_create`, right? You know that you pass it a function pointer, plus a data pointer which is a `void*` pointing at whatever that start routine is designed/documented to use. You don't need to know the implementation, although in practice you almost always do because the most common way to call `pthread_start` is with a function that the caller writes to get on with whatever you want that thread to be doing. So your absolute prohibition against casting to `void*` is rubbish. – Steve Jessop Jan 13 '12 at 11:56
  • 2
    Maybe the misapprehension is where you say, "If you need to modify the content of a const pointer ...". Kerrek has never stated nor suggested that he's going to modify the referand of a pointer-to-const, so maybe your objections are to a different question that Kerrek didn't ask. – Steve Jessop Jan 13 '12 at 12:01
  • @Steve: you don't necessarily know the *implementation* of the function pointed to by the function pointer you pass to pthread_create. If you see a function in general that does not have const pointer parameters, unless you have the source code or the documentation explicitly says so, you cannot assume the referenced object won't be changed. This is the API contract. – JeremyP Jan 13 '12 at 14:07
  • @JeremyP Hmm? Maybe... because... the C standard doesn't just say "never ever do it!", at least not without any language-sided reason, except that it looks and is in most cases *evil*. And again, he didn't ask "can/should I do it", but what the C language says to such a construct. – Christian Rau Jan 13 '12 at 14:20
  • @Christian: it says UB which is equivalent to saying "don't do it". Yes, we can argue legalistic points about language standards here or we can promote good programming practice. You decide. – JeremyP Jan 13 '12 at 15:18
  • 1
    @JeremyP Well then add the UB-point to the answer and it's much more like a real answer. And well, I decide for actually answering questions over promoting programming practice, be it good or bad. The question was about legalistic language standard points. Blame the OP for asking such a language lawyer question instead of one about good programming practice. – Christian Rau Jan 13 '12 at 15:54