Can the storage of trivially copyable objects be safely reallocated with realloc?
Asked Answered
P

2

8

I know that trivially copyable objects can safely be copied my malloc into an appropriate storage location1 and that the destination object will have the same value as the source.

Is this also possible with realloc? That is, if realloc some storage containing some objects of type T, and realloc decides to move and copy the block, will the objects in the newly allocated storage be intact and have started their lifetime, and will the lifetime of the objects in the old storage be safely ended?


1 While asking this question, I had assumed that an "appropriate storage location" included uninitialized storage of suitable alignment and size, but as M.M's answer below argues this isn't actually well supported by the standard. That would make realloc questionable since it is always copying into uninitialized storage.

Pacifically answered 19/10, 2017 at 20:53 Comment(10)
Instead of asking that question you should instead invest your time in ensuring your C++ code does not use malloc, calloc, realloc and free in the first place - just avoid having to ask the question. C++ has better alternatives in all cases - use them.Seward
I'm not sure what you mean by "your C++ code", or why you would assume "my" code uses any of these methods. If we could all be so lucky as to only work on code that was "ours"...Pacifically
"the destination object will have the same value as the source" That's not what the standard says. "[basic.types]/2 For any object ... of trivially copyable type T, ... the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value." Emphasis mine. That's not what happens with reallocUzbek
Something being trivially copyable doesn’t guarantee that. If it’s poorly written, it might technically satisfy the requirements while actually owning pointers, or having non-owning internal pointers, or otherwise make it so trivially copying it is a bad idea even if the language doesn’t realize it.Experimentation
@IgorTandetnik - right, but it's [basic.types]/3 rather than /2 that is generally understood to provide the copying guarantee. Unless your claim is that trivially copyable types cannot be copied by memcpy? It's widely understood that they can and the feature is heavily used.Pacifically
Ah, right. Then I don't see a problem. The standard doesn't say that the bytes must be copied with memcpy specifically. The footnote says "By using, for example, the library functions (17.6.1.2) std::memcpy or std::memmove" realloc copies the underlying bytes - I don't see why this is not good enough.Uzbek
@DanielH - I mean it can be legally copied in the sense of the standard and have the "same value". Of course if you object internally relies on its address it might have broken it's invariants, but that's not the question here.Pacifically
The answer is that C and C++ are both absolute rubbish regarding those issues. The claim that a new expression is needed to create an object goes against everything we know about the design of C++ as derived from C. This whole C/C++ never existed is pretty much the definition of mental illness.Scrap
@JesperJuhl "you should instead invest your time in ensuring your C++ code does not use malloc, calloc, realloc and free in the first place" this is a crazy proposition. Anyway, the question still stands. operator new is no better. C++ is broken.Scrap
I used to be that infinitely many objects resided everywhere in memory, superposed. This caused no problem what so ever (only finitely many objects were active or initialized at any time) but some people discovered the fact and called it crazy. Then they broke C++ trying to fix what wasn't broken.Scrap
H
9

No, realloc cannot be used to safely move objects, even of trivially copyable types, because realloc cannot create new objects in uninitialized storage.

In particular, according to C++14 [basic.life]/1:

The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or

  • the storage which the object occupies is reused or released.

Calling realloc releases or reuses the storage (even if a reallocation doesn't occur, I'd argue, although that is moot for your question). So the lifetime of the objects ends.

The cases when an object is created is covered by [intro.objects]/1:

An object is created by a definition (3.1), by a new-expression (5.3.4) or by the implementation (12.2) when needed.

This does not include realloc; so the realloc call ends the lifetime of the old objects and does not create new objects.

Not only does this imply that realloc isn't suitable to copy trivially copyable objects, it also implies that using malloc or operator new(size_t) to obtain uninitialized storage, followed by a memcpy from an existing object into that storage does not create a usable copy of the object as the destination object has also not been created in that case.


See also: reinterpret_cast creating a trivially-default-constructible object, or constructing a trivially copyable object with memcpy for further discussion of the fact that copying bytes to a new location does not create an object in that location.

Hebner answered 19/10, 2017 at 21:9 Comment(0)
S
-3

3.8 Object lifetime is clear:

The lifetime of an object of type T begins when:

1.1 storage with the proper alignment and size for type T is obtained, and

1.2 if the object has non-vacuous initialization, its initialization is complete.

And same of end of lifetime. You can ignore the garbage in the other parts of the std, like [intro]!

Scrap answered 21/10, 2017 at 5:50 Comment(31)
When there is no object, the question of when its lifetime begins doesn't make sense.Steeplebush
Just because you don't like [intro.object]/1 doesn't mean it doesn't exist or doesn't have effects.Complacency
Interesting. Please explain how the quoted text can make sense if objects can have non-vacuous initialization..Scrap
@NicolBolas Legal text does not have effect because it is written, only because people have enough respect for it. Ridiculous laws (and contract clauses) get little respect from reasonable people. You need your compiler writers to be reasonable people for many reasons.Scrap
Objects are created, in accord with [intro.object]/1. Objects have lifetimes, in accord with [basic.life]/1. If an object has not been created, then it cannot have a lifetime. "Legal text does not have effect because it is written, only because people have enough respect for it." By that thinking, who cares what the standard says at all? The question is tagged with "language-lawyer"; ignoring the legal text of the standard is not a valid answer.Complacency
Please explain the std text I quoted. It wouldn't make sense if you were right.Scrap
The standard text you quoted only comes into play when an object exists. [intro.object]/1 defines when an object exists. And memcpy, realloc, and so forth are not on that list of things that cause an object to exist. So therefore, they cannot cause it to exist. And if there is no object, [basic.life]/1 is irrelevant.Complacency
Also, please show us when objects exist when there lifetime has not started. Do they also exist after their lifetime has ended? Please show that this opinion is not recent (post-modern). I claim that all this theory is a recent invention by people who couldn't accept that potentially infinitely many objects could be created by a simple malloc.Scrap
"The standard text you quoted only comes into play when an object exists" Are you seriously claiming that for an existing object, its lifetime "begins when: storage with the proper alignment and size for type T is obtained ()"?!!Scrap
@curiousguy: "The lifetime of an object or reference is a runtime property of the object or reference." You cannot have a runtime property of a thing that doesn't exist yet. "I claim that all this theory is a recent invention by people who couldn't accept that potentially infinitely many objects could be created by a simple malloc." Claim whatever you like, but the standard doesn't support it.Complacency
So show me an existing object whose lifetime has no started!!! The std does not "support" anything. Only interpretation of that extremely bad attempt at specification does.Scrap
Although I mostly agree with the stance that [into] implies that the above is only relevant for objects have they have been created (which is only possibly in the limited number of ways enumerated in [into.objects], this quote is still confusing to me. Why does it not just say "the lifetime of an object starts when it is created"? For objects with vacuous initialization, 1.1 seems pointless: (ignoring declarations or temporary objects) an object cannot exist until the new statement, at which point by definition "storage with the proper..." has already been obtained.Pacifically
@BeeOnRope: "Why does it not just say 'the lifetime of an object starts when it is created'?" Because of [basic.life]/1.2: objects with non-vacuous initialization do not begin their lifetime until their initialization is complete. Which includes the call to constructors, if applicable. Until then, the object still exists (since the constructor has to access it) but its lifetime hasn't started.Complacency
So either the lifetime actually starts at the moment of creation, and the "storage has been obtained" part is just a red herring or very confusingly worded, or the "lifetime" of the object actually extends back in time, before the object was created, so the moment that the storage in which an object was ultimately created was obtained. The latter seems nuts, but the former doens't explain why 1.1 exists at all. Now for objects with non-vacuous construction, the initialization will be complete at the end of creation, right? Unless you can create an object without initializing it?Pacifically
@BeeOnRope: Consider new(ptr) SomeType. This involves two distinct operations: calling ::operator new(size_t, void*), and initializing the SomeType object in that storage. So, when during that process does the SomeType object's lifetime start? Well, if SomeType has vacuous initialization, then it starts when the ::operator new call returns. If it has to call a default constructor, then it is when the default constructor returns.Complacency
@NicolBolas - but then why does 1.1 not say "when the they are created" and then only 1.2 involve the distinction regarding constructors? The text about "storage with the proper alignment and size for type T is obtained" has caused unending confusion and if it didn't exist these conversations would be a lot shorter. Also, as far as I can tell, the process of creating (via new) an object, always involves initialization, right? You can't call new without also calling a constructor. So at the end of the creation process, the object has also been initialized.Pacifically
@BeeOnRope: "but then why does 1.1 not say 'when the they are created' and then only 1.2 involve the distinction regarding constructors?" Why does it have to? For most people, and for the standard committee, it goes without saying that you cannot have started the lifetime of something that doesn't exist. It only causes "unending confusion" for people who are trying to read into the standard words that aren't there. "Also, as far as I can tell, the process of creating (via new) an object, always involves initialization, right?" No, it doesn't. new(ptr) int creates an uninitialized intComplacency
@NicolBolas - sure, the "new-expression" may involve those two components, but [intro.objects] only talks about the new-expression itself, which is the whole thing. So the object is created by the new-expression, and it is also initialized by the new expression. That is enough to simply say the lifetime starts at the point of creation, at least with respect to [info.objects].Pacifically
@NicolBolas "then it starts when the ::operator new call returns" hilarious distinction!Scrap
@NicolBolas - when I said it always involves initialization, I mean in the case of objects with non-vacuous initialization. "Why does it have to?" - because as about the existing condition seems meaningless: the cannot even being to exist before new is called, and obviously at that point (or as part of the new expression itself) suitable storage has been obtained.Pacifically
The standard seems to imply that the lifetime of an object with vacuous initialization starts at the point where suitable storage has been obtained - but this is never true for placement new. Storage is always obtained first, then the object is created. I doesn't seem to add any useful distinction in the case of non-placement new (regular new, whatever that is called either) for vacuously-initialized objects. In that case the storage is obtained and the object is created in one fell swoop.Pacifically
So my question regarding [basic.life] is why it doesn't just say: "Object lifetime begins at creation for objects with vacuous initialization or at the completion of initialization for objects with non-vacuous initialization".Pacifically
@BeeOnRope: "but this is never true for placement new. Storage is always obtained first, then the object is created." Nonsense. The steps are: 1) The object is created. 2) the ::operator new function is called, assigning the resulting storage to the object. 3) The object is initialized, if there is any initialization to be done. The object's lifetime begins either after step 2 or after step 3, but the object already existed in step 1.Complacency
@BeeOnRope: In the standard. [intro.object]/1 tells us that the new expression creates an object. [expr.new]/1 shows us the grammar for a new expression. new(ptr) Typename is a new expression by that grammar, and therefore it creates an object.Complacency
@NicolBolas - sure but that example of placement new is a direct contradiction to the order of the three streps above. Storage is created first (by code that entirely precedes the new-expression) then an object is created into it (then initialization, if any, happens). I.e., your (1) and (2) are reversed. That basic pattern for placement new I think is obvious and not contradicted anywhere in the standard. For non-placement new it's a bit different, you can mostly treat (1) and (2) as being simultaneous there, not sure if there any specific language about it.Pacifically
I think maybe I see what you're getting at though: I'm talking about storage being obtained with reference to the creation of the underlying storage itself (e.g. via ::operator new(size_t) or new char[100] or malloc or std::aligned_storage or whatever. But you are talking about when the storage (which already exists) is "assigned" or "associated" with the object, and perhaps in a sense the object's storage only is created at that moment (it existed before, but it wasn't storage for that object). Perhaps in that sense 1.1 makes sense.Pacifically
@BeeOnRope: Objects are not created "in storage". Objects occupy storage, but they are not actually that storage. Read [intro.object]/1; it makes this quite clear: " An object occupies a region of storage in its period of construction (15.7), throughout its lifetime (6.8), and in its period of destruction (15.7)." It doesn't have to have storage before any of that happens.Complacency
Yes, I see what you are getting at. It's as if the storage doesn't exist before the object is created in it. In all the discussions regarding this I haven't seen that interpretation, but it at least makes [basic.life/1.1] make sense: by "storage is obtained" they don't mean when the actual bytes underlying the storage were obtained (e..g., via malloc) but simply some action that makes the storage be associated with the object, e.g. during a new expression. It doesn't seem to make it clearer or more useful than the alternative creation-based wording, but it is feasible I suppose.Pacifically
The idea that "storage is obtained [for an object]" somehow refers only to storage that exists when the object does seems to be directly contradicted by [expr.new/8] which explains clearly that first storage is obtained by calling an allocation function, then later (e.g., in /18) the object is created in that storage. Reading various other sections seems to support the idea that the term "storage" is not only tied to the object, but exists as a separate entity (e.g., an object may be created in storage and an object occupies a range of storage).Pacifically
The lifetime of the storage is treated separately from the lifetime of the object (and indeed there is a whole section [basic.stc] dedicated to the discussion of storage lifetime as separate from object lifetime - especially relevant for dynamic storage, but even automatic storage duration can be different than object lifetime, e.g., if you are explicitly destroying objects and placing new ones there).Pacifically
Let us continue this discussion in chat.Complacency

© 2022 - 2024 — McMap. All rights reserved.