7

For example, is the following function legal:

struct two_int {
  const int a, b;
}

void copy_two(const two_int *src, two_int *dest) {
  memcpy(dest, src, sizeof(two_int));
}

It seems like at least some types of modifications of constant-defined values is not allowed, but it is not clear to me if this qualifies.

If the answer is "it is not allowed, in general", I'm also wondering about the special case where dest is newly allocated memory with malloc (and hence hasn't yet been assigned any value), such as:

two_int  s = {.a = 1, .b = 2};
two_int *d = malloc(sizeof(two_int));
copy_two(&s, d);

Update: It seems like the latter question seems to answered in the affirmative (it's OK) for the case of a newly malloc'd structure, but the original, more general question still stands, I think.

SODIMM
  • 303
  • 2
  • 12
  • 4
    No. Attempting to modify constant variables *anywhere* is *undefined behavior*. – Some programmer dude Feb 11 '17 at 21:04
  • 2
    That implies that `two_int` can _never_ be allocated via `malloc` or otherwise dynamically, right? Since you can't pass any construction information to `malloc`... – SODIMM Feb 11 '17 at 21:06
  • 1
    This question might qualify for the [language-lawyer] tag. – Roland Illig Feb 11 '17 at 21:09
  • 1
    http://stackoverflow.com/questions/9691404/how-to-initialize-const-in-a-struct-in-c-with-malloc – Mat Feb 11 '17 at 21:11
  • 1
    Unfortunately, the linked question only covers the special case of a newly `malloc`d structure, so it doesn't answer my original question of whether it is allowed to memcpy on top of them _in general_. – SODIMM Feb 11 '17 at 21:21
  • 1
    @SODIMM It's not possible in general (http://stackoverflow.com/questions/42056823/can-i-write-to-a-const-member-of-a-non-const-struct), only if the underlying memory is dynamic. – Petr Skocik Feb 11 '17 at 21:23
  • There is something wrong and/or strange in defining things this way. How much useful is, to use _const_ without an initializer? The only thing that comes to my mind is if the struct already contains some data, perhaps because it is an alias for some hardware related resource. But then there would not be any malloc() or other allocation involved. – linuxfan says Reinstate Monica Feb 12 '17 at 12:18
  • `const` is very useful because it indicates that something won't change after construction. Of course I _want_ to use it with an initializer, and I may want to overwrite it some time. The use of `malloc` is more-or-less orthogonal to all that, but it brings up this difficulty with initialization. – SODIMM Feb 12 '17 at 16:57
  • Logically, I cannot see how an automatic `struct` variable would be different from a `malloc`d one in regards to having the ability to be initialized via `memcpy`. However, a `struct` with static storage duration may have `const` members in truly read-only memory. – jxh Feb 27 '17 at 18:09
  • @jxh: If a compiler knows that a member of an automatic variable is `const` and was initialized to a constant value, it could replace all uses of that member with that constant. – supercat Mar 31 '17 at 16:33

2 Answers2

0

Use of memcpy for such purposes would have defined behavior only if the actual destination object does not have static or automatic duration.

Given the code:

struct foo { double const x; };
void outsideFunction(struct foo *p);

double test(void)
{
  struct foo s1 = {0.12345678};
  outsideFunction(&s1);
  return 0.12345678;
}

a compiler would be entitled to optimize the function to:

double test(void)
{
  static struct foo const s1 = {0.12345678};
  outsideFunction(&s1);
  return s1.x;
}

On many processors, the only way to load a double with an arbitrary constant is to load its value from object holding that constant, and in this case the compiler would conveniently know an object (s1.x) which must hold the constant 0.12345678. The net effect would be that code which used memcpy to write to s1.x could corrupt other uses of the numeric constant 0.12345678. As the saying goes, "variables won't; constants aren't". Nasty stuff.

The problem would not exist for objects of allocated duration because memcpy requires a compiler to "forget" everything about what had previously been stored in the destination storage, including any "effective type". The declared types of static and automatic objects exist independent of anything being stored into them, and cannot be erased by "memcpy" nor any other means, but allocated-duration objects only have an "effective type" which would get erased.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • This is a valid example, but isn't this a special case of the general problem? If you exclude structs that only consist of const members, then could it be copyable? – jxh Mar 31 '17 at 17:57
  • @jxh: I started out with a more complicated example that would fail in that case. Suppose `struct foo` had non-constant members, but the function took an argument `v`, initialized `s1.x` to `v+0.12345678`, and then returned `v+0.12345678`. The compiler would be entitled to notice that the value in `s1.x` precisely matched what it needed to return, and thus load that value. I could see usefulness to having a stronger form of `const` which would promise that if a code read an object's value, any later reads of that object *using a pointer which was derived... – supercat Mar 31 '17 at 18:38
  • ...from that objects's address without any type conversions* would yield the same value, but that would require recognizing that type conversions should affect a compiler's assumptions about a pointer's target. I would think it obvious that they should, but the design of compilers like gcc would make that difficult. – supercat Mar 31 '17 at 18:42
  • I misunderstood your example. You are demonstrating unexpected behavior by allowing the copying. But, the `memcpy` of a `malloc`d structure can demonstrate the same unexpected behavior. `struct foo *s = memcpy(malloc(sizeof(*s)), &foo_init, sizeof(*s)); return s;` When `s` is returned, there could be code that did something like: `if (s->x == SOME_CONSTANT) { memcpy(s, &foo_next, sizeof(*s)); return SOME_CONSTANT; }`. If the compiler notices that `s->x` is supposed to be `const`, and optimizes it to `return s->x` instead, then the same bad thing happens. – jxh Mar 31 '17 at 18:54
  • @jxh: The first assignment would only set the effective type until the next time code attempts to write the storage. The memcpy would erase the effective type, perform the copy, and then set the effective type to match that of the source. For some application fields, it would be helpful to have rules which made effective types apply for the lifetime of the underlying storage, but the Committee has deliberately refrained from doing so. IMHO, there should be a means via which code can waive the ability to change effective types, thus allowing more optimizations if code won't... – supercat Mar 31 '17 at 19:18
  • ...need to reuse storage allocations as different types, but a conforming compiler would not be entitled to assume that the storage underlying `s->x` could not be overwritten with some other object. – supercat Mar 31 '17 at 19:22
0

First note, that almost anything is allowed, in the wider sense of the word: It might simply have undefined behavior. You can take an arbitrary number, reinterpret_cast<> it as an address, and write to that address - perfectly "legal" C++. As for what it does - usually, no guarantees. Here be dragons.

However, as indicated in an answer to this question:

behavior of const_cast in C++

if the structure you're pointing to was not originally defined as const, and simply got const'ified on the way to your code, then const_cast'ing the reference or pointer to it and setting the pointed-to values is required to succeed as you might expect.

einpoklum
  • 118,144
  • 57
  • 340
  • 684