1

With this definition:

struct vector {
    const float x;
    const float y;
};

would the code snippet below possibly result in undefined behavior?

struct vector src = {.x=1.0, .y=1.0};
struct vector dst;
void *dstPtr = &dst;    
memcpy(dstPtr, &src, sizeof dst);

gcc and clang do not emit any warnings, but it does result in modification of a const-qualified type.

The construct looks a lot like the one given in the accepted answer to How to initialize const members of structs on the heap , which apparently is conformant. I do not understand how my example would therefore be non-conformant.

Reinier Torenbeek
  • 16,669
  • 7
  • 46
  • 69
  • Can you be more precise about which part you think could invoke undefined behavior? I don't see anything suspicious – UnholySheep Jan 17 '19 at 20:46
  • 2
    See OP's last question: https://stackoverflow.com/questions/54242966/is-this-usage-of-the-const-keyword-in-line-with-its-intention – dbush Jan 17 '19 at 20:48
  • 1
    Related: https://softwareengineering.stackexchange.com/questions/222457/is-it-a-good-idea-to-const-qualify-the-fields-of-structure-in-c – Barmar Jan 17 '19 at 20:50
  • 1
    With the other answer there, this question should be closed as a duplicate. – SergeyA Jan 17 '19 at 20:56
  • Possible duplicate of [Is this usage of the const keyword in line with its intention?](https://stackoverflow.com/questions/54242966/is-this-usage-of-the-const-keyword-in-line-with-its-intention) – SergeyA Jan 17 '19 at 20:56
  • @SergeyA The other question seemed too broad and long, I realized that when I read the responses. This question here focuses on the non-opinion part. I may close one or the other later, but at this point I believe they address different questions. – Reinier Torenbeek Jan 17 '19 at 20:59
  • 2
    A `struct` that has a `const` member can _not_ be set by _assignment_, only by _initialization_ (which you do for `src`). The `memcpy` is similar to assignment. Remove the `const` from within the `struct` and things will be fine. IMO, `const` on struct members is not so useful because of the very problem you're encountering. Note that the `memcpy` won't harm anything even with `const`, but it's bad practice. – Craig Estey Jan 17 '19 at 21:03
  • 3
    @CraigEstey it can harm. Consider the code - `{struct vector foo={...}; float a = foo.x ;memcpy(&foo, ..., sizeof(foo)); int b = foo.x;}`. The compiler can reuse the value of `a` to set `b`. – Ajay Brahmakshatriya Jan 17 '19 at 21:07
  • Would you expect a compiler warning if the `void *` pointer was passed to another function which in turn called `memcpy`? How far away from the `const` would you expect the compiler to analyse? – Weather Vane Jan 17 '19 at 21:08
  • Continuing, leaving off `const` on struct _members_, [again, IMO] a better use of `const` would be on a pointer to [or instance of] the `struct` (e.g. `const struct vector *vec_nochangeme_ptr = &src;` or `const struct vector src = { ... };` – Craig Estey Jan 17 '19 at 21:08
  • @AjayBrahmakshatriya I guess by "harm", I should have said segfault. But, here, `dst` is unitialized until after the `memcpy` – Craig Estey Jan 17 '19 at 21:13
  • @WeatherVane Good question... I think it would be appropriate to emit a warning at the moment of `void *dstPtr = &dst;`. But it should accept `const void *dstPtr = &dst;`. Just thinking out loud. – Reinier Torenbeek Jan 17 '19 at 21:32
  • @AjayBrahmakshatriya Your example made it all clear to me. Would you mind promoting it to an answer? – Reinier Torenbeek Jan 17 '19 at 21:47
  • @Barmar Yes, that question "Is it a good idea to const-qualify the fields of structure in C?" is related. I know that the suggested opaque pointers are a common approach, but for this particular case I do not like the create/destroy semantics associated with that. Having the instances on the stack is more appealing. – Reinier Torenbeek Jan 17 '19 at 22:05
  • 3
    Seems like a good question, not sure why anyone downvoted it (although the last paragraph is unnecessary) – M.M Jan 17 '19 at 22:53
  • @M.M Thanks. I added the last paragraph because SO instructed me to motivate why this is not a duplicate (it was flagged as such). I have not asked many questions before so am still learning... – Reinier Torenbeek Jan 17 '19 at 22:57
  • 2
    I do not understand why this question got downvoted; It's a legitime and well formulated question, and I don't think that it is straight forward to answer. – Stephan Lechner Jan 17 '19 at 23:04
  • 2
    Unfortunately there's a handful of people who seem determined to close every question – M.M Jan 17 '19 at 23:21

1 Answers1

2

The const-qualifiers on members let the compiler assume that - after an object has been initialized - these members must not be altered through any way, and it may optimise code accordingly (cf, for example, @Ajay Brahmakshatriya comment).

So it is essential to distinguish the initialization phase from the subsequent phases where assignments would apply, i.e. from when on may a compiler assume that an object got initialized and has an effective type to rely on.

I think there is a main difference between your example and that in the accepted answer you cited. In this SO answer, the target aggregate object with const-qualified member types is created through malloc:

ImmutablePoint init = { .x = x, .y = y };
ImmutablePoint *p = malloc(sizeof *p);
memcpy(p, &init, sizeof *p);

According to the rules on how a stored value of an object may be accessed (cf. this part of an online c standard draft), the p-target object will get its effective type the first time in the course of performing memcpy; the effective type is then that of the source object init, and the first memcpy on an object that got malloced can be seen as an initialization. Modifying the target object's const members afterwards, however, would be UB then (I think that even a second memcpy would be UB, but that's probably opinion based).

6.5 Expressions

  1. The effective type of an object for an access to its stored value is the declared type of the object, if any.87) ... If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

87) Allocated objects have no declared type.

In your example, however, the target object dst already has a declared type through it's definition struct vector dst;. Hence, the const-qualifiers on dst's members are already in place before the memcpy is applied, and it has to be seen as an assignment rather than an initialization.

So I'd vote for UB in this case.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • Thanks for the response, that is an interesting citation and seems to explain it indeed. However, I have a problem interpreting "an object having no declared type". Isn't `p` declared as `ImmutablePoint *`? – Reinier Torenbeek Jan 17 '19 at 23:07
  • 1
    I don't think so; `p` is a different object than that to which it points; `p` has the declared type `ImmutablePoint *`, but the object to which it points has not even been created. – Stephan Lechner Jan 17 '19 at 23:09
  • 1
    OK, it was not entirely clear to me what 'object' meant in this context. I also found in [7.22.3.4](http://port70.net/~nsz/c/c11/n1570.html#7.22.3.4) "The malloc function allocates space for an *object* whose size is specified by size and whose value is indeterminate.", and I suppose, its type is then indeterminate too. – Reinier Torenbeek Jan 17 '19 at 23:22
  • I would like to still add that in this code - `foo(const char* t) {char s = *t; some_function_call(t); char r = *t;}`, the compiler cannot optimize the second read even if `t`, is marked to be pointing to a `const` qualified `char`. This is because `t` might not be originally pointing to a `const` object and it is perfectly valid for `some_function_call` to modify the contents. But this case is special because the `const` is inside the struct. Any pointer's field will always be `const`. – Ajay Brahmakshatriya Jan 17 '19 at 23:30
  • 1
    @ReinierTorenbeek objects allocated by `malloc` have no type yet. They can have *effective type* set by writes into the object. – M.M Jan 17 '19 at 23:31