Dereference a pointer to volatile structure in C++
Asked Answered
E

2

14

I have a pointer to some volatile memory that I am trying to dereference and copy to a unqualified copy of that structure (and vise-versa). The format of the memory is specified by a third party structure definition. In C, I can dereference the memory and all is well. However, C++ complains that it can't find a suitable assignment operator (clang's error is much clearer than gcc here: candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'volatile vec', but method is not marked volatile). This method works great for simple types (e.g. ints) but fails for structures.

In this particular case, the values being pointed to cannot change during the read of the object, but I must prevent them from being cached from read to read.

I found Can I add an implicit conversion from a volatile T to a T? which gave me a bunch of options to try, however I was unable to find the magic which will let this actually work. I suspect that all of these solutions would fail due to Why cannot a non-member function be used for overloading the assignment operator? which will cause my third-party-defined structure to not have a suitable since the structure definition is third-part, but that is theoretical since I can't even get manually specified member functions to fully work (maybe I could create a wrapper structure with something explicit?).

The example below compiles and works in C, but fails in C++. Unfortunately there are other aspects of the fuller problem which force C++. What is the magic to let this work?

    #include <stdio.h>
    #include <stdint.h>
    struct __attribute__((__packed__)) vec {
      uint32_t addr;
      uint32_t len:24;
      uint8_t id;
    };
    struct vec first = { .addr=0xfeedface, .len=10, .id=5};
    volatile struct vec vvector;
    int main(int argc, char **argv, char **envp)
    {
      struct vec cvector;
      volatile struct vec *pvector = &vvector;
      *pvector = first;
      cvector = *pvector;
      printf("%x %d %d %d\n", cvector.addr, cvector.len, cvector.id, sizeof(cvector));
    }

Trying to hide the magic in extern C (my translation of a comment suggestion) didn't seem to work--still complained about discarded qualifiers and ambiguous overloads. Still works fine when using C.

    extern "C" {
      static void vucopy(volatile struct vec *lhs, struct vec *rhs) { *lhs = *rhs; }
      static void uvcopy(struct vec *lhs, volatile struct vec *rhs) { *lhs = *rhs; }
    }
    ...
  vucopy(pvector, &first);
  uvcopy(&cvector, pvector);
Extranuclear answered 6/6, 2023 at 0:10 Comment(14)
Any chance you can hide this all behind a C wrapper you can call from C++?Vestment
@Vestment Yes, if I can do it inside a C++ source file (libraries don't exist and I probably cannot mix C and C++ at the source file level--but I will check). I tried adding an extern C but g++ still complained.Extranuclear
We shouldn’t expect extern "C" to do anything here: it affects the mangling of names and (officially) the calling convention of functions, but it doesn’t mean “the following is C”.Turbofan
Since it's a POD structure, you should be able to get away with std::memcpy().Nerta
Many uses of volatile are deprecated in C++. The semantics of volatile struct and volatile bit fields is unclear. Perhaps you should think of an alternative approach.Melodymeloid
create a minimal example to demonstrate the problem. @n.m. the standard is clear what volatile is for. now you made me wonder - what is depricated?Spotless
@n.m.: If this were a standard program, it would be a device driver where there is external hardware changing stuff. As it happens, it is for an FPGA (which is why I don't have libraries or other sorts of nice features) where there is external IP changing stuff. In any case, I have a pointer to data that can change behind my back, so I need to tell the compiler not to reorder loads, cache results, or otherwise optimize the memory accesses away. Standard volatile usage.Extranuclear
@SethRobertson so, you compiled that example using a c++ compiler? this is what was not clear to meSpotless
@BЈовић: Yes. g++, clang++, etc. Eventually it will be compiled by a proprietary C++ compiler, but this was really a generic question. If compiled as a C program, it works as is.Extranuclear
@SethRobertson Why not just write a simple C file that accesses the volatile structure via C functions, and call those C functions from your C++ code?Ovoid
@AndrewHenlez: I mentioned this in a comment response to someone else (might have been hidden for you). I don't have libraries and I probably cannot tell the compilation system to use different compilers for different source files--this isn't a nice flexible environment. Certainly it is not obvious that I can do so.Extranuclear
If you have two 32-bit hardware registers, have two uint32_t volatile variables. If you have a single 64-bit hardware register, have a single uint64_t volatile variable. But not a struct and not a bit field.Melodymeloid
@BЈовић timsong-cpp.github.io/cppwp/n4861/depr.volatile.type also read the proposal open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1152r4.html and its first version open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html which was quite a bit more radical, and not without reason.Melodymeloid
@n.m. thank you for the links. I read both papers and I agree with most things. For volatile, you just need 2 functions: 1 to read and 1 to write anyway.Spotless
R
2

volatile needs atomic operation. Default operator= can't respect that.

If data are not changed while copy, of course, you may use memcpy or copy field by field. But volatile keyword says that data may change while copy.

If you work on a 64 bit system, you could use uint64_t. But in your example, it will work only because sizeof(vec) == 8.

For example :

#include <stdint.h>
#include <stdio.h>

struct __attribute__((__packed__)) vec {
    uint32_t addr;
    uint32_t len : 24;
    uint8_t id;
};

struct vec first = {.addr = 0xfeedface, .len = 10, .id = 5};
volatile struct vec vvector;

int main(int argc, char **argv, char **envp) {
    struct vec cvector;
    volatile uint64_t *pvector =
        reinterpret_cast<volatile uint64_t *>(&vvector);

    *pvector = *reinterpret_cast<uint64_t *>(&first);

    *reinterpret_cast<uint64_t *>(&cvector) = *pvector;

    printf("%x %d %d %zu\n", cvector.addr, cvector.len, cvector.id,
           sizeof(cvector));
}
Rivy answered 6/6, 2023 at 10:44 Comment(8)
I don't think you can use memcpy with volatile typesSpotless
@BЈовић that's what they're sayingTramway
This is a pretty neat hack. I converted the technique to use a union which worked slightly better for me. It might even work for my weird use case for larger data types since I can create arbitrary sized integers. Definitely a candidate for accept.Extranuclear
@Tramway the answer says that memcpy could be used, but trying to memcpy a volatile object would fail the compilationSpotless
This code is a strict aliasing violation while also assuming a randomly-aligned uint64_t access provides an atomic operation.Ovoid
You're right, it will not work on a not aligned struct.Rivy
The union alternative I mentioned fixes both the aliasing violation and the alignment issue, AFAIK. union foo { struct vec s; uint64_t w; }; then I can pvector->w = first.w and cvector.w = pvector->wExtranuclear
@SethRobertson No, union does not solve aliasing violation. In C++ (unlike C), it is undefined behavior to assign one member of a union and read another.Digitalis
A
0

You need to define the appropriate assignment operator overloads:

vec& operator=(volatile vec&);
vec& operator=(vec&) volatile;

(Working example in godbolt: https://godbolt.org/z/jnexj4fMh)

See also Can't assign an object to a volatile object

If you want the same code to work in C and C++, the easiest way is to copy the individual POD members of struct vec instead of the whole struct.

Apologetics answered 6/6, 2023 at 7:53 Comment(3)
that is just a declaration of assignment operators, and the example fails to build cause of that, but i think it would work. Of course, it would be better to add a function, instead of using assignment operatorSpotless
I too would appreciate it if you could provide an example implementation of your two functions, I couldn't come up with a working example. I'm also suffering from the problem that I cannot modify the third party structure definition, but...maybe I could create a wrapper structure which does this?Extranuclear
@SethRobertson Sorry, I missed the part about the "third parts structure". In this case it is easiest to just create a function that copies the individual POD members.Apologetics

© 2022 - 2024 — McMap. All rights reserved.