Recasting pointers - finally worked out IDE 1.8.9

As the compiler has advanced I have struggled with a particular problem. I have a data structure for a weather station including DateTime components. I want to write a compact form of the data to external EEPROM and to do so 'view' the structure as an array of 32 bytes. However C++ wont allow me to make a pointer to my data structure become a pointer to bytes.

Unions are not an option as [I don't think] you can have unions of structures to bytes.

I discovered the work around - code snippets below.

/*
 * Snippet
 * To 'remember how to consider a block of data (struct or object?)
 * as being a block of bytes
 * 
 * April 2019
 * 
 */

 // Define my structure

struct data                 // the structure needs to be named [aka dada] so that it can be pointed to
{
 uint16_t pageCount;        //  2 bytes expected on page boundary 0x0nn0 where nn is even 00 to 7e
 DateTime dt[2];            // 14 bytes expected
 uint16_t pressure[2];      //  4 bytes expected
 uint16_t RH[2];            //  4 bytes expected
 int16_t RHtemperature[2];  //  4 bytes expected
 int16_t temperature[2];    //  4 bytes expected
} paige;                    // 32 bytes expected

The exact particulars of my struct are not important - they are included to show the origin of my problem. First step is to get a byte / uint8_t pointer to the structure.

// in code use pointers ...
uint8_t* ptr = reinterpret_cast<uint8_t*>(&paige);

I have learnt that the "reinterpret_cast<uint8_t*>(&paige)" has to be written this way. &paige is simply the address my chosen variable 'paige'. The unit8_t at the beginning (defining the pointer) has to be the same type as appearing in the <>.

I can then scan the data as if it were an array of uint8_t. But beware - I know there are only 32 bytes and what they represent. The compiler and my program have no idea so it is all my responsibility to keep exactly within my data.

When I read the data back to a uint8_t array I need to get to look at the data as if it was a structure. So here is a line of code to get a pointer to the array as if it is a structure.

// and back again
data* ptrD = reinterpret_cast<data*>(ptr);

Elements of the data can be recovered by

x=ptrD->pressure[0];

For a big Mac / Linux / Windows program I understand why C++ standards view this as a highly dangerous recasting. But with so few bytes of RAM this trick is vital. It also saves using memcpy functions.

I hope it is of use.

what would you do if you dump in EEPROM the actual memory of a DateTime instance ? you won't instantiate back from EEPROM, would you? so either you need to subclass DateTime so that it knows how to serialize itself / write to a stream somewhere and then know how to read from a stream to reinitializes its state, or you need to do the work yourself. What matters in a dateTime is the time it represents. So get a uint32_t variable, store in it the DateTime unix value and that's what you dump. When you read from memory you know you have a unix time and you use it to instantiate a DateTime.

The EEPROM get() and put() functions just do a straight cast to the uint8_t* type. Did this not work for you?

    //Functionality to 'get' and 'put' objects to and from EEPROM.
    template< typename T > T &get( int idx, T &t ){
        EEPtr e = idx;
        uint8_t *ptr = (uint8_t*) &t;
        for( int count = sizeof(T) ; count ; --count, ++e )  *ptr++ = *e;
        return t;
    }
    
    template< typename T > const T &put( int idx, const T &t ){
        EEPtr e = idx;
        const uint8_t *ptr = (const uint8_t*) &t;
        for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );
        return t;
    }

Thank you for your help and suggestions.

Jiggy-Ninja I'm probably demonstrating my ignorance but I'm not writing to the Arduino's internal EEPROM so I am missing the understanding of why EEPROM get() and put() would help me. When I get some free time I'll try the template approach. What type is EEPtr?

J-M-L dumping the DateTime to external-EEPROM is exactly what I want to do; and also bring it back and reconstitute it as the DateTime. Why? Because I want to plot the data as a history graph so the 'old' timestamp is important.

But focusing on DateTime is to conflate the matter with getting a uint8_t pointer to e.g. a structure. Clearly, I should have found better ways to express my post. With the changing permissives in GNU compiler foe C++ simply coding

uint8_t * ptr = (uint8_t) &somestructure; is no longer acceptable to C++.

What is acceptable to the newer constraints of C++ is reinterpret_cast<uint8_t>(&somestructure);

In my particular instance my structure occupies 32 bytes which is very handy for writing a single page to external EEPROM. That opens the door to fast write time (only one delay for 32 bytes) with only a single write cycle for the whole page; thus protecting the number of write cycles these devices have.

Thank you for your constructive comments and I'm sorry if I'm a slow learner. :slight_smile:

RealSaturdayScience:
Jiggy-Ninja I'm probably demonstrating my ignorance but I'm not writing to the Arduino's internal EEPROM so I am missing the understanding of why EEPROM get() and put() would help me. When I get some free time I'll try the template approach. What type is EEPtr?

I specifically highlighted that those function use the old C-style cast to change th address to a pointer value. It's the second line in each function.

uint8_t * ptr = (uint8_t) &somestructure; is no longer acceptable to C++.

The code below compiles perfectly fine, and the only warning I get is about the variables being unused. The problem with your snippet is because you're casting the pointer to a uint8_t instead of a uint8_t*. You're casting a pointer to an integer value (which is valid, but will give you a warning), then assigning that integer to another pointer (which is also valid, but will give you a second warning).

struct whatever
{
  bool yo;
  int fine;
  unsigned long long bigly;
};

void setup() {
  // put your setup code here, to run once:
  whatever you_want;
  uint8_t* pnt = (uint8_t*)&you_want;
}

void loop() {
  // put your main code here, to run repeatedly:

}

RealSaturdayScience:
As the compiler has advanced I have struggled with a particular problem.

I'm not sure what this all have to do with "compiler's advancing". Everything you stated in your post has been like that since the beginning of times.

RealSaturdayScience:
I have a data structure for a weather station including DateTime components. I want to write a compact form of the data to external EEPROM and to do so 'view' the structure as an array of 32 bytes. However C++ wont allow me to make a pointer to my data structure become a pointer to bytes.

It does allow you to make that pointer. You just need an explicit cast. You need it in both C and C++. What you used - reinterpret_cast - is actually an example of an explicit cast. A "classic" C-style cast (uint8_t*) is also an example of an explicit cast. Either will work perfectly fine.

RealSaturdayScience:
Unions are not an option as [I don't think] you can have unions of structures to bytes.

Yes, you can. But when it comes to bytes, the approach with an explicit cast is a more straightforward one.

RealSaturdayScience:
I have learnt that the "reinterpret_cast<uint8_t*>(&paige)" has to be written this way. &paige is simply the address my chosen variable 'paige'. The unit8_t at the beginning (defining the pointer) has to be the same type as appearing in the <>.

Or you can simply use a C-style cast:

uint8_t* ptr = (uint8_t*) &paige;

which could be essential in situations when you need cross-compilable C-C++ code. Note that in this case "uint8_t defining the pointer" also has to be the same type as appearing in the (), exactly as in your code. Nothing new here.

RealSaturdayScience:
But focusing on DateTime is to conflate the matter with getting a uint8_t pointer to e.g. a structure. Clearly, I should have found better ways to express my post. With the changing permissives in GNU compiler foe C++ simply coding

uint8_t * ptr = (uint8_t) &somestructure; is no longer acceptable to C++.

Um... It is completely unclear what you are talking about here. What "changing permissives" are you referring to?

Firstly, your example has never been acceptable in C++. It is simply incorrect. The proper syntax is (and has always been)

uint8_t *ptr = (uint8_t *) &somestructure;

Note the * inside the (), just like in your reinterpret_cast version.

Secondly, there is nothing "unacceptable" about C-style casts in C++, besides purely cosmetic/stylistic matters. And there were no changes about it in GCC, or in C++ in general. So, it is not clear what "no longer" you are referring to. What do you mean by "changing permissives in GNU compiler for C++"?

Montmorency:
Secondly, there is nothing "unacceptable" about C-style casts in C++, besides purely cosmetic/stylistic matters. And there were no changes about it in GCC, or in C++ in general. So, it is not clear what "no longer" you are referring to. What do you mean by "changing permissives in GNU compiler for C++"?

C-Style casting is totally acceptable, I agree. However, the C cast will just try the different C++ casts until one cast can be performed. I always find it better to be a bit more explicit with your casts, especially since a C-Style cast can cast away const.

LightuC:
C-Style casting is totally acceptable, I agree. However, the C cast will just try the different C++ casts until one cast can be performed. I always find it better to be a bit more explicit with your casts, especially since a C-Style cast can cast away const.

I entirely agree. In C++ code it is always better to clearly express your intent by using a specific kind of cast instead of "nuking" the type system with a C-style cast.

However, C-style cast is still a very valuable C++ feature because of its compatibility with C code. And it will work perfectly fine in this case as well.

Contrary to what the OP claimed, there are no changes in C++ or in GCC that would force one to abandon C-style casts. And none are on the horizon.

The OP obviously just made a silly typo in their C-style cast syntax. And when the compiler complained, instead of simply finding and fixing that typo, they built a whole strange theory about some "advances in compliers" and "changes in permissiveness". A theory with no basis in reality.

Jiggy-Ninja thank you for helping me see exactly what you were pointing me to.