Casting an uint8_t* to uint32_t* in c++
Asked Answered
H

2

0

I am trying to implement an interface function of the FATFS library. The implementation expects an uint8_t* to the data that has to be written to an internal SD card by a different library. The data should be written to the library using the function BSP_SD_WriteBlocks(uint32_t*, uint64_t, uint32_t, uint32_t). (See Below)

/*
 * Write data from a specific sector
 */
int SDMMC::disk_write(const uint8_t *buffer, uint32_t sector, uint32_t count)
{
    int res = BSP_SD_WriteBlocks((uint32_t*)buffer, (uint64_t)sector, 512, count);

    if (res == MSD_OK) {
        return RES_OK;
    } else {
        return RES_ERROR;
    }
}

As you can see I am trying to cast an 8-bit memory address to a 32-bit memory address and don't think that this is the correct way to do so.

Unfortunately, I cannot change the function arguments so the disk_write function must accept uint8_t* and the BSP_SD_WriteBlocks merely accepts uint32_t*.

What is the best - and fasted way - to do so?

Halation answered 27/1, 2017 at 15:26 Comment(2)
That's a valid cast. But the count argument is probably the number of bytes (uint8_t elements) to write, right? And if the BSP_SD_WriteBlocks function write 32-bit words then you need to divide count by four or it will write to much (and go out of bounds of your buffer).Legatee
Possible endianess issue. (and might break the strict aliasing rule).Saundra
P
1

The trick is simply to use initially a uint32_t array of appropriate size (it can be dynamically allocated, see below my first idea, but not necessarily). Any object can be accessed at the byte level, so you can cast that uint32_t * to a uint8_t * and process it normally as a character buffer: you are accessing the original uint32_t array at the byte level which is allowed by the strict aliasing rule.

When you need an uint32_t * just cast back. As you only accessed the original array at the byte level, the lifetime of the array has not ended and the uint32_t * point to a valid array.


Older and not so good solution

The trick here would be to have the buffer allocated by malloc. The C standard says in the part referencing the C standard library explicitely accessible from a C++ program (*): 7.20.3 Memory management functions

... The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated...

That means that provided buffer is the return value of a malloc call, the standard guarantees that it can safely be casted to any other pointer type.

If you do not, you are in risk of an alignment problem because alignment for uint32_t is higher than for uint8_t, and using a bad aligned pointer is explicitly Undefined Behaviour.

One could argue that we are violating the strict aliasing rule here. But any common implementation will be fine with it (it would break too much existing code to reject that) and the only strictly conformant way would be to use a full memcopy of the buffer... to end with the exact same sequence of bytes with a compatible alignment!


(*) I know that C and C++ are different languages but C++ standard reference manual says in 1.2 Normative references [intro.refs]

1 The following referenced documents are indispensable for the application of this document...
— ISO/IEC 9899:1999, Programming languages — C
...
2 The library described in Clause 7 of ISO/IEC 9899:1999 and Clause 7 of ISO/IEC 9899:1999/Cor.1:2001 and Clause 7 of ISO/IEC 9899:1999/Cor.2:2003 is hereinafter called the C standard library.1
...

1) With the qualifications noted in Clauses 18 through 30 and in C.3, the C standard library is a subset of the C++ standard library.

Plexiglas answered 27/1, 2017 at 15:49 Comment(0)
W
1

There are potentially multiple issues here.

  1. You're casting away the const.

In a pedantic world, this can lead to undefined behavior. To be absolutely correct, assuming you can't change BSP_SD_WriteBlocks to take a const pointer, you would have to make a copy of the data and use a non-const pointer to your copy. The bonus is that, in making the copy, you can solve all the other problems. The downside is that making a copy takes time and memory.

In a practical world, if you know that BSP_SD_WriteBlocks never tries to write through that pointer, you're probably fine. But this is a big deal, so I would use a C++-style const_cast<> to make it clear that you're doing this intentionally.

  1. Alignment.

In a pedantic world, the cast from std::uint8_t * to std::uint32_t * may not be safe, at least not portably, depending on whether you know the original pointer is suitably aligned or whether your platform allows unaligned access. Note that the copying suggested in #1 can solve this, as you could easily ensure that your temporary buffer is suitably aligned.

In a practical world, if you know that the source buffer will always be suitably aligned, which seems likely, then this isn't a big deal. Again, though, I'd suggest a C++-style cast. I'd also recommend an assertion that the buffer address is a multiple of the size of a std::uint32_t.

Wilsey answered 27/1, 2017 at 18:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.