C++ Logo

sg12

Advanced search

Re: [ub] Type punning to avoid copying

From: Gabriel Dos Reis <gdr_at_[hidden]>
Date: Mon, 29 Jul 2013 01:30:54 -0500
Richard Smith <richardsmith_at_[hidden]> writes:

| On Sun, Jul 28, 2013 at 10:42 AM, Nevin Liber <nevin_at_[hidden]>
| wrote:
|
| On 28 July 2013 11:44, Gabriel Dos Reis <gdr_at_[hidden]> wrote:
|
|
|
|
| We shouldn't be doing anything in rash.
|
|
|
| Who is arguing for a rash decision? This sounds like a straw man
| to me.
|
|
| As far as I can tell, we are exploring how to meet this very real
| need of programs (efficiently turning a buffer of raw data into
| something which has structure), which is currently being met
| (either accidentally or deliberately) by relying on certain
| undefined behavior not being undefined in any practical sense.
|
|
| FWIW, I don't agree that such code necessarily has undefined behavior.

In this discussion, there are several sources of undefined behavior.

The general operation model is that unless you find rules that describe
the behavior of the program, it is undefined.


| Consider this ugly code, which is the kind of thing people have been
| writing for years:
|
| ALIGNED(16) char buffer[BUFFER_SIZE];
| size_t buffer_pos, buffer_read;
|
| T *get_from_network() {
| if (buffer_pos + sizeof(T) < buffer_read) read_more_into_buffer
| (sizeof(T));
| assert(is_suitably_aligned_for<T>(buffer+buffer_pos));
| return (T*)buffer[buffer_pos];

I cannot see how we want to make sense of this as defining an object of
type T. The expression

    buffer[buffer_pos]

is of type "char", not a pointer to "char". I suspect you meant
something like

   (T*)(buffer + buffer_pos);

but even that isn't using the putative object in the storage "buffer" in
any way. Since casting between pointers to data in any arbitrary order
is allowed, we can't take the point of cast itself as defining the
beginning of the object's lifetime. Even C tries to be more careful.

| }
|
| Now, [basic.life]p1 says that, unless the object has non-trivial
| initialization, its lifetime begins "when storage with the proper
| alignment and size for type T is obtained". The wording here is
| circular, because we don't know whether an object is being initialized
| until we know whether its lifetime begins, and vice versa, but it can
| be argued that the lifetime of a T object began *before* the data was
| copied into the buffer, because storage with suitable size and
| alignment was obtained before that point.

I don't see the circularity.

|
| More generally, my view of how the lifetime rules in [basic.life]p1
| are intended to work is:
| * If there exists a set of times when objects' lifetimes begin and
| end, and that set gives the program defined behavior, then the program
| has defined behavior
| * Otherwise, the program has undefined behavior

That view may be a novel interpretation of the standards, but I can't
see the paragraph you quote supporting that, nor any other disposition,
nor does it correspond to any existing interpretation I am aware of.

In fact, it is not at clear that either programmers or compilers want
a notion of 'object resuscitation'.


| (In effect, the programmer chooses when lifetimes begin and end, and
| does not need to write this intent in the source code.) Different
| choices of lifetime beginning/end can only change whether the program
| has defined behavior, and cannot imbue it with two different defined
| behaviors, so this approach seems to be coherent, and (I think)
| captures what people expect.
|
| (The [basic.life]p1 rules are also somewhat defective, because
| obtaining storage of suitable size and alignment for an object of
| class type without a trivial default constructor should not start the
| lifetime of such an object, but if the object is not initialized, the
| relevant rule does not apply.)

-- Gaby

Received on 2013-07-29 08:31:13