15

I'm writing a memory manager for my VM in C++. Well, more exactly the VM instructions will be compiled into C++ with an embedded memory manager. I'm much more comfortable in handling C, but now I do need native support of exception handling, pretty much the only reason I'm using C++.

Both C and C++ have the strict aliasing rule that two objects of incompatible types shall not overlap, with a small exception in C for unions. But to define the behaviour of memory allocation functions such as malloc, calloc, alloca etc., the C standard has the following paragraph.

6.5-6 The effective type of an object for an access to its stored value is the declared type of the object, if any. Allocated objects have no declared type. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. 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.

This effectively makes using raw allocated memory for any type a well defined behaviour in C. I tried to find a similar paragraph in the C++ standard document but could not find one. I think C++ has a different approach in this regard. What is the C++ equivalent of an 'allocated object having no declared type' in C, and how does the C++ standard define it?

  • 2
    You may find the discussion in [“constructing” a trivially-copyable object with memcpy](http://stackoverflow.com/q/30114397/1708801) ... although helpful is subjective since the conclusion is that a lot of this is unspecified in C++, the mailing list discussion at the end of my answer is a good read. Matt has asked similar question that covers this subject you may find it helpful to browse [some of his more recent question](http://stackoverflow.com/users/1505939/matt-mcnabb?tab=questions) – Shafik Yaghmour Jul 17 '15 at 19:19
  • 2
    The nearest equivalent in C++ is the rule that says that the lifetime of an object with trivial constructor begins when storage for the object is obtained. But it's a bit of a gray area. If you just allocate memory, does that begin the lifetime of objects of all trivial types that fit into the memory? Presumably not. – Kerrek SB Jul 17 '15 at 19:22
  • @KerrekSB "_If you just allocate memory, does that begin the lifetime of objects of all trivial types_" Yes. It seems natural. Why do you have a problem with this? – curiousguy Aug 16 '15 at 00:37

4 Answers4

3

For C++, this is is described in Object lifetime [object.life]; in particular:

The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

Lifetime continues until the storage is reused or the object is destructed:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor.

This has the rather odd implication that unused allocated storage (returned from operator new) contains an object of every trivial type that fits in that block of storage, at least until the block of storage is used. But then, the standard cares more about getting the treatment of non-trivial types right than this minor wart.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
1

I'm not sure such an analog exists, or is needed in C++. To allocate memory you can do one of three things

Foo f;

This will allocate sizeof(Foo) amount of memory on the stack. The size is known at compile-time, which is how the compiler knows how much space to allocate. The same is true of arrays, etc.

The other option is

Foo* f = new Foo;  // or the smart pointer alternatives

This will allocate from the heap, but again sizeof(Foo) must be known, but this allows allocation at run-time.

The third option, as @BasileStarynkevitch mentions, is placement new

You can see that all of these allocation mechanisms in C++ require knowledge of the type that you are allocating space for.

While it is possible to use malloc, etc in C++, it is frowned upon as it goes against the grain for typical C++ semantics. You can see a discussion of a similar question. The other mechanisms of allocating "raw" memory are hacky. For example

void* p = operator new(size);

This will allocate size number of bytes.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
1

I think "an allocated object with no declared type" is effectively allocated storage that is not yet initialised and no object has yet been created in that memory space. From the global allocation function ::operator new(std::size_t) and family (§3.7.4/2);

§3.7.4.1/2

The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. There are no constraints on the contents of the allocated storage on return from the allocation function.

The creation of an object, whether it is automatic or dynamic, is governed by two stages, the allocation itself and then the construction of the object in that space.

§3.8/1

The lifetime of an object is a runtime property of the object. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [Note: initialization by a trivial copy/move constructor is non- vacuous initialization. — end note] The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

Correspondingly;

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.

C++ WD n4527.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
0

I think, the approach of C++ in this regard can be summarized as: "malloc() is evil. Use new instead. There is no such thing as an object without a declared type." Of course, C++ implementations need to define the global operator new(), which is basically the C++ version of malloc() (and which can be provided by the user). The very existence of this operator proves that there is something like objects without a declared type in C++, but the standard won't admit it.

If I were you, I'd take the pragmatic approach. Both the global operator new() and malloc() are available in C++, so any implementation must be able to use their return values sensibly. Especially malloc() will behave identical in C and C++. So, just handle these untyped objects the same way as you would handle them in C, and you should be fine.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • Objects created with new have no name and no declaration, so no declared type! – curiousguy Aug 16 '15 at 01:24
  • @curiousguy The objects returned by a `new` expression are fully constructed objects, and thus have both a static and a dynamic type in its full glory. Concluding from their lack of a name that they have no declaration and hence no declared type seems a lot like nitpicking to me. After all, the declaration of the type you want the `new` expression to have is a part of the expression. Apart from how it is destructed, there is absolutely no difference between the memory objects used in `Foo baz, *bar = &baz;` and `Foo* bar = new Foo();` – cmaster - reinstate monica Aug 16 '15 at 06:01
  • I may be nitpicking, but I don't think an object has a static type in C++ (unlike C). – curiousguy Aug 16 '15 at 13:57
  • @curiousguy I meant "static" type as in "static typechecking"; the object has a static type that can be checked at compile time, as opposed to the dynamic type that can only be checked at runtime. The result of the `new` expression has both a static and a dynamic type which are always the same. I did not mean the `static` storage class, which is a whole different story (and still available in C++). – cmaster - reinstate monica Aug 17 '15 at 05:51
  • Still nitpicking: the `new` expression certainly has a static type, but I don't think the object itself has one. – curiousguy Aug 17 '15 at 07:03
  • @curiousguy The type of the `new` expression is the static type, and the type of the object is the dynamic type. Remember, the object is fully constructed when it is returned by the `new` expression. That type is the basis for what accesses to that memory are henceforth considered legal and what are considered undefined behavior. `((Child*)new Parent())->childMethod();` is allowed to format your harddrive precisely because you are using an object in a way that is not in accordance with its type. – cmaster - reinstate monica Aug 17 '15 at 18:26