简体   繁体   中英

Is there a difference between the _Atomic type qualifier and type specifier?

Why the standard make that difference?

It seems as both designate, in the same way, an atomic type .

Atomic type specifiers :-:)

Syntax:     _Atomic ( type-name );

You can declare an atomic integer like this:

        _Atomic(int) counter;

The _Atomic keyword can be used in the form _Atomic(T) , where T is a type, as a type specifier equivalent to _Atomic T. Thus, _Atomic(T) x, y; declares x and y with the same type, even if T is a pointer type. This allows for trivial C++0x compatibility with a C++ only _Atomic(T) macro definition as atomic.

Atomic type specifiers shall not be used if the implementation does not support atomic types. The type name in an atomic type specifier shall not refer to an array type, a function type, an atomic type, or a qualified type.

The properties associated with atomic types are meaningful only for expressions that are lvalues.

If the _Atomic keyword is immediately followed by a left parenthesis, it is interpreted as a type specifier (with a type name), not as a type qualifier.

Atomic type qualifiers :-:)

        _Atomic volatile int *p;

It specifies that p has the type ''pointer to volatile atomic int'', a pointer to a volatile-qualified atomic type.

Types other than pointer types whose referenced type is an object type shall not be restrict-qualified. The type modified by the _Atomic qualifier shall not be an array type or a function type. The properties associated with qualified types are meaningful only for expressions that are lvalues.

If the same qualifier appears more than once in the same specifier-qualifier-list, either directly or via one or more typedefs, the behavior is the same as if it appeared only once. If other qualifiers appear along with the _Atomic qualifier in a specifier-qualifier-list, the resulting type is the so-qualified atomic type.

The keyword _Atomic is used, alone, as a type qualifier. An implementation is allowed to relax the requirement of having the same representation and alignment of the corresponding non-atomic type, as long as appropriate conversions are made, including via the cast operator.

Yes. There is a difference. When it is used as type specifier then standard restrict it as (6.7.2.4 p(3)):

The type name in an atomic type specifier shall not refer to an array type, a function type, an atomic type, or a qualified type .

For example

typedef int arr[5];  

arr can be a type name when _Atomic is used as qualifier but can't be used as type name if _Atomic is used as type specifier (like _Atomic (arr) )

After many attempts, I have found why this is needed: pointers !

Let's suppose you have:

int foo = 1;
int bar = 2;
int *p = &foo;

Picture that as memory locations, first two holding an int, the last one holding a pointer to the first int. _Atomic makes it so that those memory locations are suited for atomic operations.

For reasons that concern your program, you might want:

  • foo to be atomic, so that you can, for example, atomically change foo's value to be 2 instead of 1.
  • p to be atomic, so that you can, for example, change atomically what p is pointing to, and point to bar instead of foo.

In the first case, to make foo atomic is easy, there is no ambiguity when reading it:

_Atomic int foo;
atomic_store_explicit(&foo , 2, memory_order_release); /* valid atomic op. */

But now you want to make p atomic, if you write:

_Atomic int *p;

... that is not what you want!

That is, as explained above, a non atomic pointer to an atomic int. Strictly speaking, there is no guarantee that this pointer will be correctly aligned to be able to do atomic operations on it (although you'll have hard time to force a compiler to misalign a pointer!). This means that, if you managed to make the pointer misaligned, the atomic operations on it will have a chance to fail. What you want is, on the other hand, an atomic pointer to a int that is non necessary atomic.

So you have to write:

int bar = 2;
_Atomic (int *) p;
atomic_store(&p , &bar); /* now valid atomic operation */

Now you have your atomic pointer!

Note that for the very simple case of making the foo int atomic, you could also have written, any of these 3 declarations, the last one uses the convenience typedef defined in stdatomic.h:

typedef _Atomic int atomic_int;

_Atomic int foo;
_Atomic (int) foo;
atomic_int foo;

I made it "easy to understand" with an int and a pointer to and int, but when you have to deal with

_Atomic (struct foobar *) *q;

You will now know that q itself is not an atomic pointer, but it points to an atomic pointer to a foobar struct!

And so the demonstration:

#include <stdatomic.h>
void test()
{
    _Atomic int foo = 1;         /*   Atomic   */
    _Atomic int *pf = &foo;      /* Non Atomic */
    _Atomic int **ppf = &pf;     /* Non Atomic */
    int bar = 2;                 /* Non Atomic */
    _Atomic (int *) pb = &bar;   /*   Atomic   */
    _Atomic (int *) *ppb = &pb;  /* Non Atomic */

    int *res;

    res = atomic_load(ppf); /* Not OK, yields a warning */
    res = atomic_load(ppb); /* This is correct */
}

In function 'test':

test.c:13:6: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]

res = atomic_load(ppf);

Indeed, the first atomic_load tries to return a non atomic pointer to an int: the int pointed to is atomic, not the pointer. It could also fail, because there is no guarantee that &pf (the content of ppf) is properly aligned for an atomic operation (although practically here it is, you would have to cast pf to a misaligned int to make it fail).

The second atomic_load correctly works with an atomic pointer and returns it to 'res'.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM