Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do all the member functions in std::atomic appear both with and without volatile?

I noticed that most member functions of std::atomic<T> types are declared twice, once with the volatile modifier and once without (example)). I checked the source code of the G++ standard library implementation and I found all of them to be exact duplicates, for example,

bool
load(memory_order __m = memory_order_seq_cst) const noexcept
{ return _M_base.load(__m); }

bool
load(memory_order __m = memory_order_seq_cst) const volatile noexcept
{ return _M_base.load(__m); }

I could not find any example where the volatile variant behaves differently than the non-volatile one, differs in return type or anything of that sort.

Why is that? I thought a volatile member function could also be called in objects which are not volatile. So declaring and defining std::atomic::load(...) const volatile noexcept etc. should be enough.

Update:

Based on the comments my question basically boils down to: Can you provide an example where some calls using a non-volatile instance (not necessarily of std::atomic) would generate a different assembly in the two following cases,

  1. every member function appears with the same body with and without volatile,

  2. only the volatile variant exists?

This, assuming the compiler can do whatever optimization the standard allows it to (or simply highest optimization levels).

like image 643
The Vee Avatar asked Jul 24 '18 08:07

The Vee


People also ask

Does STD Atomic need volatile?

Atomic variable examples In order to use atomicity in your program, use the template argument std::atomic on the attributes. Note, that you can't make your whole class atomic, just it's attributes. }; You don't need to use volatile along with std::atomic .

Can a function be volatile in C++?

Constant and volatile member functions (C++ only) A nonconstant member function can only be called for a nonconstant object. Similarly, a member function declared with the volatile qualifier can be called for volatile and nonvolatile objects. A nonvolatile member function can only be called for a nonvolatile object.


1 Answers

Probably it all stems from what volatile is, for that see this answer. As the use-cases are quite slim compare to usual application development, that is why nobody usually cares. I will assume that you do not have any practical scenario where you wondering if you should apply those volatile overloadings. Then I will try to come up with an example where you may need those (do not judge it to be too real).

volatile std::sig_atomic_t status = ~SIGINT;
std::atomic<int> shareable(100);

void signal_handler(int signal)
{
  status = signal;
}

// thread 1
  auto old = std::signal(SIGINT, signal_handler);
  std::raise(SIGINT);
  int s = status;
  shareable.store(10, std::memory_order_relaxed);
  std::signal(SIGINT, old);

// thread 2
  int i = shareable.load(std::memory_order_relaxed);

memory_order_relaxed guarantees atomicity and modification order consistency, no side-effects. volatile cannot be reordered with side-effects. Then here we are, in thread 2 you can get shareable equal 10, but status is still not SIGINT. However, if you set type qualifier to volatile of shareable that must be guaranteed. For that you will need the member methods be volatile-qualified.

Why would you do something like this at all? One case, I might think of, is you have some old code that is using old volatile-based stuff and you cannot modify it for one or another reason. Hard to imagine, but I guess that there might be a need to have some kind of guaranteed order between atomic and volatile inline assembly. The bottom line, IMHO, is that whenever it is possible you can use new atomic library instead of volatile objects, in the case there are some volatile objects, that you cannot rid of, and you want to use atomic objects, then you might need volatile qualifier for the atomic objects to have proper ordering guarantees, for that you would need overloading.

UPDATE

But if all I wanted was to have atomic types usable as both volatile and non-volatile, why not just implement the former?

struct Foo {
    int k;
};

template <typename T>
struct Atomic {
  void store(T desired) volatile { t = desired; }
  T t;
};

int main(int i, char** argv) {
    //error: no viable overloaded '='
    // void store(T desired) volatile { t = desired; }
  Atomic<Foo>().store(Foo());
  return 0;
}

The same would be with load and other operations, because those are usually not trivial implementations that require copy operator and/or copy constructor (that also can volatile or non-volatile).

like image 107
Yuki Avatar answered Oct 27 '22 15:10

Yuki