2

Sorry if my doubt is too naive. But I have a difficulty in typecasting std::atomic to char* type. Is casting from std::atomic to char is valid?

Can I write to such type casted variable. I am sure that there will be no multithreaded reads/writes while a thread tries to write into the variable.(I understand that, there is no need for using atomics when there will be no concurrent access on this variable)

std::atomic<uint8_t>* data_;
char *data = reinterpret_cast<char*>(data_);
*data |= mask;

Is it safe to do?

EDIT: I am not sure if its worth mentioning. In my code,

char *raw;
// variable raw is allocated
std::atomic<uint8_t>* data_ = reinterpret_cast<std::atomic<uint8_t>*>(raw);

The above is how the std::atomic< uint8_t> is created (as char and type casted to std::atomic type).

Thanks :)

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • The answer you linked does not mention `std::atomic` at all. You can't even cast between `std::atomic*` and `uint8_t*`. – molbdnilo Jun 24 '19 at 08:00
  • 1
    @molbdnilo But we can `reinterpret_cast` a pointer to an object of any type into `char*`, right? – Daniel Langr Jun 24 '19 at 08:02
  • 1
    @DanielLangr You can cast, but that doesn't mean that you can do anything meaningful with the result. – molbdnilo Jun 24 '19 at 08:04
  • @molbdnilo Yes u re right. That link was a wrong reference i was looking for. Removed it – krithikaGopalakrishnan Jun 24 '19 at 08:18
  • 1
    The link in a previous version of your question shows that it is safe to write to `std::uint8_t`. However `std::atomic` is not a `std::uint8_t`. It is a very different thing with potentially different layout and alignment. – Martin Bonner supports Monica Jun 24 '19 at 08:19
  • 3
    Maybe, you should describe the problem you are trying to solve by these constructs than to ask about your solution (XY). – Daniel Langr Jun 24 '19 at 08:34
  • @MartinBonner In practice if the representation of `std::atomic` is not the same as `std:uint8_t`, you probably should use a different tool. – curiousguy Jun 26 '19 at 15:17
  • @curiousguy Not so! On many platforms it is only possible to operate atomically on an aligned full-word. So a `std::atomic` will have some wasted bits. Depending on where the wasted bits are, the cast the OP asks about may well write to them rather than to the actual value. Further, the edit shows how he obtains the atomic in the first place - and it is quite likely not to be suitably aligned. – Martin Bonner supports Monica Jun 26 '19 at 15:34
  • @MartinBonner On which platforms? – curiousguy Jun 26 '19 at 15:53
  • @curiousguy Well LDREX and STREX were introduced in ARMv6T2, and the \*B versions didn't appear until ARMv6K. – Martin Bonner supports Monica Jun 26 '19 at 16:01
  • 1
    @MartinBonner: pure load and pure store of a byte are still atomic, and you can still implement a byte atomic RMW using word LL/SC by just merging the change in the appropriate word. The commit of the whole word only succeeds if the cache line hasn't been touched by other threads, so it's safe. But yes, it might be preferable to pad the size of an `atomic` to the CPU's native atomic-RMW size and save an instruction or two inside the LL/SC retry loop. – Peter Cordes Jun 27 '19 at 07:31
  • 1
    But really, IDK why the OP would ever want this. If you want a non-atomic RMW for performance, do a separate atomic load and store. If you have parts of your program that don't need atomicity for the object, use C++20 [`std::atomic_ref foo(myvar)`](https://en.cppreference.com/w/cpp/atomic/atomic_ref) for the parts that do need to be atomic. (But beware of 8-byte objects on 32-bit platforms that are under-aligned for double-word CAS; naive atomic_ref implementations will not actually be atomic in that case) – Peter Cordes Jun 27 '19 at 07:50
  • @MartinBonner: GCC for ARM chooses to make `atomic` lock-free and 1 byte, but using a helper function instead of inlining a word LL/SC retry loop. https://godbolt.org/z/9J-xFy. Notice that even with `gcc -march=armv6` (not k) where we get a `bl __sync_fetch_and_or_1`, it still returns `1` for `return data_.is_lock_free();`. The gotcha you propose is certainly a possible implementation choice, but it isn't present in real ABIs on ARM. – Peter Cordes Jun 27 '19 at 08:03

1 Answers1

5

I don't think it's safe at all. The C++ Standard does not guarantee std::atomic<uint8_t> to be lock-free. If it is not, then there might be, e.g., a mutex member variable stored in every std::atomic<uint8_t> object starting at its first byte. In such a case, your command *data |= mask; would manipulate a bit of this mutex, which might completely break its internal implementation.


This might be also a relevant question: Is it possible to get the address of the underlying storage for an atomic_int?.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • 3
    @krithikaGopalakrisnan It's even worse now. While you can `reinterpret_cast` a pointer to an object of any type into `char*`, the opposite is not true. – Daniel Langr Jun 24 '19 at 08:33