Wednesday, June 17, 2020

Digest for comp.lang.c++@googlegroups.com - 25 updates in 5 topics

Bonita Montero <Bonita.Montero@gmail.com>: Jun 17 07:11AM +0200

The cmpxchg has a membar itself.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jun 17 01:12PM -0700

On 6/16/2020 10:11 PM, Bonita Montero wrote:
> The cmpxchg has a membar itself.
 
you mean LOCK cmpxchg on x86, yes you are correct.
 
Have you ever programmed for the SPARC in RMO mode or PPC? What about a
DEC Alpha?
 
Please read all of this:
 
https://en.wikipedia.org/wiki/Memory_ordering
 
Can you name some algorithms that require explicit membars on an x86
when using atomic loads and stores? I know of several.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jun 17 01:34PM -0700

On 6/17/2020 1:12 PM, Chris M. Thomasson wrote:
 
> https://en.wikipedia.org/wiki/Memory_ordering
 
> Can you name some algorithms that require explicit membars on an x86
> when using atomic loads and stores? I know of several.
 
On x86 wrt atomic loads and stores...
 
Atomic Store to A release
Atomic Load from B acquire
 
Well.... The load from B can be hoisted up above the store to A on an
x86. This can occur because A and B are different locations.
 
The MFENCE instruction can take care of this, to ensure that the load
from B is performed _after_ the store to A.
 
Store to A release
MFENCE
Load from B acquire
 
Okay, now, it works. Btw, there are several algorithms that depend on
this. Using a LOCK RMW on x86 can do this as well. It has the right membar.
 
Iirc, there was a problem with a bugged processor. It was a long time
ago. Iirc it was part of the Plan9 problem. Iirc, it had something to do
with using an atomic store to release a spinlock. I first learned about
it way back on comp.programming.threads.
Bonita Montero <Bonita.Montero@gmail.com>: Jun 18 12:02AM +0200

> you mean LOCK cmpxchg on x86, yes you are correct.
 
I'm talking about the used intinsics of MSVC and gcc.
They have full barriers on all CPUs.
 
> Have you ever programmed for the SPARC in RMO mode or PPC?
> What about a DEC Alpha?
 
That are all dead CPUs.
Bonita Montero <Bonita.Montero@gmail.com>: Jun 18 12:03AM +0200

Sorry, but youre talkin stupid stuff. I'm using the
MSVC / gcc intinsics, and both have full barriers.
 
Look at this code:
 
long xchg;
 
void f( int &i, int &j, int &k )
{
k = i + j;
__sync_bool_compare_and_swap( &xchg, 0, 1 );
k = i + j;
}
 
This compiles to this with g++:
 
movl (%rsi), %eax
addl (%rdi), %eax
movl $1, %ecx
movl %eax, (%rdx)
xorl %eax, %eax
lock cmpxchgq %rcx, xchg(%rip)
movl (%rsi), %eax
addl (%rdi), %eax
movl %eax, (%rdx)
ret
 
So the compiler doesn't optimize away the duplicate calculation
because __sync_bool_compare_and_swap has acquire- as well as
release- behaviour - logically from the view of the instruction
-stream as well as related to the internal order of the loads
and stores of the CPU.
Lynn McGuire <lynnmcguire5@gmail.com>: Jun 17 01:52PM -0500

On 6/16/2020 2:21 PM, Bonita Montero wrote:
>     }
>     return this;
> }
 
Thanks, I need to review and think about this. There are actually over
ten base data types in my software.
 
Lynn
Lynn McGuire <lynnmcguire5@gmail.com>: Jun 17 01:57PM -0500

On 6/16/2020 3:18 PM, Alf P. Steinbach wrote:
> X/Y problem where you have a Real Issue X, you imagine that Y could be a
> good solution to X, you can't make Y work cleanly, and you ask about Y?
 
> - Alf
 
This is code that I have had in our product since 2003 that I am
revising at the moment and disturbed by our duplicated code in each of
the 10+ methods.
 
This code was actually written in Smalltalk back in 1990 or so. Two of
my programmers and I converted it to C++ in 2002-3 when we rebuilt the
product totally in Win32 C++. ObjPtr is our base class that mimics the
base object class in Smalltalk. Works very well for us in 450,000+
lines of C++ code.
 
I had not though about using a template. I had a bad experience with a
template a couple of decades ago in the Visual C++ compiler and stay
away from them now.
 
Thanks,
Lynn
Jorgen Grahn <grahn+nntp@snipabacken.se>: Jun 17 09:05PM

On Wed, 2020-06-17, Lynn McGuire wrote:
...
> I had not though about using a template. I had a bad experience with a
> template a couple of decades ago in the Visual C++ compiler and stay
> away from them now.
 
I can happily recommend templates; they are good for all kinds of tasks
in all kinds of programs.
 
I wish it was easier to learn how to apply them, though. For me it's
still trial and error: I can "smell" an opportunity to simplify code
with use of templates, but not know exactly how to proceed. Then
after some failed attempts, I may end up with a really clean solution.
 
/Jorgen
 
--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Jun 17 12:24AM +0100

On Tue, 16 Jun 2020 16:11:31 -0700
> So, basically, I need to use placement new and explicit dtor via
> calling the dtor ~Type(). Then it becomes a "real object" in C++. I
> thought that POD would be different in a sense.
 
Yes, although you don't actually need to call the destructor because
your types are trivial.
 
But there are other issues with your code which I have alluded to in
other posts. I should use 'new unsigned char[...]' instead of
std::malloc, placement new your 'header' struct into it and (as you do
at present) cast the buffer part to char* if you really want char*
instead of unsigned char* for the buffer. As I have indicated in those
posts I think that cast is valid but you never know with C++17/20: if
the committee don't understand the rules who are we to say.
 
As I have also mentioned in other posts I agree with your sentiments
about PODs/trivial types. That seems to me to be another fail in the
standard. It ought to be valid in my view, but it isn't.
Juha Nieminen <nospam@thanks.invalid>: Jun 17 06:36AM

> Technically, constructing the 'header' object in the malloc'ed buffer
> is also reputed to be undefined behaviour if you do it otherwise that
> through placement new, even though 'header' is a trivial type.
 
Thinking about it, it might actually have merit to worry about such things
being UB, no matter how "technically" and how obscure the rule may be.
 
One could easily just think like "who cares if it's "technically" UB?
There's no practical implementation where it would cause anything else
than intended behavior."
 
The problem is, UB allows the compiler to do whatever it wants. Including
not doing what the programmer "intended" for it to do. Tehcnically speaking
if the compiler detects UB, it's allowed to think "this is UB, I don't need
to do anything here, I'll just skip the whole thing and optimize it all
away". Suddenly you might find yourself with an incredibly obscure
"compiler bug" where the compiler isn't generating the code you wrote...
when in fact it's not a compiler bug at all.
 
I remember a particularly nasty bug many years ago in the Linux kernel that
was caused precisely by this kind of thing. That part of the kernel code
was technically UB... and the compiler did whatever it wanted and not the
thing that the code was "intending" it to do (and, IIRC, it optimized that
part away, which caused the intended thing to not happen. I don't remember
now the exact reason, but might have had something to do with deliberately
dererencing a null pointer, which is UB, and which the compiler was
"optimizing" away because the standard allowed it to.)
Juha Nieminen <nospam@thanks.invalid>: Jun 17 06:37AM

> Don't care for the spec. It works with any compiler and
> it will work with any compiler that will ever exist.
 
The problem is that if you trigger UB, the compiler is allowed to do
whatever it wants with it. Including not doing what you want.
Scott Newman <scott69@gmail.com>: Jun 17 08:42AM +0200

>> it will work with any compiler that will ever exist.
 
> The problem is that if you trigger UB, the compiler is allowed to
> do whatever it wants with it. Including not doing what you want.
 
Not in this case.
Juha Nieminen <nospam@thanks.invalid>: Jun 17 06:47AM


>> The problem is that if you trigger UB, the compiler is allowed to
>> do whatever it wants with it. Including not doing what you want.
 
> Not in this case.
 
What do you mean? The compiler is *always* allowed to do whatever it wants
if something is UB.
Scott Newman <scott69@gmail.com>: Jun 17 09:06AM +0200

>> Not in this case.
 
> What do you mean? The compiler is *always* allowed to do whatever it wants
> if something is UB.
 
There's no UB with what Chris does initially.
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Jun 17 10:49AM +0100

On Wed, 17 Jun 2020 06:36:24 +0000 (UTC)
> now the exact reason, but might have had something to do with deliberately
> dererencing a null pointer, which is UB, and which the compiler was
> "optimizing" away because the standard allowed it to.)
 
Yes I agree that is the problem with undefined behaviour. Which I
suppose is one reason why the standard committee should be careful
before spraying it over the standard and undermining programmers' well
established past practices.
Manfred <noname@add.invalid>: Jun 17 03:27PM +0200

On 6/17/2020 12:39 AM, Chris M. Thomasson wrote:
 
>> Would would one need to cast the return value of malloc to (X*)?
 
> Why would one need to cast the return value? void* can do it as is,
> right? In pure C...
 
In C yes, in C++ a type cast is required. A static_cast would suffice.
 
This is why in my other posts I wonder why such a type cast could not be
enough (at least for PODs) to overcome the issue about object creation
and lifetime that is described in the proposal.
Instead of going down some contrived path like requiring malloc to "look
into the future" and such.
Manfred <noname@add.invalid>: Jun 17 03:35PM +0200

On 6/17/2020 1:11 AM, Chris M. Thomasson wrote:
 
> Touche! So, basically, I need to use placement new and explicit dtor via
>  calling the dtor ~Type(). Then it becomes a "real object" in C++. I
> thought that POD would be different in a sense.
 
If you look at the quote from Bjarne's "The C++ Programming Language"
that I posted earlier you see that he himself does not require using
placement new for malloc on a type with no constructor like this.
Apparently, for some reason, the standard landed somewhere else.
boltar@nowhere.co.uk: Jun 17 03:20PM

On Wed, 17 Jun 2020 15:27:26 +0200
 
>> Why would one need to cast the return value? void* can do it as is,
>> right? In pure C...
 
>In C yes, in C++ a type cast is required. A static_cast would suffice.
 
A static_cast is just a more verbose C cast and the two are interchangable.
Manfred <noname@add.invalid>: Jun 17 05:41PM +0200

>>> right? In pure C...
 
>> In C yes, in C++ a type cast is required. A static_cast would suffice.
 
> A static_cast is just a more verbose C cast and the two are interchangable.
 
No, they are not.
 
https://en.cppreference.com/w/cpp/language/expressions#Conversions
boltar@nowhere.co.uk: Jun 17 03:46PM

On Wed, 17 Jun 2020 17:41:33 +0200
 
>> A static_cast is just a more verbose C cast and the two are interchangable.
 
>No, they are not.
 
>https://en.cppreference.com/w/cpp/language/expressions#Conversions
 
Its close enough as substitute in 99% of circumstances. Though personally
I prefer a C cast, C++ has enough verbiage as it is.
Manfred <noname@add.invalid>: Jun 17 07:13PM +0200


>> https://en.cppreference.com/w/cpp/language/expressions#Conversions
 
> Its close enough as substitute in 99% of circumstances. Though personally
> I prefer a C cast, C++ has enough verbiage as it is.
 
Read more carefully:
A C-style cast can perform a reinterpret_cast (possibly even combined
with a const_cast). This is a major difference with static_cast.
 
Moreover, verbosity has been widely publicized as intentional for cast
operators, for obvious reasons.
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Jun 17 06:32PM +0100

On Wed, 17 Jun 2020 15:35:06 +0200
Manfred <noname@add.invalid> wrote:
[snip]
> that I posted earlier you see that he himself does not require using
> placement new for malloc on a type with no constructor like this.
> Apparently, for some reason, the standard landed somewhere else.
 
He even gives an example (4th edition) of the dangers of realloc, while
constructing trivial types in malloc'ed memory as part of the
explanation, on the basis that using malloc for trivial types is fine.
 
The fact is that until C++17's new object lifetime rules everyone
believed this usage was OK with trivial types, and it was commonplace to
static_cast malloc'ed memory to a pointer to such types and manipulate
them. The first time I heard that this was now thought to be incorrect
was about 2 years ago. There will be shed-loads of code out there
which does this. Similarly the new requirement in C++17 for
std::launder as a supplement to reinterpret_cast and the strict
aliasing rules for some cases.
 
The standard committee probably managed to persuade themselves that
pre-C++17 usage was undefined in some way (you can pretty much persuade
yourself of anything in the C++ standard if you try hard enough).
But instead of supporting ancient usage they decided to deliberately
break it.
 
I really can't respect that kind of thing, and it has certainly
affected my approach when considering programming languages. I heard
tell that Stroustrup was opposed to the object lifetime changes in
C++17: if so, he lost the argument.
James Kuyper <jameskuyper@alumni.caltech.edu>: Jun 17 01:05PM -0700

> On Wed, 17 Jun 2020 17:41:33 +0200
> Manfred <noname@add.invalid> wrote:
> >On 6/17/2020 5:20 PM, boltar@nowhere.co.uk wrote:
...
 
> >https://en.cppreference.com/w/cpp/language/expressions#Conversions
 
> Its close enough as substitute in 99% of circumstances. Though personally
> I prefer a C cast, C++ has enough verbiage as it is.
 
No, they are not interchangeable - they're not even close to being 99%
interchangeable.
 
"The conversions performed by
(4.1) — a const_cast (8.2.11),
(4.2) — a static_cast (8.2.9),
(4.3) — a static_cast followed by a const_cast,
(4.4) — a reinterpret_cast (8.2.10), or
(4.5) — a reinterpret_cast followed by a const_cast,
can be performed using the cast notation of explicit type conversion.
(4.6) — a pointer to an object of derived class type or an lvalue or
rvalue of derived class type may be explicitly converted to a pointer or
reference to an unambiguous base class type, respectively;
(4.7) — a pointer to member of derived class type may be explicitly
converted to a pointer to member of an unambiguous non-virtual base
class type;
(4.8) — a pointer to an object of an unambiguous non-virtual base class
type, a glvalue of an unambiguous non-virtual base class type, or a
pointer to member of an unambiguous non-virtual base class type may be
explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively." (8.4)
 
Therefore, if you run into a C-style cast, there's 7 different types of
conversions it might be performing that cannot be performed by a
static_cast<> alone. I've no hard figures on this, but I suspect that
the number of C-style casts that perform one of those 7 other
conversions is a lot higher than 1%, and those casts cannot be replaced
by a static_cast<>.
 
Replacing a working static_cast<> with a C-style cast always results in
working code, but is a bad idea. However, your comment about preferring
C-style casts suggests that you might not agree. I'll explain why it is
for the benefit of other people - feel free to stop reading here.
 
Stroustruap split the C style cast into multiple different named casts
precisely because accidentally using a C style cast to perform a
conversion more dangerous than the one you thought you were performing
was one of the most common mistakes made by C programmers. Every
conversion that can be performed by a C-style cast that cannot be done
by a static_cast<> is a more dangerous conversion than a static_cast<>.
Stroustrup's idea was to have multiple different named casts, each of
which could only do some of the things that a C-style cast could do, so
you would only get one of the more dangerous conversions if you
explicitly requested it. It is an error requiring a diagnostic to try to
use one of those casts to perform a conversion that it couldn't do.
Those diagnostics are the key benefit that comes proper use of the named
casts.
Juha Nieminen <nospam@thanks.invalid>: Jun 17 06:44AM


> if (cond1 && cond2 && cond3 && cond4) {
> DoSomething();
> }
 
Only possible if there isn't any other code inside those conditionals.
Like:
 
if(!cond1) return;
doSomething();
if(!cond2) return;
doSomethingElse();
if(!cond3) return;
andSomethingOther();
if(!cond4) return;
theFinalThing();
Juha Nieminen <nospam@thanks.invalid>: Jun 17 06:41AM


>>Have you learned how to count to three yet?
 
> If you think a major rebalance would only involved updating 3 nodes then
> you have no idea how balancing works.
 
Rebalancing a binary tree (such as a red-black tree) requires updating
O(log(n)) nodes. Most usually approximately 2*log(n) nodes (no big-O).
 
So if your tree has 100 million nodes, rebalancing it after an insertion
or deletion requires updating about 54 nodes (give or take).
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: