1

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?

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
  • 5
    Hmmm, does not `extern int x; .... int x = 42;` separate the two? (Declaration from initialization) – chux - Reinstate Monica Sep 15 '21 at 17:15
  • I don't understand how anything you quoted is related to your question about separation declarations from initialization. – Nicol Bolas Sep 15 '21 at 17:16
  • 1
    From [Default initialization](https://en.cppreference.com/w/cpp/language/default_initialization) "_The effects of default initialization are: .... otherwise, no initialization is performed: the objects with automatic storage duration (and their subobjects) contain indeterminate values._" – Ted Lyngmo Sep 15 '21 at 17:19
  • `int i; i = 3;` is a _definition_ (and _declaration_) with no _initialization_ followed by an _assignment_. – chux - Reinstate Monica Sep 15 '21 at 17:19
  • Initialization is always separate from declaration when the definition is also separate from the declaration. This is pretty common, for example for `static` data members. – François Andrieux Sep 15 '21 at 17:21
  • @chux-ReinstateMonica About `extern int x;`, I don’t think external linkage is not covered by the rule ‘If no initializer is specified for an object, the object is default-initialized.’ So no, this does not separate declaration from initialization. – Géry Ogam Sep 15 '21 at 19:49
  • @NicolBolas My quotes explain why I consider `int i;` to be vacuous initialization to an indeterminate value. – Géry Ogam Sep 15 '21 at 19:52
  • @TedLyngmo Absolutely, default initialization of a scalar type means [‘no initialization is performed’](https://timsong-cpp.github.io/cppwp/dcl.init#general-7.3). But now I am wondering why it was called default initialization in the first place… – Géry Ogam Sep 15 '21 at 20:00
  • @chux-ReinstateMonica ‘`int i; i = 3;` is a *definition* (and *declaration*) with no *initialization* followed by an *assignment*.’ This seems confirmed by the standard which specifies that default initialization of a scalar type means [‘no initialization is performed’](https://timsong-cpp.github.io/cppwp/dcl.init#general-7.3). So why is it called default initialization? – Géry Ogam Sep 15 '21 at 20:04
  • It seems like you've taken "If no initializer is specified for an object, the object is default-initialized" to mean "if no initializer occurs *at the declaration*, ...". I think you should instead read this as "if no initializer occurs *at the definition*, ...". In your example `int i;` is both a declaration and definition; since no initializer occurs in this definition, its default initialization. However eg. `class C { static int x; }` is not a definition; the rule doesn't apply because its antecedent is false; so then here the declaration is separate from initialization. – user2407038 Sep 15 '21 at 20:16
  • @FrançoisAndrieux Are you sure? Static data members are [statically initialized](https://timsong-cpp.github.io/cppwp/basic.start.static#2) (constant initialized or zero initialized).at program startup so initialization is not separate from declaration. – Géry Ogam Sep 15 '21 at 20:30
  • @Maggyero "So why is it called default initialization?" --> depending on where the object is declared, the _default initialization_ (when code does not have an explicit `= ....` par) is either _no initializaiton_ or some type of zeroing. It is the _default_ action when code lacks explicit initialization. – chux - Reinstate Monica Sep 15 '21 at 20:43
  • 1
    @Maggyero Statically initialized is part of a run time process while declaration and definition are a compile time concern. It isn't clear to me what relation between them you are asking about. – François Andrieux Sep 15 '21 at 20:46
  • @chux-ReinstateMonica ‘*default initialization* […] is either no initializaiton or some type of zeroing.’ [Not exactly](https://timsong-cpp.github.io/cppwp/dcl.init#general-7.1), it is default constructor call, not zero initialization. – Géry Ogam Sep 15 '21 at 21:13
  • @Maggyero True, I was paraphrasing the C behavior. Not quite right for this C++ question. – chux - Reinstate Monica Sep 15 '21 at 21:14
  • @FrançoisAndrieux You are right, I should have used the phrase *storage allocation* instead of *declaration*, cf. [Wikipedia](https://en.wikipedia.org/wiki/Object_lifetime#Steps): ‘Object creation can be broken down into two operations: memory **allocation** and **initialization**, where initialization both includes assigning values to object fields and possibly running arbitrary other code. These are implementation-level concepts, roughly analogous to the distinction between **declaration** and **definition** of a variable, though these later are language-level distinctions.’ Fixed. – Géry Ogam Sep 15 '21 at 21:14
  • @chux-ReinstateMonica By the way, thanks to FrançoisAndrieux I realized that your `extern int x;` actually [does separate](https://timsong-cpp.github.io/cppwp/basic#def-2.2) *declaration* from *definition*, which are compile-time concepts. But it [does not separate *storage allocation* from *object initialization*](https://timsong-cpp.github.io/cppwp/dcl.init#general-11), which are run-time concepts and the topic of this post. – Géry Ogam Sep 15 '21 at 21:26
  • @FrançoisAndrieux So to come back to your remark on static data members, since static objects are [statically initialized](https://timsong-cpp.github.io/cppwp/basic.start.static#2) (constant initialized or zero initialized) *at program startup*, I think that storage allocation and object initialization are not separated even in this case, don’t you? – Géry Ogam Sep 15 '21 at 22:01
  • @Maggyero It depends, because most of the time objects in static storage also have a separate *actual* initialization. Something like a global `int i;` won't but any class type object with static storage duration will. – François Andrieux Sep 15 '21 at 22:03
  • "... and the topic of this post." --> yes, after the question's title changed. Not really fair for Dúthomhas to change the quesiton after answered arrive. Moving targets do not make good questions. You could roll-back the question, take in all that was answered and commented and after a few days form a new different question. – chux - Reinstate Monica Sep 15 '21 at 22:17
  • @chux-ReinstateMonica The wording was just imprecise (declaration -> storage allocation) but Dúthomhas perfectly understood anyway so his answer is still valid, so that is not a problem. – Géry Ogam Sep 15 '21 at 22:20
  • @FrançoisAndrieux Are you alluding to [dynamic initialization](https://timsong-cpp.github.io/cppwp/basic#start.static-note-1)? – Géry Ogam Sep 15 '21 at 22:24
  • @Maggyero I think so. I haven't see that terminology before. – François Andrieux Sep 15 '21 at 22:29

3 Answers3

3

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;
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks! Why not `void* ptr = operator new(sizeof(int));`? Could you quote the standard supporting that no initialization happens? – Géry Ogam Sep 16 '21 at 06:29
  • 1
    @Maggyero See http://eel.is/c++draft/basic.stc.dynamic#allocation-2.sentence-1. Quoting: _"An allocation function attempts to allocate the requested amount of storage."_ It is clearly written that `operator new` only allocates storage. There is no initialization involved (there is nothing to be initialized). – Daniel Langr Sep 16 '21 at 06:51
  • 1
    @Maggyero And yes, I believe `operator new` is a better example, since `malloc` since C++20 _implicitly creates objects_: http://eel.is/c++draft/c.malloc#4.sentence-1. `operator new` does not seem to do the same. – Daniel Langr Sep 16 '21 at 06:58
  • @DanielLangr Thanks for the references! Nicol Bolas, could you fix your answer (standard quote + operator new - malloc)? I will accept it. – Géry Ogam Sep 16 '21 at 07:17
  • 2
    @DanielLangr: [From](https://timsong-cpp.github.io/cppwp/n4861/intro.object#13) [intro.object]/13: "Any implicit or explicit invocation of a function named `operator new` or `operator new[]` implicitly creates objects in the returned region of storage". Also, I noted that it may create objects in C++20, but to ignore that for the purpose of this question. – Nicol Bolas Sep 16 '21 at 13:41
  • @NicolBolas Thanks for the link, didn't know that. The implicit object creation is not an easily understandable concept. – Daniel Langr Sep 16 '21 at 13:56
  • So since even `malloc`, `operator new`, and `operator new[]` can all perform object initialization, the answer to my question ‘Is it really possible to separate storage allocation from object initialization?’ is no, isn’t it? – Géry Ogam Sep 16 '21 at 15:57
  • 1
    @Maggyero: As I said, ignore implicit object creation for the purposes of this question. It is only triggered by doing certain things to memory, and the examples I posted *do not trigger it*. – Nicol Bolas Sep 16 '21 at 16:34
  • @Maggyero This answer might be useful (at least, it was for me): https://stackoverflow.com/a/61442225/580083. – Daniel Langr Sep 17 '21 at 06:26
2

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.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • 1
    The whole point about "vacuous initialization" is to make it clear that "no initialization" is considered to be a form of initialization, as far as lifetime rules are concerned. – Nicol Bolas Sep 15 '21 at 17:46
  • "Vacuous initialization" is dumb language lawyering in the standard which serves no useful purpose beyond muddying conceptual boundaries. So _"no initialization" is a form of initialization_ only gets a one handed clap from me. Nevertheless, I know my answer could be improved. Feel free to give it a go. (Or write a better one. I'll upvote it if it is.) – Dúthomhas Sep 15 '21 at 17:53
  • Meh, language lawyering == spelling out obvious stuff, IMO. But, hey, I'm too snotty about it to be a language lawyer. Seriously, if y'all come up with a better answer (or _an_ answer, maybe?) I will happily remove mine and upvote. – Dúthomhas Sep 15 '21 at 18:05
  • Thanks for the answer! ‘In your example, the object `i` is never initialized.’ This seems confirmed by the standard which specifies that default initialization of a scalar type means [‘no initialization is performed’](https://timsong-cpp.github.io/cppwp/dcl.init#general-7.3). So why is it called default initialization? – Géry Ogam Sep 15 '21 at 22:05
  • The standard lists the specific conditions under which initialization is to be performed. If none of those conditions are satisfied (hence the word “else”) then the object is not initialized (“no initialization is performed”). This is the “default” behavior. This is why I dislike language lawyering — it confuses people too easily. It is also, unfortunately, a continuing problem the C++ Committee struggles with, as no matter how carefully they try to word things there are always people who will be confused, and the more lawyering, the more confusion... Such is the nature of language. Foo. – Dúthomhas Sep 15 '21 at 22:14
  • Alright, so do you think that the only point of putting the item ‘Otherwise, no initialization is performed.’ in the default initialization list is so that it is still considered as a form of initialization (even though no *actual* initialization is performed) so that the object’s lifetime can begin? Otherwise the object `i` would have technically no existence in my code. – Géry Ogam Sep 15 '21 at 22:55
  • 2
    @Maggyero The whole thing with zero initialization of static object storage is just legalese to say just that. Objects with static storage duration are required to be stored in memory that is zero initialized. So, if you try to use an object that wasn't initialized "normally" but that could legally be initialized by `memset`ing its representation to zero, than you can legally go ahead and use it as if that had happened. The wording is to circumvent other rules, like the object wouldn't technically be within its lifetime yet or it would otherwise have an indeterminate state. – François Andrieux Sep 15 '21 at 23:05
1

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.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • Thanks. Could you quote the standard supporting that no initialization happens? – Géry Ogam Sep 16 '21 at 06:31
  • 1
    @Maggyero Answer updated to quote the standard. Not that there is much to quote; it's more applying information to this particular situation. I thought most of this was obvious, but I admit that "obvious" is a dangerous word to toss around. – JaMiT Sep 16 '21 at 15:39