Wednesday, June 16, 2021

Digest for comp.lang.c++@googlegroups.com - 25 updates in 1 topic

Sam <sam@email-scan.com>: Jun 16 08:16AM -0400

Bonita Montero writes:
 
 
>> Of course they do, it happens all the time.
 
> What I meant was that you can be assured that std::allocator<>
> directly or indirectly uses malloc() because what I said.
 
No, you cannot be assured of that. If you don't believe me, see the C++
standard.
Sam <sam@email-scan.com>: Jun 16 08:25AM -0400


> Then whats the point of std::atomic? The only time people need atomic
> operations is precisely during multithreaded or parallel style programming.
> Even SysV semaphores can manage true atomicity between threads.
 
There are some situations where atomic variables are what the doctor ordered.
 
The C++ library's std::shared_ptr is a perfect example. If its destructor
decrements the shared reference count and becomes zero it logically means
that no other instance of std::shared_ptr exists, so it's safe to destroy
the referenced object. In all other cases, the actual non-0 result is
irrelevant, and does not have to precisely 54tl4f6 the count of
std::shared_ptrs of the same object that remain in scope.
 
But if, at any given moment, all std::shared_ptrs are getting destroyed the
atomic counter guarantees that exactly one execution thread's destructor
will observe the final reference count of 0, and destroy the object. Who
cares which execution thread does it, that's irrelevant. But as long as
there is at least one hold-out somewhere it will never reach 0 in any
execution thread.
 
Also one could use atomic variables, manually, with memory barriers and
reinvent the wheel called "sequencing", if there's some pressing need to do
so and std::mutex doesn't fit the bill.
David Brown <david.brown@hesbynett.no>: Jun 16 02:43PM +0200


> Hand up. What does it do? Or is it simply the same as static int because
> the compiler doesn't care about certain syntax order, the same way that a[0]
> and 0[a] are equivalent?
 
"void bar(int a[static 10]);" means "bar" can only be called with a
pointer to an array of at least 10 int's. The compiler can optimise the
implementation based on that, or warn if the function is called in a way
that violates this (for example, with a null pointer).
 
It's a fairly obscure usage, and I think few C programmers will know
about it even if they have long experience of the language. I can't say
it is a feature I have ever thought to use. You could use:
 
void foo(int a[static 1]);
 
as a portable alternative to gcc's
 
void foo(int * a) __attribute__((nonull));
 
However, I would say that gcc's syntax is a lot clearer (assuming, of
course, you are using a compiler that accepts it).
David Brown <david.brown@hesbynett.no>: Jun 16 02:44PM +0200

On 16/06/2021 14:43, David Brown wrote:
 
> void foo(int * a) __attribute__((nonull));
 
> However, I would say that gcc's syntax is a lot clearer (assuming, of
> course, you are using a compiler that accepts it).
 
I forgot to mention - AFAIK, the "void foo(int a[static 1])" syntax does
not exist for C++.
Juha Nieminen <nospam@thanks.invalid>: Jun 16 01:09PM

> presuming that nothing else happens. However, it is unspecified when this
> joyous event occurs and is observable by a given execution thread. That
> requires sequencing.
 
A variable being atomic guarantees that no thread will see a garbage value
for it. In other words, if one thread is changing the value of the atomic
variable and another thread is reading it at the same time, that second
thread is going to get either the old value or the new value, but it's
guaranteed that it will not get some third garbage value.
 
It also guarantees that two threads trying to assign something to the
atomic variable will not interfere with each other so that garbage ends
up in the variable. The final value will be one or the other (depending
on which thread was allowed to access it first and which one after that),
and while the variable may contain for a brief moment the value written
by the first thread before it gets the value written by the second thread,
at no point will it contain any garbage value that's neither (nor the
original).
 
Atomic variables (at least with std::atomic) are also what you would
call "volatile", in that any read and assignment will happen exactly
where the operation appears in the code and nowhere else. The compiler
will not optimize it away nor move it somewhere else (such as outside
a loop). It will read or assign it precisely where and how many times
you say.
 
Of course atomic variables can be misused. They are not locks, fences
or semaphores. For example using atomic variable for a spinlock is
most probably not going to work unless you use an atomic compare-and-swap
instruction or similar. Simply doing a
"if(atomic_var == true) { atomic_var = false; dosomething(); }"
will not stop two threads from incorrectly entering that critical
section at the same time. Atomic variables need to be used correctly.
Paavo Helde <myfirstname@osa.pri.ee>: Jun 16 04:10PM +0300

16.06.2021 15:25 Sam kirjutas:
> Also one could use atomic variables, manually, with memory barriers and
> reinvent the wheel called "sequencing", if there's some pressing need to
> do so and std::mutex doesn't fit the bill.
 
As far as I can see the atomics are by default using memory barriers.
The default memory ordering is std::memory_order_seq_cst which means
memory barriers and lots of other guarantees:
 
https://en.cppreference.com/w/cpp/atomic/memory_order :
 
"Atomic operations tagged memory_order_seq_cst not only order memory the
same way as release/acquire ordering (everything that happened-before a
store in one thread becomes a visible side effect in the thread that did
a load), but also establish a single total modification order of all
atomic operations that are so tagged."
 
What you are describing seems more like std::memory_order_relaxed, which
is not the default. What I have misunderstood?
Juha Nieminen <nospam@thanks.invalid>: Jun 16 01:14PM

> "static" in "void bar(int a[static 10]);" is doing?), people will find
> unusual ways to express things. And it is best solved by reading each
> other's code, talking together, and sharing information.
 
C (and thus usually also C++) can indeed have some weird features that
aren't widely known even by experienced programmers. For example, I would
bet that quite a large portion of C (and C++) programmers would think
this is invalid and doesn't compile:
 
int i = 3;
int j = i["hello"];
Juha Nieminen <nospam@thanks.invalid>: Jun 16 01:16PM

> You have lost capability to read, Juha, so you build straw-men that I did not
> write about C++98 and so then argue with those.
 
Is it even possible to just have a conversation in this newsgroup without
accusations being thrown around? Just relax.
Bonita Montero <Bonita.Montero@gmail.com>: Jun 16 03:35PM +0200

>> What I meant was that you can be assured that std::allocator<>
>> directly or indirectly uses malloc() because what I said.
 
> No, you cannot be assured of that. ...
 
It has nothting to do with the standard.
I told you why this is the most reasonable assumption.
MrSpook_ucGx3e4l@x8gqtuqjwl9z3g.com: Jun 16 01:47PM

On Wed, 16 Jun 2021 08:25:18 -0400
 
>The C++ library's std::shared_ptr is a perfect example. If its destructor
>decrements the shared reference count and becomes zero it logically means
>that no other instance of std::shared_ptr exists, so it's safe to destroy
 
Sure, but that only matters in a multi threaded program. And if atomic
isn't guaranteed to be thread safe then surely you'd be better off just
using mutex locks?
MrSpook_g3w@2ur2yrijxtw7d3jt394yu9dp.org: Jun 16 01:48PM

On Wed, 16 Jun 2021 14:44:37 +0200
>> course, you are using a compiler that accepts it).
 
>I forgot to mention - AFAIK, the "void foo(int a[static 1])" syntax does
>not exist for C++.
 
Is it C99?
MrSpook_74D@ov88_wt8h.co.uk: Jun 16 01:54PM

On Wed, 16 Jun 2021 14:43:59 +0200
 
> void foo(int * a) __attribute__((nonull));
 
>However, I would say that gcc's syntax is a lot clearer (assuming, of
>course, you are using a compiler that accepts it).
 
Just tried this with gcc 8.3.0:
 
void func(int a[static 10])
{
}
 
 
int main()
{
int a[5];
func(a);
return 0;
}
 
 
Compiles without errors or warnings even with -Wall -pedantic -Wextra -std=c99
which is obviously not whats intended. Presumably implemented only in the
parser, not the core compiler.
David Brown <david.brown@hesbynett.no>: Jun 16 03:59PM +0200


> Sure, but that only matters in a multi threaded program. And if atomic
> isn't guaranteed to be thread safe then surely you'd be better off just
> using mutex locks?
 
The atomics /are/ guaranteed to be thread safe. But they are not
guaranteed to be synchronising (depending on the memory order given).
 
The prime point of atomic increments and decrements is that you'll end
up with the right number in the result, regardless of the order things
are done. This makes relaxed atomics very efficient and lightweight
compared to big locks.
David Brown <david.brown@hesbynett.no>: Jun 16 04:07PM +0200


>> I forgot to mention - AFAIK, the "void foo(int a[static 1])" syntax does
>> not exist for C++.
 
> Is it C99?
 
Yes, I believe so.
James Kuyper <jameskuyper@alumni.caltech.edu>: Jun 16 10:38AM -0400

On 6/16/21 7:14 AM, David Brown wrote:
...
> programming language doesn't have unusual and rarely used features (and
> which language doesn't? Hands up those C experts who know what the
> "static" in "void bar(int a[static 10]);" is doing?),
 
Hand up!
scott@slp53.sl.home (Scott Lurndal): Jun 16 03:04PM

>> a custom allocator/deallocator in the same program that also includes
>> C code, too, ...
 
>They don't.
 
As usual, you are wrong. It's quite common in some circles. None of
which you've ever had any experience with; nor have you exhibited the
programming skills (or interpersonal skills) to survive in those circles.
scott@slp53.sl.home (Scott Lurndal): Jun 16 03:09PM


>Then whats the point of std::atomic? The only time people need atomic
>operations is precisely during multithreaded or parallel style programming.
>Even SysV semaphores can manage true atomicity between threads.
 
Incrementing a shared counter without first acquiring a lock is a perfect
use for an atomic fetch_and_add (e.g. the ARM64 LDADD instruction or intel
LOCK INC).
Paavo Helde <myfirstname@osa.pri.ee>: Jun 16 06:10PM +0300

16.06.2021 14:40 Bonita Montero kirjutas:
>> executables where our library might be loaded into still use
>> whatever they want, typically standard malloc().
 
> Better replace the global memory-allocation through mimalloc.
 
This would still require access or influence over the main executable,
which I (and many other C++ developers) do not have.
Bonita Montero <Bonita.Montero@gmail.com>: Jun 16 05:37PM +0200

>>> C code, too, ...
 
>> They don't.
 
> As usual, you are wrong. It's quite common in some circles. ...
 
It's quite stupid to do that. You have to implement an allocator which
can be rebound for all types with typename allocator_traits<alloc>
::template rebind_alloc<T> since you can't anticipate what the STL
-containers rebind to, i.e. you have to write an allocator whith the
full capabilities of malloc(). That's much work and it's almost
impossible to beat the performance of common replacement-allocators.
So it's just the best way to replace malloc() which is indirectly or
directly used by std::allocator<> on all platforms.
 
> None of which you've ever had any experience with; nor have you exhibited
> the programming skills (or interpersonal skills) to survive in those circles.
 
I've already built a custom std::allocator<> replacement for special
cases where the allocator can be assumed not to be rebound (vector<>).
And I would be able to write an efficient allocator like mimalloc; but
I wouldn't have invented the basic structures for that - and you not
as well.
MrSpook_hjesjs@8pot25r5sg1xe.info: Jun 16 03:45PM

On Wed, 16 Jun 2021 15:09:58 GMT
 
>Incrementing a shared counter without first acquiring a lock is a perfect
>use for an atomic fetch_and_add (e.g. the ARM64 LDADD instruction or intel
>LOCK INC).
 
I'm confused. Either std::atomic is thread safe or it isn't. Which is it?
Bonita Montero <Bonita.Montero@gmail.com>: Jun 16 05:50PM +0200

> Incrementing a shared counter without first acquiring a lock is a perfect
> use for an atomic fetch_and_add (e.g. the ARM64 LDADD instruction or intel
> LOCK INC).
 
No, fetch_and_add is LOCK XADD.
David Brown <david.brown@hesbynett.no>: Jun 16 07:55PM +0200


> Compiles without errors or warnings even with -Wall -pedantic -Wextra -std=c99
> which is obviously not whats intended. Presumably implemented only in the
> parser, not the core compiler.
 
clang warns here, gcc does not. It's a case of clang being a little
better and more helpful at the static analysis in this case. The C
syntax provides compilers with an opportunity to warn you, but does not
require them to do so.
 
You could file this as a bug in gcc if you like (it's a missing feature,
rather than a bug, but it goes in the same bugzilla list).
David Brown <david.brown@hesbynett.no>: Jun 16 07:58PM +0200

On 16/06/2021 16:38, James Kuyper wrote:
>> which language doesn't? Hands up those C experts who know what the
>> "static" in "void bar(int a[static 10]);" is doing?),
 
> Hand up!
 
I bet /you/ read the challenge correctly, and are raising your hand
because you /do/ know what it means - unlike Mr Spook who raised his
hand because he /doesn't/ know. Still, that gave me the excuse to give
the answer - which I must have got right, otherwise you'd have corrected
me by now :-)
"Öö Tiib" <ootiib@hot.ee>: Jun 16 11:03AM -0700

On Wednesday, 16 June 2021 at 16:17:09 UTC+3, Juha Nieminen wrote:
> > write about C++98 and so then argue with those.
> Is it even possible to just have a conversation in this newsgroup without
> accusations being thrown around? Just relax.
 
But I feel relaxed ... my accusations were straight to the point. You in
lengths discussed something with what I mostly agree in manner like
I had said something that I did not. You avoided discussing what I said
that C++17 was horrible, pathetic trash, sabotage of language I liked
and so I'm stuck in C++14.
Paavo Helde <myfirstname@osa.pri.ee>: Jun 16 09:08PM +0300

>> use for an atomic fetch_and_add (e.g. the ARM64 LDADD instruction or intel
>> LOCK INC).
 
> I'm confused. Either std::atomic is thread safe or it isn't. Which is it?
 
Of course it is thread-safe, that's what the name 'atomic' means. Alas,
there are many definitions of "thread-safe", so one might easily get
confused.
 
In the case of atomics, "thread-safe" at least means the value of a
particular atomic is well-defined and predictable when the atomic is
accessed from multiple threads. Whether it means anything more depends
on the used memory order and other details.
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: