8

It is my understanding that something like this is okay:

const int ci = 42;
const int *cip = &ci;
int *ip = (int *)cip;
int j = *ip;

What about this?

const int ci = 42;
const int *cip = &ci;
const int **cipp = &cip;
int **ipp = (int **)cipp;
int j = **ipp;
Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
  • `int *ip = (int *)cip;` is not OK. Your compiler should complain about shedding the `const`. If not use higher warning levels. Or what kind of "OK" are you thinking of? "works", "works reliably", "works portably", "is good code", ... – Yunnosch Jul 16 '17 at 01:12
  • 7
    @Yunnosch: No. It is perfectly OK. The explicit cast is actually how you *tell* the compiler that you *really want* to shed the `const`. That's how you tell the compiler not to issue any warnings. The first code snippet is perfectly legal C. – AnT stands with Russia Jul 16 '17 at 01:14
  • @AnT For the shown code, shedding the const is not needed. For other code shedding a const which has a purpose is not a good idea. I admit however, that I confused compiler warnings with the kind of nitpicky warnings generated by explicitly super-strict static code analysers; which I am to use for code in the job. They need more than a cast to stop nagging. It is kind of burnt into my thinking. – Yunnosch Jul 16 '17 at 01:18
  • If your compiler/linker put ci in genuine read only storage then *ip = (int *)cip is an instant fault. – Gilbert Jul 16 '17 at 01:41
  • 3
    @Gilbert That may be true but `int *ip = ...;` is very different from `*ip = ...;`. Just reading through a pointer should be fine, writing through it is undefined though. – Tavian Barnes Jul 16 '17 at 01:43
  • 1
    @Gilbert: There's no `*ip = (int *)cip` in the above code (that would make no sense at all, since `*ip` is `int`). As for `int *ip = (int *)cip;`... There's no fault of any kind in `int *ip = (int *)cip;` regardless of what kind of storage `ci` is stored in. The "fault" will occur if you do `*ip = 56` after that. But `int *ip = (int *)cip;` is not only perfectly valid code, but a valuable implementational idiom widely used in C programming. – AnT stands with Russia Jul 16 '17 at 01:57
  • 1
    @Yunnosch: This is artificial code created just for the question. It is irrelevant whether shedding of `const` it is "needed" or "not needed" here. The very same shedding of `const` is used inside, say, standard `strchr` function. And there it is needed for rather obvious purpose. – AnT stands with Russia Jul 16 '17 at 01:58
  • @AnT Exactly, although a devils advocate could argue that the `(char *)` cast is only okay due to the "character type" exception, which is why I've used `int` everywhere in this example. – Tavian Barnes Jul 16 '17 at 02:07
  • 1
    @Tavian Barnes: I'm not sure what "character type exception" you are talking about. Casting away const has always been perfectly legal in C (and C++), regardless of type, as long as no attempt is made to modify a `const` object. – AnT stands with Russia Jul 16 '17 at 02:10
  • @AnT I was referring to the last bullet in the [effective type rules](https://stackoverflow.com/a/7005988/502399). I agree that casting away const *should* be legal, but there is some [doubt](https://stackoverflow.com/a/14437985/502399) over whether the letter of the standard indeed allows it. – Tavian Barnes Jul 16 '17 at 02:16

1 Answers1

5

The expression *ipp is an lvalue of type int *, however it is being used to access an object of effective type const int *. (Namely, cip).

According to the letter of the standard, it is a strict aliasing violation: the list of allowed types to alias does not include aliasing T * as const T * or vice versa.

The closest exception is this one: (C11 6.5/6 excerpt)

  • a qualified version of a type compatible with the effective type of the object

"qualified version" is clearly defined by C11 6.2.5/26:

Each unqualified type has several qualified versions of its type, corresponding to the combinations of one, two, or all three of the const, volatile, and restrict qualifiers. The qualified or unqualified versions of a type are distinct types that belong to the same type category and have the same representation and alignment requirements. A derived type is not qualified by the qualifiers (if any) of the type from which it is derived.

So the exception is that T may be aliased as const T and vice versa, but there is no similar exception for pointers to aliasable types. const T * is not a qualified version of T *.


However there is of course the footnote:

The intent of this list is to specify those circumstances in which an object may or may not be aliased

I couldn't say whether the intent of the rule is for const T * and T * to be aliasable or not. It seems unclear to me what the purpose of specifying that T * and const T * have "the same representation and alignment requirements" (6.2.5/28) would be if it is not aliasable.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    The fact that a guarantee would only be useful if it were treated as an exception to aliasing rules no longer implies that compiler writers will interpret it as one. The Common Initial Sequence guarantees are basically useless under gcc and clang except in "no strict aliasing" mode. That having been said, neither compiler *yet* assumes that a `int*p` won't be accessed using an `int const **p`, nor vice versa; I don't know whether that will remain true. – supercat Jul 17 '17 at 19:50
  • 1
    Here is some evidence of GCC optimizing under the assumption that `char *` and `unsigned char *` don't alias: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85923 – Tavian Barnes May 27 '18 at 22:03