Assigning pointers to atomic type to pointers to non atomic typeWhere is the lock for a std::atomic?Casting pointers to _Atomic pointers and _Atomic sizesHow do function pointers in C work?Are these compatible function types in C?Pointer to qualified and non-qualified type representationtechnical legality of incompatible pointer assignmentsDangerous pointer cast results in loss of const qualificationIs this behavior of clang standard compliant?How to understand the conversion rule when a “pointer to an object type” compares for eqality with a “pointer to a void”?Why are structs not allowed in equality expressions in C?What made i = i++ + 1; legal in C++17?Function without prototype called with non-compatible type
Email Account under attack (really) - anything I can do?
Arthur Somervell: 1000 Exercises - Meaning of this notation
Adding span tags within wp_list_pages list items
What do you call a Matrix-like slowdown and camera movement effect?
Test if tikzmark exists on same page
Is it important to consider tone, melody, and musical form while writing a song?
In Japanese, what’s the difference between “Tonari ni” (となりに) and “Tsugi” (つぎ)? When would you use one over the other?
How can bays and straits be determined in a procedurally generated map?
Why, historically, did Gödel think CH was false?
How is the claim "I am in New York only if I am in America" the same as "If I am in New York, then I am in America?
Is it possible to do 50 km distance without any previous training?
"You are your self first supporter", a more proper way to say it
Why Is Death Allowed In the Matrix?
Font hinting is lost in Chrome-like browsers (for some languages )
Why did Neo believe he could trust the machine when he asked for peace?
How do I create uniquely male characters?
Can I ask the recruiters in my resume to put the reason why I am rejected?
A newer friend of my brother's gave him a load of baseball cards that are supposedly extremely valuable. Is this a scam?
How to write a macro that is braces sensitive?
Why was the small council so happy for Tyrion to become the Master of Coin?
Fencing style for blades that can attack from a distance
What is the offset in a seaplane's hull?
Has the BBC provided arguments for saying Brexit being cancelled is unlikely?
Is it unprofessional to ask if a job posting on GlassDoor is real?
Assigning pointers to atomic type to pointers to non atomic type
Where is the lock for a std::atomic?Casting pointers to _Atomic pointers and _Atomic sizesHow do function pointers in C work?Are these compatible function types in C?Pointer to qualified and non-qualified type representationtechnical legality of incompatible pointer assignmentsDangerous pointer cast results in loss of const qualificationIs this behavior of clang standard compliant?How to understand the conversion rule when a “pointer to an object type” compares for eqality with a “pointer to a void”?Why are structs not allowed in equality expressions in C?What made i = i++ + 1; legal in C++17?Function without prototype called with non-compatible type
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
Is the behavior of this code well-defined?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %dn", **int_ptr_ptr); //prints int = 42
I assigned a pointer to atomic type to a pointer to non-atomic type (the types are the same). Here are my thoughts of this example:
The Standard explicitly specify distinction of const
, volatile
and restrict
qualifiers from the _Atomic
qualifier 6.2.5(p27)
:
this Standard explicitly uses the phrase ‘‘atomic, qualified or
unqualified type’’ whenever the atomic version of a type is permitted
along with the other qualified versions of a type. The phrase
‘‘qualified or unqualified type’’, without specific mention of atomic,
does not include the atomic types.
Also the compatibility of qualified types is defined as 6.7.3(p10)
:
For two qualified types to be compatible, both shall have the
identically qualified versionof a compatible type; the order of
type qualifiers within a list of specifiers or qualifiers does
not affect the specified type.
Combining the quotes cited above I concluded that atomic and non-atomic types are compatible types. So, applying the rule of simple assigning 6.5.16.1(p1)
(emp. mine):
the left operand has atomic, qualified, or unqualified pointer
type, and (considering the type the left operand would have
after lvalue conversion) both operands are pointers to qualified
or unqualified versions of compatible types, and the type pointed to by
the left has all the qualifiers of the type pointed to by the right;
So I concluded that the behavior is well defined (even in spite of assigning atomic type to a non-atomic type).
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store
function for that.
c concurrency language-lawyer c11 stdatomic
add a comment |
Is the behavior of this code well-defined?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %dn", **int_ptr_ptr); //prints int = 42
I assigned a pointer to atomic type to a pointer to non-atomic type (the types are the same). Here are my thoughts of this example:
The Standard explicitly specify distinction of const
, volatile
and restrict
qualifiers from the _Atomic
qualifier 6.2.5(p27)
:
this Standard explicitly uses the phrase ‘‘atomic, qualified or
unqualified type’’ whenever the atomic version of a type is permitted
along with the other qualified versions of a type. The phrase
‘‘qualified or unqualified type’’, without specific mention of atomic,
does not include the atomic types.
Also the compatibility of qualified types is defined as 6.7.3(p10)
:
For two qualified types to be compatible, both shall have the
identically qualified versionof a compatible type; the order of
type qualifiers within a list of specifiers or qualifiers does
not affect the specified type.
Combining the quotes cited above I concluded that atomic and non-atomic types are compatible types. So, applying the rule of simple assigning 6.5.16.1(p1)
(emp. mine):
the left operand has atomic, qualified, or unqualified pointer
type, and (considering the type the left operand would have
after lvalue conversion) both operands are pointers to qualified
or unqualified versions of compatible types, and the type pointed to by
the left has all the qualifiers of the type pointed to by the right;
So I concluded that the behavior is well defined (even in spite of assigning atomic type to a non-atomic type).
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store
function for that.
c concurrency language-lawyer c11 stdatomic
add a comment |
Is the behavior of this code well-defined?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %dn", **int_ptr_ptr); //prints int = 42
I assigned a pointer to atomic type to a pointer to non-atomic type (the types are the same). Here are my thoughts of this example:
The Standard explicitly specify distinction of const
, volatile
and restrict
qualifiers from the _Atomic
qualifier 6.2.5(p27)
:
this Standard explicitly uses the phrase ‘‘atomic, qualified or
unqualified type’’ whenever the atomic version of a type is permitted
along with the other qualified versions of a type. The phrase
‘‘qualified or unqualified type’’, without specific mention of atomic,
does not include the atomic types.
Also the compatibility of qualified types is defined as 6.7.3(p10)
:
For two qualified types to be compatible, both shall have the
identically qualified versionof a compatible type; the order of
type qualifiers within a list of specifiers or qualifiers does
not affect the specified type.
Combining the quotes cited above I concluded that atomic and non-atomic types are compatible types. So, applying the rule of simple assigning 6.5.16.1(p1)
(emp. mine):
the left operand has atomic, qualified, or unqualified pointer
type, and (considering the type the left operand would have
after lvalue conversion) both operands are pointers to qualified
or unqualified versions of compatible types, and the type pointed to by
the left has all the qualifiers of the type pointed to by the right;
So I concluded that the behavior is well defined (even in spite of assigning atomic type to a non-atomic type).
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store
function for that.
c concurrency language-lawyer c11 stdatomic
Is the behavior of this code well-defined?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %dn", **int_ptr_ptr); //prints int = 42
I assigned a pointer to atomic type to a pointer to non-atomic type (the types are the same). Here are my thoughts of this example:
The Standard explicitly specify distinction of const
, volatile
and restrict
qualifiers from the _Atomic
qualifier 6.2.5(p27)
:
this Standard explicitly uses the phrase ‘‘atomic, qualified or
unqualified type’’ whenever the atomic version of a type is permitted
along with the other qualified versions of a type. The phrase
‘‘qualified or unqualified type’’, without specific mention of atomic,
does not include the atomic types.
Also the compatibility of qualified types is defined as 6.7.3(p10)
:
For two qualified types to be compatible, both shall have the
identically qualified versionof a compatible type; the order of
type qualifiers within a list of specifiers or qualifiers does
not affect the specified type.
Combining the quotes cited above I concluded that atomic and non-atomic types are compatible types. So, applying the rule of simple assigning 6.5.16.1(p1)
(emp. mine):
the left operand has atomic, qualified, or unqualified pointer
type, and (considering the type the left operand would have
after lvalue conversion) both operands are pointers to qualified
or unqualified versions of compatible types, and the type pointed to by
the left has all the qualifiers of the type pointed to by the right;
So I concluded that the behavior is well defined (even in spite of assigning atomic type to a non-atomic type).
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store
function for that.
c concurrency language-lawyer c11 stdatomic
c concurrency language-lawyer c11 stdatomic
edited 17 hours ago
Peter Cordes
134k18203342
134k18203342
asked 18 hours ago
Some NameSome Name
1,775418
1,775418
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
6.2.5p27:
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
I think this should make it clear that atomic-qualified types are not deemed compatible with qualified or unqualified versions of the types they're based on.
add a comment |
I C11 allows _Atomic T
to have a different size and layout than T
, e.g. if it's not lock-free. (See @PSkocik's answer). And also alignof(T) !=
alignof(_Atomic T)`, but that's not an issue for casting pointers to objects that already exist.
For example, the implementation could choose to put a mutex inside each atomic object, and put it first. (Instead of the usual using the address as an index into a table of locks: Where is the lock for a std::atomic?).
Therefore _Atomic T*
is not compatible with T*
even in a single-threaded program.
Merely assigning a pointer might not be UB (sorry I didn't put on my language lawyer hat), but dereferencing certainly must be.
(In practice on real implementations, _Atomic T*
and T*
use the same object representation, and a single-threaded or mutex-guarded part of a program could do non-atomic access to an _Atomic
object.)
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
No, that reasoning is flawed.
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
They both do an atomic store with memory_order_seq_cst
. You can see this from looking at the code-gen for real compilers, e.g. x86 compilers will use an xchg
instruction, or mov
+mfence
. Similarly, shared_var++
compiles to an atomic RMW (with mo_seq_cst
).
IDK why there's an atomic_store
generic function. Maybe just for contrast / consistency with atomic_store_explicit
, which lets you do atomic_store_explicit(&shared_var, 1, memory_order_release)
or memory_order_relaxed
to do a release or relaxed store instead of sequential-release. (On x86, just a plain store. Or on weakly-ordered ISAs, some fencing but not a full barrier.)
For types that are lock-free, where the object representation of _Atomic T
and T
are identical, there's no problem in practice accessing an atomic object through a non-atomic pointer in a single-threaded program. I suspect it's still UB, though.
C++20 is planning to introduce std::atomic_ref<T>
which will let you do atomic operations on a non-atomic variable. (With no UB as long as no threads are potentially doing non-atomic access to it during the time window of being written.) This is basically a wrapper around the __atomic_*
builtins in GCC for example, that std::atomic<T>
is implemented on top of. (This presents some problems, like if atomic<T>
needs more alignment than T
, e.g. for long long
or double
on i386 System V. Or a struct of 2x int
on most 64-bit ISAs.)
Anyway, I'm not aware of any standards-compliant way to do similar things in portable ISO C11, but it's worth mentioning that real C compilers very much do support doing atomic operations on objects declared without _Atomic
. But only using stuff like GNU C atomic builtins.:
See Casting pointers to _Atomic pointers and _Atomic sizes : apparently casting a T*
to _Atomic T*
is not recommended even in GNU C. Although we don't have a definitive answer that it's actually UB.
You mentioned thatatomic_store(&my_atomic, 1)
is equivalent tomy_atomic=1;
. I tried to test a similar thing and wrote the following function:void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with-O3
gave themfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining themfence
).
– Some Name
17 hours ago
1
@SomeName: yes, I said that in my answer. But you didn't try compiling*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly,mov+mfence
with gcc, or a more efficientxchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...
– Peter Cordes
17 hours ago
2
"unless GNU C defines the behaviour of casting aT*
to_Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…
– PSkocik
17 hours ago
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for_Atomic
types.
– Peter Cordes
16 hours ago
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on analignof(T) = 4
struct of size=8. Will fix later when I ahve time.
– Peter Cordes
11 hours ago
|
show 2 more comments
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55547081%2fassigning-pointers-to-atomic-type-to-pointers-to-non-atomic-type%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
6.2.5p27:
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
I think this should make it clear that atomic-qualified types are not deemed compatible with qualified or unqualified versions of the types they're based on.
add a comment |
6.2.5p27:
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
I think this should make it clear that atomic-qualified types are not deemed compatible with qualified or unqualified versions of the types they're based on.
add a comment |
6.2.5p27:
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
I think this should make it clear that atomic-qualified types are not deemed compatible with qualified or unqualified versions of the types they're based on.
6.2.5p27:
Further, there is the _Atomic qualifier. The presence of the _Atomic
qualifier designates an atomic type. The size, representation, and
alignment of an atomic type need not be the same as those of the
corresponding unqualified type. Therefore, this Standard explicitly
uses the phrase ''atomic, qualified or unqualified type'' whenever the
atomic version of a type is permitted along with the other qualified
versions of a type. The phrase ''qualified or unqualified type'',
without specific mention of atomic, does not include the atomic types.
I think this should make it clear that atomic-qualified types are not deemed compatible with qualified or unqualified versions of the types they're based on.
answered 17 hours ago
PSkocikPSkocik
35k65579
35k65579
add a comment |
add a comment |
I C11 allows _Atomic T
to have a different size and layout than T
, e.g. if it's not lock-free. (See @PSkocik's answer). And also alignof(T) !=
alignof(_Atomic T)`, but that's not an issue for casting pointers to objects that already exist.
For example, the implementation could choose to put a mutex inside each atomic object, and put it first. (Instead of the usual using the address as an index into a table of locks: Where is the lock for a std::atomic?).
Therefore _Atomic T*
is not compatible with T*
even in a single-threaded program.
Merely assigning a pointer might not be UB (sorry I didn't put on my language lawyer hat), but dereferencing certainly must be.
(In practice on real implementations, _Atomic T*
and T*
use the same object representation, and a single-threaded or mutex-guarded part of a program could do non-atomic access to an _Atomic
object.)
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
No, that reasoning is flawed.
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
They both do an atomic store with memory_order_seq_cst
. You can see this from looking at the code-gen for real compilers, e.g. x86 compilers will use an xchg
instruction, or mov
+mfence
. Similarly, shared_var++
compiles to an atomic RMW (with mo_seq_cst
).
IDK why there's an atomic_store
generic function. Maybe just for contrast / consistency with atomic_store_explicit
, which lets you do atomic_store_explicit(&shared_var, 1, memory_order_release)
or memory_order_relaxed
to do a release or relaxed store instead of sequential-release. (On x86, just a plain store. Or on weakly-ordered ISAs, some fencing but not a full barrier.)
For types that are lock-free, where the object representation of _Atomic T
and T
are identical, there's no problem in practice accessing an atomic object through a non-atomic pointer in a single-threaded program. I suspect it's still UB, though.
C++20 is planning to introduce std::atomic_ref<T>
which will let you do atomic operations on a non-atomic variable. (With no UB as long as no threads are potentially doing non-atomic access to it during the time window of being written.) This is basically a wrapper around the __atomic_*
builtins in GCC for example, that std::atomic<T>
is implemented on top of. (This presents some problems, like if atomic<T>
needs more alignment than T
, e.g. for long long
or double
on i386 System V. Or a struct of 2x int
on most 64-bit ISAs.)
Anyway, I'm not aware of any standards-compliant way to do similar things in portable ISO C11, but it's worth mentioning that real C compilers very much do support doing atomic operations on objects declared without _Atomic
. But only using stuff like GNU C atomic builtins.:
See Casting pointers to _Atomic pointers and _Atomic sizes : apparently casting a T*
to _Atomic T*
is not recommended even in GNU C. Although we don't have a definitive answer that it's actually UB.
You mentioned thatatomic_store(&my_atomic, 1)
is equivalent tomy_atomic=1;
. I tried to test a similar thing and wrote the following function:void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with-O3
gave themfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining themfence
).
– Some Name
17 hours ago
1
@SomeName: yes, I said that in my answer. But you didn't try compiling*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly,mov+mfence
with gcc, or a more efficientxchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...
– Peter Cordes
17 hours ago
2
"unless GNU C defines the behaviour of casting aT*
to_Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…
– PSkocik
17 hours ago
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for_Atomic
types.
– Peter Cordes
16 hours ago
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on analignof(T) = 4
struct of size=8. Will fix later when I ahve time.
– Peter Cordes
11 hours ago
|
show 2 more comments
I C11 allows _Atomic T
to have a different size and layout than T
, e.g. if it's not lock-free. (See @PSkocik's answer). And also alignof(T) !=
alignof(_Atomic T)`, but that's not an issue for casting pointers to objects that already exist.
For example, the implementation could choose to put a mutex inside each atomic object, and put it first. (Instead of the usual using the address as an index into a table of locks: Where is the lock for a std::atomic?).
Therefore _Atomic T*
is not compatible with T*
even in a single-threaded program.
Merely assigning a pointer might not be UB (sorry I didn't put on my language lawyer hat), but dereferencing certainly must be.
(In practice on real implementations, _Atomic T*
and T*
use the same object representation, and a single-threaded or mutex-guarded part of a program could do non-atomic access to an _Atomic
object.)
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
No, that reasoning is flawed.
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
They both do an atomic store with memory_order_seq_cst
. You can see this from looking at the code-gen for real compilers, e.g. x86 compilers will use an xchg
instruction, or mov
+mfence
. Similarly, shared_var++
compiles to an atomic RMW (with mo_seq_cst
).
IDK why there's an atomic_store
generic function. Maybe just for contrast / consistency with atomic_store_explicit
, which lets you do atomic_store_explicit(&shared_var, 1, memory_order_release)
or memory_order_relaxed
to do a release or relaxed store instead of sequential-release. (On x86, just a plain store. Or on weakly-ordered ISAs, some fencing but not a full barrier.)
For types that are lock-free, where the object representation of _Atomic T
and T
are identical, there's no problem in practice accessing an atomic object through a non-atomic pointer in a single-threaded program. I suspect it's still UB, though.
C++20 is planning to introduce std::atomic_ref<T>
which will let you do atomic operations on a non-atomic variable. (With no UB as long as no threads are potentially doing non-atomic access to it during the time window of being written.) This is basically a wrapper around the __atomic_*
builtins in GCC for example, that std::atomic<T>
is implemented on top of. (This presents some problems, like if atomic<T>
needs more alignment than T
, e.g. for long long
or double
on i386 System V. Or a struct of 2x int
on most 64-bit ISAs.)
Anyway, I'm not aware of any standards-compliant way to do similar things in portable ISO C11, but it's worth mentioning that real C compilers very much do support doing atomic operations on objects declared without _Atomic
. But only using stuff like GNU C atomic builtins.:
See Casting pointers to _Atomic pointers and _Atomic sizes : apparently casting a T*
to _Atomic T*
is not recommended even in GNU C. Although we don't have a definitive answer that it's actually UB.
You mentioned thatatomic_store(&my_atomic, 1)
is equivalent tomy_atomic=1;
. I tried to test a similar thing and wrote the following function:void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with-O3
gave themfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining themfence
).
– Some Name
17 hours ago
1
@SomeName: yes, I said that in my answer. But you didn't try compiling*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly,mov+mfence
with gcc, or a more efficientxchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...
– Peter Cordes
17 hours ago
2
"unless GNU C defines the behaviour of casting aT*
to_Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…
– PSkocik
17 hours ago
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for_Atomic
types.
– Peter Cordes
16 hours ago
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on analignof(T) = 4
struct of size=8. Will fix later when I ahve time.
– Peter Cordes
11 hours ago
|
show 2 more comments
I C11 allows _Atomic T
to have a different size and layout than T
, e.g. if it's not lock-free. (See @PSkocik's answer). And also alignof(T) !=
alignof(_Atomic T)`, but that's not an issue for casting pointers to objects that already exist.
For example, the implementation could choose to put a mutex inside each atomic object, and put it first. (Instead of the usual using the address as an index into a table of locks: Where is the lock for a std::atomic?).
Therefore _Atomic T*
is not compatible with T*
even in a single-threaded program.
Merely assigning a pointer might not be UB (sorry I didn't put on my language lawyer hat), but dereferencing certainly must be.
(In practice on real implementations, _Atomic T*
and T*
use the same object representation, and a single-threaded or mutex-guarded part of a program could do non-atomic access to an _Atomic
object.)
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
No, that reasoning is flawed.
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
They both do an atomic store with memory_order_seq_cst
. You can see this from looking at the code-gen for real compilers, e.g. x86 compilers will use an xchg
instruction, or mov
+mfence
. Similarly, shared_var++
compiles to an atomic RMW (with mo_seq_cst
).
IDK why there's an atomic_store
generic function. Maybe just for contrast / consistency with atomic_store_explicit
, which lets you do atomic_store_explicit(&shared_var, 1, memory_order_release)
or memory_order_relaxed
to do a release or relaxed store instead of sequential-release. (On x86, just a plain store. Or on weakly-ordered ISAs, some fencing but not a full barrier.)
For types that are lock-free, where the object representation of _Atomic T
and T
are identical, there's no problem in practice accessing an atomic object through a non-atomic pointer in a single-threaded program. I suspect it's still UB, though.
C++20 is planning to introduce std::atomic_ref<T>
which will let you do atomic operations on a non-atomic variable. (With no UB as long as no threads are potentially doing non-atomic access to it during the time window of being written.) This is basically a wrapper around the __atomic_*
builtins in GCC for example, that std::atomic<T>
is implemented on top of. (This presents some problems, like if atomic<T>
needs more alignment than T
, e.g. for long long
or double
on i386 System V. Or a struct of 2x int
on most 64-bit ISAs.)
Anyway, I'm not aware of any standards-compliant way to do similar things in portable ISO C11, but it's worth mentioning that real C compilers very much do support doing atomic operations on objects declared without _Atomic
. But only using stuff like GNU C atomic builtins.:
See Casting pointers to _Atomic pointers and _Atomic sizes : apparently casting a T*
to _Atomic T*
is not recommended even in GNU C. Although we don't have a definitive answer that it's actually UB.
I C11 allows _Atomic T
to have a different size and layout than T
, e.g. if it's not lock-free. (See @PSkocik's answer). And also alignof(T) !=
alignof(_Atomic T)`, but that's not an issue for casting pointers to objects that already exist.
For example, the implementation could choose to put a mutex inside each atomic object, and put it first. (Instead of the usual using the address as an index into a table of locks: Where is the lock for a std::atomic?).
Therefore _Atomic T*
is not compatible with T*
even in a single-threaded program.
Merely assigning a pointer might not be UB (sorry I didn't put on my language lawyer hat), but dereferencing certainly must be.
(In practice on real implementations, _Atomic T*
and T*
use the same object representation, and a single-threaded or mutex-guarded part of a program could do non-atomic access to an _Atomic
object.)
The problem with all that is that applying the rules above we can also conclude that simple assignment a non-atomic type to an atomic type is also well defined which is obviously not true since we have a dedicated generic atomic_store function for that.
No, that reasoning is flawed.
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
They both do an atomic store with memory_order_seq_cst
. You can see this from looking at the code-gen for real compilers, e.g. x86 compilers will use an xchg
instruction, or mov
+mfence
. Similarly, shared_var++
compiles to an atomic RMW (with mo_seq_cst
).
IDK why there's an atomic_store
generic function. Maybe just for contrast / consistency with atomic_store_explicit
, which lets you do atomic_store_explicit(&shared_var, 1, memory_order_release)
or memory_order_relaxed
to do a release or relaxed store instead of sequential-release. (On x86, just a plain store. Or on weakly-ordered ISAs, some fencing but not a full barrier.)
For types that are lock-free, where the object representation of _Atomic T
and T
are identical, there's no problem in practice accessing an atomic object through a non-atomic pointer in a single-threaded program. I suspect it's still UB, though.
C++20 is planning to introduce std::atomic_ref<T>
which will let you do atomic operations on a non-atomic variable. (With no UB as long as no threads are potentially doing non-atomic access to it during the time window of being written.) This is basically a wrapper around the __atomic_*
builtins in GCC for example, that std::atomic<T>
is implemented on top of. (This presents some problems, like if atomic<T>
needs more alignment than T
, e.g. for long long
or double
on i386 System V. Or a struct of 2x int
on most 64-bit ISAs.)
Anyway, I'm not aware of any standards-compliant way to do similar things in portable ISO C11, but it's worth mentioning that real C compilers very much do support doing atomic operations on objects declared without _Atomic
. But only using stuff like GNU C atomic builtins.:
See Casting pointers to _Atomic pointers and _Atomic sizes : apparently casting a T*
to _Atomic T*
is not recommended even in GNU C. Although we don't have a definitive answer that it's actually UB.
edited 16 hours ago
answered 17 hours ago
Peter CordesPeter Cordes
134k18203342
134k18203342
You mentioned thatatomic_store(&my_atomic, 1)
is equivalent tomy_atomic=1;
. I tried to test a similar thing and wrote the following function:void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with-O3
gave themfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining themfence
).
– Some Name
17 hours ago
1
@SomeName: yes, I said that in my answer. But you didn't try compiling*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly,mov+mfence
with gcc, or a more efficientxchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...
– Peter Cordes
17 hours ago
2
"unless GNU C defines the behaviour of casting aT*
to_Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…
– PSkocik
17 hours ago
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for_Atomic
types.
– Peter Cordes
16 hours ago
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on analignof(T) = 4
struct of size=8. Will fix later when I ahve time.
– Peter Cordes
11 hours ago
|
show 2 more comments
You mentioned thatatomic_store(&my_atomic, 1)
is equivalent tomy_atomic=1;
. I tried to test a similar thing and wrote the following function:void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with-O3
gave themfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining themfence
).
– Some Name
17 hours ago
1
@SomeName: yes, I said that in my answer. But you didn't try compiling*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly,mov+mfence
with gcc, or a more efficientxchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...
– Peter Cordes
17 hours ago
2
"unless GNU C defines the behaviour of casting aT*
to_Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…
– PSkocik
17 hours ago
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for_Atomic
types.
– Peter Cordes
16 hours ago
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on analignof(T) = 4
struct of size=8. Will fix later when I ahve time.
– Peter Cordes
11 hours ago
You mentioned that
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
. I tried to test a similar thing and wrote the following function: void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with -O3
gave the mfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining the mfence
).– Some Name
17 hours ago
You mentioned that
atomic_store(&my_atomic, 1)
is equivalent to my_atomic=1;
. I tried to test a similar thing and wrote the following function: void do_test_atomic(volatile _Atomic int *ptr, int val) atomic_store(ptr, val);
compiled with -O3
gave the mfence
at the end of the function. godbolt.org/z/vrFCLT . (I'm not closely familar with intel x86 memory model so please correct me if I'm wrong, but afaik stores go into store buffer first so we need a fence to avoid reordering caused by the store buffer forwarding which I think explaining the mfence
).– Some Name
17 hours ago
1
1
@SomeName: yes, I said that in my answer. But you didn't try compiling
*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly, mov+mfence
with gcc, or a more efficient xchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...– Peter Cordes
17 hours ago
@SomeName: yes, I said that in my answer. But you didn't try compiling
*ptr=val;
, which was the whole point. godbolt.org/z/OdxR_h It compiles to identical assembly, mov+mfence
with gcc, or a more efficient xchg [rdi], esi
) with clang. On a weakly-ordered ISA, you'd get more fencing. Or AArch64 has a special instruction for a seqeuential-release store...– Peter Cordes
17 hours ago
2
2
"unless GNU C defines the behaviour of casting a
T*
to _Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…– PSkocik
17 hours ago
"unless GNU C defines the behaviour of casting a
T*
to _Atomic T*
" I asked about that 2 weeks ago. Was told to use the builtins: stackoverflow.com/questions/55299525/…– PSkocik
17 hours ago
1
1
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for
_Atomic
types.– Peter Cordes
16 hours ago
@SomeName: no, I meant it's equivalent in the C abstract machine. (And separately that you can check it with whatever compiler you want on whatever architecture you want, x86 being one example.) ISO C11 "overloads" assignment and various other operators for
_Atomic
types.– Peter Cordes
16 hours ago
1
1
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on an
alignof(T) = 4
struct of size=8. Will fix later when I ahve time.– Peter Cordes
11 hours ago
@AndrewHenle: Oh, you're right. A machine with alignment-required 8-byte loads could fault on an
alignof(T) = 4
struct of size=8. Will fix later when I ahve time.– Peter Cordes
11 hours ago
|
show 2 more comments
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55547081%2fassigning-pointers-to-atomic-type-to-pointers-to-non-atomic-type%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown