Géry Ogam
Géry Ogam

Reputation: 8027

Is it really possible to separate storage allocation from object initialization?

From [basic.life/1]:

The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or a (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),

except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union ([dcl.init.aggr], [class.base.init]), or as described in [class.union] and [class.copy.ctor], and except as described in [allocator.members].

From [dcl.init.general/1]:

If no initializer is specified for an object, the object is default-initialized.

From [basic.indet/1]:

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]). [Note 1: Objects with static or thread storage duration are zero-initialized, see [basic.start.static]. — end note]

Consider this C++ program:

int main() {
    int i;
    i = 3;
    return 0;
}

Is initialization performed in the first statement int i; or second statement i = 3; of the function main according to the C++ standard?

I think it is the former, which performs vacuous initialization to an indeterminate value and therefore begins the lifetime of the object (the latter does not perform initialization, it performs assignment to the value 3). If that is so, is it really possible to separate storage allocation from object initialization?

Upvotes: 1

Views: 408

Answers (3)

Nicol Bolas
Nicol Bolas

Reputation: 473332

If that is so, is it really possible to separate storage allocation from object initialization?

Yes:

void *ptr = malloc(sizeof(int));

ptr points to allocated storage, but no objects live in that storage (C++20 says that some objects may be there, but nevermind that now). Objects won't exist unless we create some there:

new(ptr) int;

Upvotes: 3

JaMiT
JaMiT

Reputation: 16843

Is initialization performed in the first statement int i; or second statement i = 3; of the function main according to the C++ standard?

The first. The second statement is assignment, not initialization. The second statement marks the point "until that value is replaced ([expr.ass])" from your quotes of the standard.

If [initialization is the first statement] is so, is it really possible to separate storage allocation from object initialization?

Yes, but not in a such a simple example as yours. A common example that comes to mind is a std::vector. Reserving capacity allocates storage space, but that storage is not initialized until an object is added to the vector.

std::vector<int> v;   // Allocates and initializes the vector object.
v.reserve(1);         // Ensures space has been allocated for an int object.
/*
 At this point, the first contained element has space allocated, but has
 not yet been initialized. If you want to do nutty things between allocation
 and object initialization, this is the place to do it. Note that you are
 not allowed to access the allocated space since it belongs to the vector.
 You'd have to replicate the inner workings of a vector to do that...
*/
v.emplace_back(3);    // Initializes the first contained object.

Quoting the standard

Short version:
There is nothing to quote because the standard does not explicitly prohibit all spurious actions. Compilers avoid spurious actions by their own volition.

Long version:

Strictly speaking, the standard does not guarantee that reserve() does not initialize anything. The requirements imposed on reserve() in [vector.capacity] are more focused on what must be done than on prohibiting spurious activity. The closest it comes to this guarantee is the requirement that the time complexity of reserve() be linear in the size of the container (not in the capacity, but in the current size). This would make it impossible to always initialize everything that was reserved. However, a compiler could still choose to initialize a fixed number of reserved elements, say up to 10 million of them. As long as this limit is fixed, it counts as constant-time complexity, so is allowed by [vector.capacity].

Now let's get real. Compilers are designed to produce fast code, without introducing unnecessary, useless busywork. Compilers do not seek out the possibility of doing additional work simply because the standard does not prohibit it. Except for debug builds, no compiler is going to introduce an initialization when it it not required. The people who view the possibility as something worth considering are language lawyers who lose sight of the big picture. You don't pay for what you don't need. The question to ask here is not "Could you quote the standard supporting that no initialization happens?" but "Could you quote the standard supporting that no initialization is required?" Since the additional work of initialization is not required, it will not happen in practice.

Still, reality means little to some language lawyers, and this question does have that tag. To be thorough, I will demonstrate that it is "possible to separate storage allocation from object initialization" even if you happen to use a pathological, yet standards-compliant, compiler that was over-engineered by masochists. I need only one case to demonstrate "possible", so let's abandon int for a more bizarre, yet fully legal, type.

The sole precondition for reserve() is that the contained type can be move-inserted into the container. This precondition is satisfied by the following class.

class C {
    // Default construction is not supported.
    C() = delete;
  public:
    // Move construction is allowed, even outside this class.
    C(C &&) = default;
};

I have designed this class to be rather hard to initialize. The only allowed construction is move-construction; in order to initialize an object of this type, you need to already have an object of this type. Who creates the first object? No one. No objects of this type can exist. However, one can still create a vector of these objects (an empty vector, but still a vector).

It is legal to define std::vector<C> v;, and to follow that by a call to v.reserve(1);. This allocates space (1 byte is needed on my system) for an object of type C, and yet there is no possible initialization of this object. QED.

Upvotes: 1

D&#250;thomhas
D&#250;thomhas

Reputation: 10048

You are getting confused between allocating storage for an object and initializing an object, and they are definitely not the same thing.

In your example, the object i is never initialized. As a local value, it has space reserved for its storage, but it is not initialized with any value.

The second line’s statement assigns a value of 3 to it. This again is not initialization.

Objects with global storage are required by the standard to be both allocated and initialized (to zero or whatever the default initializer does). All other objects are only initialized if the written language construct can support it.

C++ allocators work on this same distinction. The new operator, behind the scenes, both allocates and initializes objects, after which you may assign the object a new value. If you need to, though, you can use the underlying language constructs to manage the two things separately.

For most purposes you do not need to care about the difference between object initialization and assignment in your code. If you get to the point where it matters, you either already know how the concepts differ or need to learn really quickly.

Upvotes: 2

Related Questions