According to cppreference, most uses of the volatile keyword are to be deprecated in C++20. What is the disadvantage of volatile? And what is the alternative solution when not using volatile?
There's a good talk by the C++ committee language evolution chair on why.
Brief summary, the places that volatile is being removed from didn't have any well defined meaning in the standard and just caused confusion.
Motivating (Ambiguous) Examples
Volatile bit Fields should be specified by your hardware manual and/or compiler.
Is += a single/atomic instruction? How about ++?
How many reads/writes are needed for compare_exchange? What if it fails?
What does void foo(int volatile n) mean? or int volatile foo()?
Should *vp; do a load? (This has changed twice in the standard.)
Threading
Historically, people have used volatile to achieve thread safety in C and C++. In C++11, non-UB ways to create synchronization and shared state between threads were added. I recommend Back to Basics: Concurrency as a good introduction.
Related
I'm trying to safely zero a std::array in a class destructor. From safely, I mean I want to be sure that compiler never optimize this zeroing. Here is what I came with:
template<size_t SZ>
struct Buf {
~Buf() {
auto ptr = static_cast<volatile uint8_t*>(buf_.data());
std::fill(ptr, ptr + buf_.size(), 0);
}
std::array<uint8_t, SZ> buf_{};
};
is this code working as expected? Will that volatile keyword prevent optimizing by compiler in any case?
The C++ standard itself doesn't make explicit guarantees. It says:
[dcl.type.cv]
The semantics of an access through a volatile glvalue are implementation-defined. ...
[Note 5: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation.
Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object.
See [intro.execution] for detailed semantics.
In general, the semantics of volatile are intended to be the same in C++ as they are in C.
— end note]
Despite the lack of guarantees by the C++ standard, over-writing the memory through a pointer to volatile is one way that some crypto libraries clear memory - at least as a fallback when system specific function isn't available.
P.S. I recommend using const_cast instead, in order to avoid accidentally casting to a different type rather than differently qualified same type:
auto ptr = const_cast<volatile std::uint8_t*>(buf_.data());
Implicit conversion also works:
volatile std::uint8_t* ptr = buf_.data();
System specific functions for this purpose are SecureZeroMemory in windows and explicit_bzero in some BSDs and glibc.
The C11 standard has an optional function memset_s for this purpose and it may be available for you in C++ too but isn't of course guaranteed to be available.
There is a proposal P1315 to introduce similar function to the C++ standard.
Note that secure erasure is not the only consideration that has to be taken to minimise possibility of leaking sensitive data. For example, operating system may swap the memory onto permanent storage unless instructed to not do so. There's no standard way to make such instruction in C++. There's mlock in POSIX and VirtualLock in windows.
I have only recently started exploring C++ Eigen library and a little puzzled with some of the documentation. It would be great if someone can clarify this.
In the common pitfalls (https://eigen.tuxfamily.org/dox-devel/TopicPitfalls.html) Alignment Issues section, it says " Indeed, since C++17, C++ does not have quite good enough support for explicit data alignment.".
The page on how to get rid of alignment issues (https://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html#getrid), the documentation says, "If you can target [c++17] only with a recent compiler (e.g., GCC>=7, clang>=5, MSVC>=19.12), then you're lucky: enabling c++17 should be enough" .
So is alignment not an issue with Eigen Matrix if I am using c++ 17 with gcc>=7.0? Have I understood this right? And that the macro EIGEN_MAKE_ALIGNED_OPERATOR_NEW won't be needed? And if this is correct, what is different between c++14/c++17 which takes care of the alignment issues?
The second question is regarding the pass-by-value section (https://eigen.tuxfamily.org/dox-devel/group__TopicPassingByValue.html). The documentation claims that pass-by-value could be illegal and could crash the program. This is very puzzling to me. Wouldn't pass-by-value just invoke a copy constructor? As an example.
Eigen::Vector3f veca = ComputeVecA();
Eigen::Vector3f vecb = veca; //< If pass-by-value is unsafe, is this operation safe?
And lastly, can I rely on RVO/NRVO for Eigen fixed sized matrix class? I suspect the answer to this is yes.
In the common pitfalls
(https://eigen.tuxfamily.org/dox-devel/TopicPitfalls.html) Alignment
Issues section, it says "Indeed, since C++17, C++ does not have quite
good enough support for explicit data alignment."
This seems to be a typo. It should say "until C++17" instead of "since C++17" because C++17 actually added support for allocation with extraordinary alignment restrictions. Two comments agree with me.
The page on how to get rid of alignment issues
(https://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html#getrid),
the documentation says, "If you can target [C++17] only with a recent
compiler (e.g., GCC >= 7, Clang >= 5, MSVC >= 19.12), then you're
lucky: enabling C++17 should be enough."
So is alignment not an issue with Eigen Matrix if I am using C++17
with gcc >= 7.0? Have I understood this right? And that the macro
EIGEN_MAKE_ALIGNED_OPERATOR_NEW won't be needed?
Yes.
And if this is correct, what is different between C++14/C++17 which
takes care of the alignment issues?
C++17 supports Dynamic memory allocation for over-aligned data. operator new now properly allocates over-aligned memory with the align_val_t argument.
The second question is regarding the pass-by-value section
(https://eigen.tuxfamily.org/dox-devel/group__TopicPassingByValue.html).
The documentation claims that pass-by-value could be illegal and could
crash the program. This is very puzzling to me. Wouldn't pass-by-value
just invoke a copy constructor?
If the variable is a local variable (as vecb in your example), then the compiler and the library take care to ensure that vecb meets the special alignment restriction required by Eigen. However, if the variable is a function parameter, this alignment restriction is not respected, meaning the program may operate on ill-aligned memory, thus crash. (This has little to do with the copy constructor.)
And lastly, can I rely on RVO/NRVO for Eigen fixed sized matrix class?
I suspect the answer to this is yes.
The answer is pretty much the same for Eigen classes and other classes: try and see. Usually the answer is yes.
Q1: as already commented, this was a typo when updating this paragraph for c++17. This is already fixed.
Q2: I don't remember all the details about this one but it is related to two technical issues.
Some compilers failed to properly align the stack, in this case it is hopeless to get aligned function parameters.
Old ABI specifications did not allowed overalignment of function parameters.
I would expect that since c++11 and the usage of the standardized alignas keyword this is not an issue anymore, but maybe this is still a problem on some exotic compiler-OS combinations.
Q3: there is nothing preventing RVO/NRVO, and from my experience when it can apply it does apply.
After having read this wonderful article, I starting digging around to volatile-correct some code. One consequence of volatile-correctness (as I understand it) is that methods accessed from different threads should be volatile qualified.
A simple example might be a mutex using Win32 or pthreads. We could make a class Mutex and give it a field:
#if defined BACKEND_WIN32
CRITICAL_SECTION _lock;
#elif defined BACKEND_POSIX
pthread_mutex_t _lock;
#endif
The ".acquire()" method might look like:
void Mutex::acquire(void) volatile {
#if defined BACKEND_WIN32
EnterCriticalSection(&_lock);
#elif defined BACKEND_POSIX
pthread_mutex_lock(&_lock);
#endif
}
Trying this doesn't work. From MSVC:
error C2664: 'void EnterCriticalSection(LPCRITICAL_SECTION)' : cannot convert argument 1 from 'volatile CRITICAL_SECTION *' to 'LPCRITICAL_SECTION'
From g++:
error: invalid conversion from ‘volatile pthread_mutex_t*’ to ‘pthread_mutex_t*’ [-fpermissive]
You'll get similar problems trying to unlock _lock. Two questions:
My impression is that these are just artifacts of the APIs being outdated. Is this in fact the case? Did I misunderstand something?
The whole purpose of a lock or unlock API function is to switch between being in a critical section, therefore, is it the case that I should just use the inadequately named const_cast to cast away the volatile-ness of _lock before passing to these functions, with no ill effect?
I think the best summary of what the standard (now) intends by volatile is to be found in n3797 S7.1.6.1 /6/7
What constitutes an access to an object that has volatile-qualified type is implementation-defined. If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.
[ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. —end note ]
Sadly, this means that a standards-complying implementation is not required to do as much as that article seems to suggest. There is no obligation to store values promptly and a limited obligation to reload values that might have changed. The compiler is not free to elide code that loads a value that it has not stored, but it is free to elide code that stores a value that it never uses.
As noted in the comments, it's better to use implementation-defined APIs or atomic or a mutex. Volatile will let you down. Stroustrup says as much in the linked question.
On the heels of a specific problem, a self-answer and comments to it, I'd like to understand if it is a proper solution, workaround/hack or just plain wrong.
Specifically, I rewrote code:
T x = ...;
if (*reinterpret_cast <int*> (&x) == 0)
...
As:
T x = ...;
if (*reinterpret_cast <volatile int*> (&x) == 0)
...
with a volatile qualifier to the pointer.
Let's just assume that treating T as int in my situation makes sense. Does this accessing through a volatile reference solve pointer aliasing problem?
For a reference, from specification:
[ Note: volatile is a hint to the implementation to avoid aggressive
optimization involving the object because the value of the object might
be changed by means undetectable by an implementation. See 1.9 for
detailed semantics. In general, the semantics of volatile are intended
to be the same in C++ as they are in C. — end note ]
EDIT:
The above code did solve my problem at least on GCC 4.5.
Volatile can't help you avoid undefined behaviour here. So, if it works for you with GCC it's luck.
Let's assume T is a POD. Then, the proper way to do this is
T x = …;
int i;
memcpy(&i,&x,sizeof i);
if (i==0)
…
There! No strict aliasing problem and no memory alignment problem. GCC even handles memcpy as an intrinsic function (no function call is inserted in this case).
Volatile can't help you avoid undefined behaviour here.
Well, anything regarding volatile is somewhat unclear in the standard. I mostly agreed with your answer, but now I would like to slightly disagree.
In order to understand what volatile means, the standard is not clear for most people, notably some compiler writers. It is better to think:
when using volatile (and only when), C/C++ is pretty much high level assembly.
When writing to a volatile lvalue, the compiler will issue a STORE, or multiple STORE if one is not enough (volatile does not imply atomic).
When writing to a volatile lvalue, the compiler will issue a LOAD, or multiple LOAD if one is not enough.
Of course, where there is no explicit LOAD or STORE, the compiler will just issue instructions which imply a LOAD or STORE.
sellibitze gave the best solution: use memcpy for bit reinterpretations.
But if all accesses to a memory region are done with volatile lvalues, it is perfectly clear that the strict aliasing rules do not apply. This is the answer to your question.
From what I've read from Herb Sutter and others you would think that volatile and concurrent programming were completely orthogonal concepts, at least as far as C/C++ are concerned.
However, in GCC implementation all of std::atomic's member functions have the volatile qualifier. The same is true in Anthony Williams's implementation of std::atomic.
So what's deal, do my atomic<> variables need be volatile or not?
To summarize what others have correctly written:
C/C++ volatile is for hardware access and interrupts. C++11 atomic<> is for inter-thread communication (e.g., in lock-free code). Those two concepts/uses are orthogonal, but they have overlapping requirements and that is why people have often confused the two.
The reason that atomic<> has volatile-qualified functions is the same reason it has const-qualified functions, because it's possible in principle for an object be both atomic<> and also const and/or volatile.
Of course, as my article pointed out, a further source of confusion is that C/C++ volatile isn't the same as C#/Java volatile (the latter is basically equivalent to C++11 atomic<>).
Why is the volatile qualifier used throughout std::atomic?
So that volatile objects can also be atomic. See here:
The relevant quote is
The functions and operations are defined to work with volatile objects, so that variables that should be volatile can also be atomic. The volatile qualifier, however, is not required for atomicity.
Do my atomic<> variables need to be volatile or not?
No, atomic objects don't have to be volatile.
As const, volatile is transitive. If you declare a method as volatile then you cannot call any non-volatile method on it or any of its member attributes. By having std::atomic methods volatile you allow calls from volatile member methods in classes that contain the std::atomic variables.
I am not having a good day... so confusing... maybe a little example helps:
struct element {
void op1() volatile;
void op2();
};
struct container {
void foo() volatile {
e.op1(); // correct
//e.op2(); // compile time error
}
element e;
};