Date: Mon, 29 Jul 2013 10:43:14 -0700
On Sun, Jul 28, 2013 at 11:30 PM, Gabriel Dos Reis <gdr_at_[hidden]> wrote:
> 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.
Absolutely; I was trying to demonstrate the rules which I claim can be
interpreted as giving this example defined behavior in the rest of my
message.
Identifying differences of opinion on the meaning of the standard seems
like an important activity here: they create a grey area between
programmers and implementors, where there is disagreement on the
definedness of programs. Both sides tend to wander into this grey area, so
exploring it and clarifying the standard seems like an important function
of this SG (indeed, I think this should be our primary goal).
> | 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 suspect you meant something like
>
> (T*)(buffer + buffer_pos);
>
Yes, I did, thanks.
> 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.
That was not what I was suggesting: my claim was that the lifetime of the
object started *before* the data was copied into it. [basic.life]p1
explicitly permits allocating an appropriately-sized-and-aligned buffer of
characters and using it to store an object (without calling a constructor
or using placement new).
| }
> |
> | 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.
>
The standard says that the lifetime of an object begins "when storage with
the proper alignment and size for type T is obtained". It says little about
what constitutes obtaining storage ([expr.new] implies that calling an
allocation function qualifies, and [basic.align]p8 suggests non-normatively
that an object of type std::aligned_storage<T>::type qualifies). If a
program takes an existing buffer and selects a subsequence of characters
with the proper size and alignment (for instance, using std::align), that
seems to qualify, and in practice it is very widely assumed that this does
qualify as obtaining such storage.
Since the programmer is not required to make an explicit statement of the
intent to start the lifetime of an object, the compiler cannot declare a
program to have undefined behavior if such a statement of intent would give
it defined behavior. Therefore we can conclude that the *existence* of such
intent is sufficient to force the implementation to provide the desired
behavior.
In fact, it is not at clear that either programmers or compilers want
> a notion of 'object resuscitation'.
I don't know what you mean by 'resuscitation' here. Can you elaborate?
> 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.
Absolutely; I was trying to demonstrate the rules which I claim can be
interpreted as giving this example defined behavior in the rest of my
message.
Identifying differences of opinion on the meaning of the standard seems
like an important activity here: they create a grey area between
programmers and implementors, where there is disagreement on the
definedness of programs. Both sides tend to wander into this grey area, so
exploring it and clarifying the standard seems like an important function
of this SG (indeed, I think this should be our primary goal).
> | 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 suspect you meant something like
>
> (T*)(buffer + buffer_pos);
>
Yes, I did, thanks.
> 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.
That was not what I was suggesting: my claim was that the lifetime of the
object started *before* the data was copied into it. [basic.life]p1
explicitly permits allocating an appropriately-sized-and-aligned buffer of
characters and using it to store an object (without calling a constructor
or using placement new).
| }
> |
> | 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.
>
The standard says that the lifetime of an object begins "when storage with
the proper alignment and size for type T is obtained". It says little about
what constitutes obtaining storage ([expr.new] implies that calling an
allocation function qualifies, and [basic.align]p8 suggests non-normatively
that an object of type std::aligned_storage<T>::type qualifies). If a
program takes an existing buffer and selects a subsequence of characters
with the proper size and alignment (for instance, using std::align), that
seems to qualify, and in practice it is very widely assumed that this does
qualify as obtaining such storage.
Since the programmer is not required to make an explicit statement of the
intent to start the lifetime of an object, the compiler cannot declare a
program to have undefined behavior if such a statement of intent would give
it defined behavior. Therefore we can conclude that the *existence* of such
intent is sufficient to force the implementation to provide the desired
behavior.
In fact, it is not at clear that either programmers or compilers want
> a notion of 'object resuscitation'.
I don't know what you mean by 'resuscitation' here. Can you elaborate?
Received on 2013-07-29 19:43:16