Topic: is std::copy on 3 nulls valid?


Author: Bjarke Hammersholt Roune <bjarke.roune@gmail.com>
Date: Mon, 14 Feb 2011 04:32:51 CST
Raw View
MS VC++ 2010 is giving me assertion failures in debug mode when I run
code like:

 int* p = 0;
 std::copy (p, p, p)

It complains about there being a null pointer. I think that this is
fine - it does not matter that the address can't be written to since
the range is empty so I'm not asking for the range to be written to.

Is the code snippet above a valid use of std::copy?

Cheers
Bjarke Hammersholt Roune
www.broune.com


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Tue, 15 Feb 2011 01:16:38 CST
Raw View
On 2011-02-14 11:32, Bjarke Hammersholt Roune wrote:
>
> MS VC++ 2010 is giving me assertion failures in debug mode when I run
> code like:
>
>  int* p = 0;
>  std::copy (p, p, p)
>
> It complains about there being a null pointer. I think that this is
> fine - it does not matter that the address can't be written to since
> the range is empty so I'm not asking for the range to be written to.
>
> Is the code snippet above a valid use of std::copy?

The current wording is not clear about this, but a conservative
reading of [iterator.requirements.general] p. 5 would cause above code
to result in undefined behaviour, because one might associate a null
pointer to be a singular pointer. IMO this conservative interpretation
has never been intended. Issue

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html

is concerned about this, but probably won't be considered for C++0x.

HTH & Greetings from Bremen,

Daniel Kr   gler


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Francis Glassborow <francis.glassborow@btinternet.com>
Date: Tue, 15 Feb 2011 01:17:09 CST
Raw View
On 14/02/2011 10:32, Bjarke Hammersholt Roune wrote:
>
> MS VC++ 2010 is giving me assertion failures in debug mode when I run
> code like:
>
>  int* p = 0;
>  std::copy (p, p, p)
>
> It complains about there being a null pointer. I think that this is
> fine - it does not matter that the address can't be written to since
> the range is empty so I'm not asking for the range to be written to.
>
> Is the code snippet above a valid use of std::copy?
>
> Cheers
> Bjarke Hammersholt Roune
> www.broune.com
>


I think there are several things here. It is difficult to tell from
the Standard when you start trying to do arithmetic with null pointers
but I think copy assumes that there is at least one element to copy.
However that does not really matter. Do you really want to copy to a
destination that does not exist? Do you really want to copy from a
destination that does not exist? Because that is the effective meaning
of a null pointer (nowhere in the data space)

I note that the failure is specifically in the debug mode. Obviously
in debug mode any good implementation is going to check the
destination and an assertion seems a good way to do it. Note that your
code is only 'safe' in the one case that all three pointers are null,
if only one or two of them were your code would be a disaster waiting
to happen. I have no problem with an implementation that does not
check that I have written a bizarre do nothing expression.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Balog Pal" <pasa@lib.hu>
Date: Wed, 16 Feb 2011 10:16:45 CST
Raw View
"Daniel Kr   gler" <daniel.kruegler@googlemail.com>
>> MS VC++ 2010 is giving me assertion failures in debug mode when I run
>> code like:
>>
>>  int* p = 0;
>>  std::copy (p, p, p)
>>
>> It complains about there being a null pointer. I think that this is
>> fine - it does not matter that the address can't be written to since
>> the range is empty so I'm not asking for the range to be written to.
>>
>> Is the code snippet above a valid use of std::copy?
>
> The current wording is not clear about this, but a conservative
> reading of [iterator.requirements.general] p. 5 would cause above code
> to result in undefined behaviour, because one might associate a null
> pointer to be a singular pointer.

I read N3225 and can't conclude that the null pointer would be singular
allowing UB. However a range (i,i) is said to be empty what is the case for
the mentioned input. The null pointer is valid to pass around. So I'd rule
this as a valid call to std::copy, that shall result in no-op.  With library
implementator hat on, it is a hellish issue, as diagnostics shall be put
only after the range check or suppressed afterwards.

> IMO this conservative interpretation
> has never been intended. Issue
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html
>
> is concerned about this, but probably won't be considered for C++0x.

It is very very said.  The issue (#1213) is well spelled, and poins out a
serious defect in the upcoming standard.   As I see it the real value of
having a standard is that it defines terms and can be used to deduce
behavior.  Here it just uses term "singular" without an attempt to define it
first -- making all the referring text moot.

And the issue is marked as 'deferred' and IUC having a verdict from Batavia
"Doesn't need to be resolved for Ox".

I strongly disagree with that.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Bjarke Hammersholt Roune <bjarke.roune@gmail.com>
Date: Wed, 16 Feb 2011 10:16:56 CST
Raw View
> I think there are several things here. It is difficult to tell from
> the Standard when you start trying to do arithmetic with null pointers
> but I think copy assumes that there is at least one element to copy.
>
That would be insane. Then even this code would be invalid:

int* p = new int[4];
std::copy (p, p, p)

> However that does not really matter. Do you really want to copy to a
> destination that does not exist? Do you really want to copy from a
> destination that does not exist? Because that is the effective meaning
> of a null pointer (nowhere in the data space)
>
I absolutely don't want to copy from or to null. I'm OK with copying
everything in an empty range, and I'm OK with specifying an empty
range using nulls.

> I note that the failure is specifically in the debug mode. Obviously
> in debug mode any good implementation is going to check the
> destination and an assertion seems a good way to do it. Note that your
> code is only 'safe' in the one case that all three pointers are null,
> if only one or two of them were your code would be a disaster waiting
> to happen. I have no problem with an implementation that does not
> check that I have written a bizarre do nothing expression.
>
The destination could be non-null with no problem as long as the range
is empty. Obviously I don't have the 2 exact lines

  int* p = 0;
  std::copy (p, p, p);

in my code. What I have is a container much like vector that uses a
begin and an end pointer. In the case that the container has never
needed to allocate memory (it's size has always been zero) both
pointers are null. When I copy the contents of such a container I have
code like

  ensureCapacityOfDestination(dest, size);
  copy(begin, end, dest);

here begin, end and p could be null if I'm copying a container that
has always been empty to another container that has always been empty.
I think that's completely straight forward and it will work fine with
the obvious implementation of copy as:

   while (begin != end)
     *dest++ = *begin++

Since the MS implementation blows up on this code I went here to
figure out if MS had a bug in their assert or if the standard was
crazy.

Cheers
Bjarke


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Howard Hinnant <howard.hinnant@gmail.com>
Date: Wed, 16 Feb 2011 10:14:49 CST
Raw View
On Feb 15, 2:16 am, Daniel Kr   gler <daniel.krueg...@googlemail.com>
wrote:
> On 2011-02-14 11:32, Bjarke Hammersholt Roune wrote:
>
>
>
> > MS VC++ 2010 is giving me assertion failures in debug mode when I run
> > code like:
>
> >  int* p = 0;
> >  std::copy (p, p, p)
>
> > It complains about there being a null pointer. I think that this is
> > fine - it does not matter that the address can't be written to since
> > the range is empty so I'm not asking for the range to be written to.
>
> > Is the code snippet above a valid use of std::copy?
>
> The current wording is not clear about this, but a conservative
> reading of [iterator.requirements.general] p. 5 would cause above code
> to result in undefined behaviour, because one might associate a null
> pointer to be a singular pointer. IMO this conservative interpretation
> has never been intended. Issue
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html
>
> is concerned about this, but probably won't be considered for C++0x.

To me this looks more like LWG 1205:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#1205

And I feel the OP's code is valid.  Any reasonable implementation of
copy should do exactly what [alg.copy] specifies when given 3 null
pointers to a CopyAssignable type:

Copies elements in the range [first,last) into the range
[result,result + (last - first)) starting from first and proceeding to
last. For each non-negative integer n < (last - first), performs
*(result + n) = *(first + n).

In this corner case [first, last) is empty and thus the Effects clause
is a no-op.  Furthermore the Returns clause is well specified in this
case too.

This looks like nothing more than a bug in the debug-checking of this
implementation. Iterators into empty ranges should never be
dereferenced, and thus need not be dereferencable, whether or not that
iterator is a pointer or class.  I see no difference whatsoever
between the OP's code and this code, which I claim should also be
valid:

std::forward_list<int> f1, f2;
std::copy(f1.begin(), f1.end(), f2.begin());

-Howard


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Balog Pal" <pasa@lib.hu>
Date: Wed, 16 Feb 2011 10:17:04 CST
Raw View

"Francis Glassborow" <francis.glassborow@btinternet.com>

> I think there are several things here. It is difficult to tell from
> the Standard when you start trying to do arithmetic with null pointers
> but I think copy assumes that there is at least one element to copy.

As I read it, copy gets an iterator range as input, and shall act on that
range instead of assume anything.  If the range is empty, it shall copy no
elements. Semantics state exactly N assignments are allowed using the output
iter, that is zero for the empty range.

I see just no ground on assuming at least one element to copy.

IMO the question can be reduced to a simple one: ruling whether (0,0) is a
valid empty range or not.  It can just be spelled out -- stating yes or no,
and closing off way to much confusion.

> However that does not really matter. Do you really want to copy to a
> destination that does not exist?

Do I want to copy 0 elements to a non-existing destination?  Yes, I do.
I actually recall to hit similar issues in real life code -- processing data
"structures" that  pass element count then that many elements -- where 0
length is natural, and not a special case.   and in most operation should
NOT have extra checks for zero!

> Do you really want to copy from a
> destination that does not exist? Because that is the effective meaning
> of a null pointer (nowhere in the data space)

Those cases turn up, the important rules are limited to the natural ones:
- input range must be valid  ( j == i or j reachable from i by ++)
- output iterator dereferencable N times suggested from the input

Isn't copy supposed to be like

while (i != end) *out++ = *i++;

that works fine having a combination of any (valid) empty input range and a
(also valid) non-dereferenceable out.  And null pointer does not look
invalid in either.  Why should I make up some other pointer?


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Thu, 17 Feb 2011 19:10:10 CST
Raw View
[comp.std.c++ again does not react, therefore posted a second time]

Am 16.02.2011 17:14, schrieb Howard Hinnant:
>
> On Feb 15, 2:16 am, Daniel Kr   gler<daniel.krueg...@googlemail.com>
> wrote:
>> On 2011-02-14 11:32, Bjarke Hammersholt Roune wrote:
>>
>>
>>
>>> MS VC++ 2010 is giving me assertion failures in debug mode when I run
>>> code like:
>>
>>>   int* p = 0;
>>>   std::copy (p, p, p)
>>
>>> It complains about there being a null pointer. I think that this is
>>> fine - it does not matter that the address can't be written to since
>>> the range is empty so I'm not asking for the range to be written to.
>>
>>> Is the code snippet above a valid use of std::copy?
>>
>> The current wording is not clear about this, but a conservative
>> reading of [iterator.requirements.general] p. 5 would cause above code
>> to result in undefined behaviour, because one might associate a null
>> pointer to be a singular pointer. IMO this conservative interpretation
>> has never been intended. Issue
>>
>> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html
>>
>> is concerned about this, but probably won't be considered for C++0x.
>
> To me this looks more like LWG 1205:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#1205

I believe the OP problem is not the empty range, even though the
revised wording of 1205 has made the algorithms now very much clearer
in this regard.

> And I feel the OP's code is valid.

Well yes, I agree that the code *should* be valid, but the standard -
and in particular the definition of valid and singular iterators -
unfortunately can be read otherwise, see below.

> Any reasonable implementation of
> copy should do exactly what [alg.copy] specifies when given 3 null
> pointers to a CopyAssignable type:
>
> Copies elements in the range [first,last) into the range
> [result,result + (last - first)) starting from first and proceeding to
> last. For each non-negative integer n<  (last - first), performs
> *(result + n) = *(first + n).
>
> In this corner case [first, last) is empty and thus the Effects clause
> is a no-op.  Furthermore the Returns clause is well specified in this
> case too.

I agree with your analysis that the wording for std::copy itself is
fine, but the requirements for the iterator values may not be
satisfied according to [iterator.requirements.general] p. 5. Please
see at the very end of this lengthy text ;-)

> This looks like nothing more than a bug in the debug-checking of this
> implementation. Iterators into empty ranges should never be
> dereferenced, and thus need not be dereferencable, whether or not that
> iterator is a pointer or class.  I see no difference whatsoever
> between the OP's code and this code, which I claim should also be
> valid:
>
> std::forward_list<int>  f1, f2;
> std::copy(f1.begin(), f1.end(), f2.begin());

>From the point of the user of the library, [f1.begin(), f1.end())
defines an empty range of two *valid* iterator values. The wording of
23.2.1 p. 6,

"begin() returns an iterator referring to the first element in the
container. end() returns an iterator which is the past-the-end value
for the container. If the container is empty, then begin() == end();"

does not say this *exactly* that both return values are valid
iterators, but IMO leaves not much freedom of interpretation. The
problem in the OP example is the usage of the null pointer value for
iterator values. Question is: Are these singular iterator?

Interestingly the rationale for the wording provided by

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#208

*was* the assumption that a null pointer value is a singular iterator.

I strongly object to that interpretation and think I'm backed by the
historical intent reflected in

http://www.sgi.com/tech/stl/trivial.html

that a null pointer value is *not* a singular iterator. Now with the
addition of the 208 P/R the standard has now slightly agreed that null
pointer values are singular iterators and that basically has the
effect that not much operations are possible and a feasible
implementation - like that used by the OP - can *legally* diagnose the
incorrect usage of a singular iterator. It is incorrect, because
according to 24.2.1 p. 6 we can for sure only do the following
operations with a singular iterator:

- We can destroy this value
- We can use it as the target of an assignment with a non-singular value.
- We can use this null pointer value as a source of an assignment,
because a pointer is DefaultConstructible.

The last bullet is a new one as a result from accepting

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3066.html

during the Pittsburgh meeting.

There is *no* guarantee that we can *compare* two value-initialized
iterator with each other - IMO such a requirement is not needed in a
general case and this state of the standard is OK. I know
(non-RandomAcess) iterator implementations where the
default-constructed value represents a quite-NaN-like state. In these
cases the past-the-end value is provided by other means, like using
the syntax:

MyIterator end(past_the_end);

where past_the_end is some typical tag-object similar to std::ignore,
std::piecewise_construct, etc.

In fact, the 208 wording as *un*clarified the OP's example, because we
could otherwise easily argue that the two null pointer values define a
feasible empty range produced from two past-the-end iterator values.

IMO the decision for accepting 208 was wrong.

- Daniel


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Howard Hinnant <howard.hinnant@gmail.com>
Date: Sat, 19 Feb 2011 08:49:32 CST
Raw View
> IMO the decision for accepting 208 was wrong.

If anyone can fix this for us, I think it is you. :-)  Sounds like a
new issue to me.

More ammo for this issue:  vector<int>::iterator is allowed to be a
pointer, and returning a null pointer from vector<int>::begin() when
capacity() == 0 should be a perfectly valid implementation.

-Howard


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Martin B." <0xCDCDCDCD@gmx.at>
Date: Sat, 19 Feb 2011 08:48:26 CST
Raw View
On 18.02.2011 02:10, Daniel Kr   gler wrote:
>
> [comp.std.c++ again does not react, therefore posted a second time]
>
> (... ...)
>
> Interestingly the rationale for the wording provided by
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#208
>
> *was* the assumption that a null pointer value is a singular iterator.
>
> I strongly object to that interpretation and think I'm backed by the
> historical intent reflected in
>
> http://www.sgi.com/tech/stl/trivial.html
>
> that a null pointer value is *not* a singular iterator. Now with the
> addition of the 208 P/R the standard has now slightly agreed that null
> pointer values are singular iterators (...)
> (...)
> In fact, the 208 wording [h]as *un*clarified the OP's example, because we
> could otherwise easily argue that the two null pointer values define a
> feasible empty range produced from two past-the-end iterator values.
>
> IMO the decision for accepting 208 was wrong.
>

Note: It does appear the standard does not directly define the term
'singular', although it is used in the context of iterators and
pointers.

I do agree however, that a NULL pointer isn't singular, as a NULL
pointer *can* be compared to another pointer and it appears that
singular values cannot be meaningfully compared.

Therefore #208 would *also* be wrong in its assertion
   ... If the past-the-end value
   on such a container was a
   well-known singular value ...

I think a singular value cannot be well-known, as then it no longer is
a singular value.


The solution to this mess would obviously be to properly define what
the std means when referring to singular pointers or iterators.

cheers,
Martin


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: usenet@mkarcher.dialup.fu-berlin.de (Michael Karcher)
Date: Sat, 19 Feb 2011 08:49:08 CST
Raw View
Daniel Kr   gler <daniel.kruegler@googlemail.com> wrote:
> Interestingly the rationale for the wording provided by
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#208
>
> *was* the assumption that a null pointer value is a singular iterator.
[...]
> IMO the decision for accepting 208 was wrong.

The problem seems to be caused by "Iterators can also have singular
values that are not associated with any container." (also in 24.1/5)
It seems like people infer that this sentence means that any iterator
value that is not associated with any container is singular. The NULL
pointer definitiely is not associated with any container, so according
to that interpretation, the NULL pointer must be singular.

On the other hand, there are iterators that are derefenceable but not
associated with any container. Think of istream_iterator or
ostream_iterator. While there is no container associated to an
ostream_iterator, ostream_iterator values are non-singular and
even dereferenceable.

A final note is that C++98 already contains iterators with the "past-the-end
is equal for iterators over difference sequences" property, namely the
istream_iterator again. While they are not associated to "containers" (as
mentioned in the paragraph above), they are associated to different logical
sequences if they are created from different stream objects referencing
different files. Still their past-the-end iterator is equal.

Regards,
 Michael Karcher


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]