Is it UB to cast away const and read value? [duplicate]
Asked Answered
V

1

7

Clarification: My question is:

  • Is it UB to use an lvalue of type int to access an object of effective type const int ?

This question has two code samples which use an lvalue of type int to access an object of effective type const int, and my intent is to achieve this with as little distraction as possible. If there is any other source of UB besides this specific problem please leave a comment and I will try to update the code sample.


Here is a specific code example for discussion:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    const int c = 5;

    printf("%d\n", *(int *)&c);
}

The reason I think it might be UB is that the strict aliasing rule seems to say that it is UB:

C11 6.5/7

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

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

The effective type of the object (6.5/6) here is const int.

First bullet point: int and const int are not compatible types (6.2.7/1, 6.7.3/10).

Second bullet point: int does not seem to be a qualified version of const int, because we didn't produce it by adding qualifiers. However 6.2.5/26 is unclear:

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.

It doesn't define what a "qualified version of const int" would be, it only defines the term "qualified version" when applied to an unqualified type.


Second code sample:

int *pc = malloc(sizeof *pc);
memcpy(pc, &c, sizeof c);
printf("%d\n", *pc);   // UB?

Since memcpy preserves the effective type (6.5/6) , reading through *pc has exactly the same interaction with the strict aliasing rule as reading through *(int *)&c does in the first example.

Vistula answered 18/2, 2015 at 1:42 Comment(5)
Why do you need to un-const a variable that you only read?Tews
@Tews I don't need to but it would be interesting to know whether this is UB or not, and the same principle is relevant in other questions (e.g. the corollary I have just added)Vistula
I can tell you for certain this may not work in practice (casting away const on a small POD). I got bit in the ass by it when GCC put a const character in a register, and then I tired to modify it :) It resulted in a SGIBUS or a SIGTERM (its been a few years since it happened). Mess with const-ness at your own peril :)Huda
@Huda no doubt that modifying it is UB, but what if you only read it?Vistula
@user3386109 I would like to know if it violates any rules at all . I've provided a start by quoting the strict aliasing rule as it is a likely candidate but I do not want to limit discussion to just the strict aliasing rule. I don't feel at this stage that it's worthy of two separate questions (1. does the strict aliasing rule apply, 2. is there any other issue) because I have no reason to believe there is any other issie besides struct aliasing -- but I could be wrong and I'd like to leave it open for anyone else to submit anything. Agree that we should delete these commentsVistula
T
3

It is not. What you have found is why it cannot be implicitely cast.

[6.2.5/26] states:

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.

(Note: each unqualified type. const int is not unqualified but int is unqualified.)

With footnote:

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

This means reading it will work the same way and yield the same value.

[6.7.3/6] specifies UB only for modifications:

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.

Tews answered 18/2, 2015 at 2:53 Comment(10)
Footnotes are non-normative, but in any case, we are not talking about arguments to functions, return values from functions, or members of unions, so I don't see how that applies or how you conclude from it that the read must be OK. What do you think about the equivalent question but with volatile instead of const ?Vistula
It has the same representation and alignment requirements so you should be able to read it in the same way. The footnote is useful to understand the reason behind it. For volatile, [6.7.3/6] specifies that any non-volatile reference to volatile is UB.Tews
Good spotting about the explicit prohibition of volatile in 6.7.3/6 , I have removed that paragraph from my questionVistula
Are you saying then that the strict aliasing rule has defective text and it should also include reading via "anything with the same representation and alignment requirements" ?Vistula
It does violate strict aliasing rules indeed. But aliasing can trigger UB only when the value is changed (reordering reads is safe) which would result in UB anyway.Tews
The strict aliasing rule says "accessing" which means both reading and writing, and it says "shall" which means that violating it causes UB.Vistula
Do you think reading data from a file and then converting part of it to struct Something* causes UB? It definitely violates strict aliasing.Tews
Violating strict aliasing causes UB. If you use a struct Something * to point at a char buffer then you violate strict aliasing; if you memcpy from a char buffer into a struct Something or into a malloc'd area then you don't.Vistula
@M.M: The only way to read the aliasing rules as not invalidating a substantial portion of the pre-existing corpus of C code is as defining a minimum set of circumstances in which implementations are required to recognize aliasing (e.g. when a pointer to a nop-qualified type is used to access an object of qualified type). Since a platform could use padding bits to trap attempts at type pinning via types other than "char", no other type of type punning is guaranteed to be useful on all platforms; thus so the authors of the Standard saw no need to require support for other forms.Stacistacia
@M.M: Unfortunately, because of how badly the Standard was written, it has been interpreted as suggesting that even in cases where aliasing would be both obvious and useful, recognizing it should not be considered necessary for a quality implementation [notwithstanding the fact that the Standard explicitly refrains from saying what factors would make one implementation superior or inferior to another].Stacistacia

© 2022 - 2024 — McMap. All rights reserved.