FYI -- ongoing discussion related to Debug Visual Studio builds and
nullptr checks (or lack thereof)


---------- Forwarded message ----------
From: David Cole <dc...@neocisinc.com>
Date: Mon, Oct 26, 2015 at 11:24 AM
Subject: Re: Change in ITK[master]: BUG: Make valid calling with
m_NumElements == 0
To: Luc Hermitte <luc.hermi...@c-s.fr>
Cc: "Johnson, Hans J" <hans-john...@uiowa.edu>, David Cole <dlrd...@aol.com>


(cc-ing myself with my ITK dev mailing list email so I can loop in the
ITK dev list so others may participate in the discussion if they feel
so inclined)


I think you must mean:
std::fill(m_Data, m_NumElements, val)

...because if you really do mean m_Data->m_NumElements, you still have
a problem when m_Data is nullptr.

Same with:
std::copy(&v.m_Data[0], &v.m_Data[N], &this->m_Data[0]);

If m_Data can possibly be nullptr in either "v" or "this" then I do
not see how this is a useful call to std::copy without checking for
nullptr. If m_Data is nullptr, m_Data[0] and m_Data[N] are meaningless
expressions.

I would really really really like to see some evidence from you that
adding nullptr checks is materially harmful to performance in some
real world fill/copy scenario before agreeing to any patch which does
not include nullptr checks.


Thanks,
David C.



On Mon, Oct 26, 2015 at 10:48 AM, Luc Hermitte <luc.hermi...@c-s.fr> wrote:
>
> After further investigations.
>
> VC++'s fill() requires:
> - the output range to be valid (i.e., in our case, if first!=last, first
> and last must be non null pointers, and first must be < last)
>
> VC++'s fill_n() requires
> - the output iterator to be valid, i.e. not null in case of a pointer
>
> This means that by using std::fill(m_Data, m_Data->m_NumElements, val)
> we should fix the bug observed.
>
> I've embedded the fix in my latest patch set
> (http://review.source.kitware.com/#/c/20253/)
>
>
> Regarding std::copy().
> VC++'s copy() requires :
> - the input range to be valid (if first!=last, first and last shall not
> be null pointers, and first shall be < last)
> - the output iterator to be valid (!=0 in case of a pointer)
>
> There is a still bug with VC++ copy() implementation in the following
> use case:
>   VariableLengthVector v1, v2;
>   v1 = v2; // should fail as well with VC++
>
>
> Indeed, first SetSize(0, DontShrinkToFit(), DumpOldValue()) is called
> -> no copy is done, but this->m_Data is left null.
> Then, std::copy(&v.m_Data[0], &v.m_Data[N], &this->m_Data[0]);
> Unfortunately, &this->m_Data[0] is left unchanged and null, and VC++
> will complain :(
>
>
> This means, either :
> - we provide an itk::copy() that does not require outIt to be non null
> when the input range is empty
> - or we write the loop manually
> - or we add another test in operator=(Self) -- thing that'll really like
> to avoid
>
> Finally, note as well odd situations in the current implementation -- my
> fault I guess.
> A newly default-constructed VLV will have a null m_Data pointer.
> However, a VLV constructed with VariableLengthVector<type> v(0) will
> have a non null m_Data pointer.
>
> Regards
> --Luc Hermitte
>
> Le 26/10/2015 09:03, Luc Hermitte a écrit :
> > Hi,
> >
> > Le 25/10/2015 18:37, Johnson, Hans J a écrit :
> >> Should we simply add documentation that states:  "Behavior is undefined 
> >> when length of vector is zero”, add an assertion when compiled in debug 
> >> mode, and then remove the degenerate case in the test suite?
> >
> >
> > This could be enough regarding Fill(). I'd say it's up to you. IMO, it
> > could be simply fixed with an loop or an alternative of fill_n that
> > states as precondition that "The range [outIt, outIt+n) shall be valid ;
> > and n shall be >= 0", instead of "The iterator outIt shall be valid ;
> > and n >= 0".
> > This way the end-user won't have to check he doesn't call Fill() on
> > empty VariableLengthVectors -- it would be considered as a no-op exactly
> > as "delete nullptr".
> >
> > However, we need to be sure that the copy constructor of empty
> > VariableLengthVector has no side effect.
> >
> > Regards,
> > --Luc Hermitte
> >
> >>
> >>
> >>
> >>
> >>
> >> On 10/25/15, 8:49 AM, "Luc Hermitte (Code Review)" <rev...@kitware.com> 
> >> wrote:
> >>
> >>> Luc Hermitte has posted comments on this change.
> >>>
> >>> Change subject: BUG: Make valid calling with m_NumElements == 0
> >>> ......................................................................
> >>>
> >>>
> >>> Patch Set 2:
> >>>
> >>>> You cannot call std::fill_n with a nullptr for Debug VS 2013.
> >>>>
> >>>> The implementation of fill_n looks like this:
> >>>>
> >>>> template<class _OutIt,
> >>>> class _Diff,
> >>>> class _Ty> inline
> >>>> _OutIt fill_n(_OutIt _Dest, _Diff _Count, const _Ty& _Val)
> >>>> {  // copy _Val _Count times through [_Dest, ...)
> >>>> _DEBUG_POINTER(_Dest);
> >>>> return (_Fill_n(_Dest, _Count, _Val,
> >>>> _Is_checked(_Dest)));
> >>>> }
> >>>>
> >>>> The _DEBUG_POINTER line is called unconditionally, and will throw
> >>>> this assert if the pointer is nullptr.
> >>>
> >>> That's odd. I don't read such a precondition in the standard.
> >>> Is it the same with std::copy()?
> >>>
> >>>
> >>>> Why are you against doing a nullptr check or a check on the number
> >>>> of elements prior to calling fill_n?
> >>>>
> >>>> If you are calling fill_n so frequently that you're concerned about
> >>>> adding a micro-smidge of time to each call, perhaps you're calling
> >>>> it too frequently...
> >>>
> >>> I totally agree that such algorithms are often called to many times. and 
> >>> yet, when they are, they are called on pixels. This means they are 
> >>> sometimes called billion times. We don't need to add a test that we could 
> >>> easily avoid and still stay robust.
> >>>
> >>> What we need is a fill_n implementation that have as a precondition: 
> >>> "ptr!=nullptr || size==0", and not "ptr!=nullptr".
> >>>
> >>>
> >>>
> >>>> If you want the fastest code possible, without doing the
> >>>> appropriate nullptr safety checks, then you must guarantee that the
> >>>> entire test suite never tries to execute a test case with a
> >>>> nullptr. So another solution would be simply NOT to execute tests
> >>>> that result in these calls for Debug Microsoft builds.
> >>>
> >>> We simply need algorithms with less restrictive preconditions.
> >>> For instance
> >>>
> >>>    // untested code
> >>>    template <typename OutIt, typename Size, typename T>
> >>>    OutIt itk::fill_n(OutIt start, Size size, T const& value) {
> >>>        assert(start ||size == 0); // the test is not correct, just to 
> >>> give an idea of what it should look like
> >>>        for ( ; size != 0 ; --size, ++start)
> >>>            *start = value;
> >>>        return start;
> >>>    }
> >>>
> >>>
> >>>> It is my contention that **if** there is a performance problem with
> >>>> code after simply adding "if (this->m_Data)" checks before fill and
> >>>> copy calls, that the performance problem is with the **callers** of
> >>>> these things.
> >>>
> >>> Somehow I agree. Unfortunately, we tend to call these functions too many 
> >>> times, i.e. once per pixel if not more. We have a lot of pixels.
> >>>
> >>> Here it's easy to have a correct code (that'll pass tests with VC++ in 
> >>> Debug Mode) without this redundant branching. Why pay for this branching?
> >>>
> >>>
> >>>> Fill and copy effectively perform N assignment operations. If you
> >>>> cannot afford a single nullptr check when you are doing N
> >>>> assignment operations then you should write special case code to
> >>>> avoid the safety checks.
> >>>
> >>> N is often small (< 16)
> >>>
> >>>> The rest of us would like to be alerted ASAP when we accidentally
> >>>> try to "fill" or "copy into" an empty container.
> >>>
> >>> We don't need to here.
> >>> However, if you consider that filling or copying an empty vector is a 
> >>> programming error, then we should document it and formalize the related 
> >>> precondition (assertion, or C++17 [[expect: size != 0]]).
> >>>
> >>> --
> >>> To view, visit http://review.source.kitware.com/20307
> >>>
> >>>
> >>> To unsubscribe, visit http://review.source.kitware.com/settings
> >>>
> >>> Gerrit-MessageType: comment
> >>> Gerrit-Change-Id: I6b9ba9d6ddf05450485a88ffbff4f14568205f26
> >>> Gerrit-PatchSet: 2
> >>> Gerrit-Project: ITK
> >>> Gerrit-Branch: master
> >>> Gerrit-Owner: Hans J. Johnson <hans-john...@uiowa.edu>
> >>> Gerrit-Reviewer: David Cole <dc...@neocisinc.com>
> >>> Gerrit-Reviewer: Hans J. Johnson <hans-john...@uiowa.edu>
> >>> Gerrit-Reviewer: Kitware Build Robot <kwbuildro...@yahoo.com>
> >>> Gerrit-Reviewer: Luc Hermitte <luc.hermi...@c-s.fr>
> >>> Gerrit-HasComments: No
> >>
> >>
> >> ________________________________
> >> Notice: This UI Health Care e-mail (including attachments) is covered by 
> >> the Electronic Communications Privacy Act, 18 U.S.C. 2510-2521, is 
> >> confidential and may be legally privileged.  If you are not the intended 
> >> recipient, you are hereby notified that any retention, dissemination, 
> >> distribution, or copying of this communication is strictly prohibited.  
> >> Please reply to the sender that you have received the message in error, 
> >> then delete it.  Thank you.
> >> ________________________________
> >>
> >
>
_______________________________________________
Powered by www.kitware.com

Visit other Kitware open-source projects at
http://www.kitware.com/opensource/opensource.html

Kitware offers ITK Training Courses, for more information visit:
http://kitware.com/products/protraining.php

Please keep messages on-topic and check the ITK FAQ at:
http://www.itk.org/Wiki/ITK_FAQ

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/insight-developers

Reply via email to