I would have really liked to see a concrete example of where the UB of update_data would actually cause a result other than the anticipated one.
@davidforgeas2235
5 жыл бұрын
One example is that the compiler generates the code you expect but when you run it you get a segmentation fault if the pointer isn't aligned properly when the CPU expects it to be. I.e. it would be an error at runtime.
@mortenhaukesolvang5665
5 жыл бұрын
@@davidforgeas2235 Disregarding the fact that x64 doesn't have strict alignment requirements, 'reinterpret_cast' actually states alignment requirements in its specification. The bit of UB that is interesting is that accessing the 'S *' obtained from 'reinterpret_cast' supposedly is UB due to it's lifetime not having begun. I would also like to see examples of where this can cause issues, as it seems like an unintuitive rule to have in the language. Why would 'reinterpret_cast' even allow pointers then?
@JohnSmith-one
4 жыл бұрын
Hi, Chili)
@_lapys
4 жыл бұрын
Hey, Chili :)
@ArjanvanVught
4 жыл бұрын
@@mortenhaukesolvang5665 I also would like to see real world use cases for UB; also on ARM you can have unaligned memory access. The struct is just a template representing your data layout. When the compiler believes that a memcpy is needed, then you would have major performance impact.
@Omnifarious0
5 жыл бұрын
I'm glad C++20 is finally addressing this hole in the standard.
@benoitrousseau4137
4 жыл бұрын
This isn't a solution for various use cases involving memory mapping, where reading and writing can have side effects. In addition, std::bit_cast won't let me write back into the array of bytes because it returns a value (makes sense given the implementation) so it's not a full replacement even in general scenarios where you would do that. And just in general I'm not a fan of using memcpy and having to trust the optimizer to do the right thing because optimizations are easily broken. What I find unfortunate is that a C cast or reinterpret_cast clearly states the intent of the programmer whereas memset does not. I would like to see the objections to relaxing the lifetime rules for types that are both trivially constructible and destructible so that kind of code can work, because clearly there is a need.
@KennyMinigun
5 жыл бұрын
Then how am I supposed to turn const char* into const unsigned char*?
@SeppyYT
5 жыл бұрын
there is still memcpy and in c++20 there will be std::bit_cast
@phonlolol5153
5 жыл бұрын
they may alias each other. so reinterpret cast is allowed in this case :^)
@KennyMinigun
5 жыл бұрын
@@phonlolol5153 That is exactly true, same as any object pointer can be converted to byte (char*) pointer by interpret_cast witout UB. However, it cannot be done by any other _cast at the moment.
@tubebha
5 жыл бұрын
I am unable to understand the reasoning and what went wrong with function using reinterpret_cast...
@davidforgeas2235
5 жыл бұрын
You can't create any object anywhere in memory. If the CPU needs the ints to be aligned, an arbitrary char pointer can't have that guarantee. If you start with a S object on the stack, you know the pointer you get is aligned.
@ArjanvanVught
4 жыл бұрын
@@davidforgeas2235 Also on ARM you can have unaligned memory access. The struct is just a template representing your data layout. When the compiler believes that a memcpy is needed, then you would have major performance impact. Just do not try unaligned memory as of it is int for example. Otherwise remapping your raw data with a struct is working fine.
@marktaylor6052
4 жыл бұрын
@@davidforgeas2235 So if you are targeting Windows its okay,
@davidforgeas2235
4 жыл бұрын
@@marktaylor6052 only when the compiler can't see the mischief. It's still wrong according to C++ but usually it compiles all right and runs all right on the CPU. The issue is "usually" instead of "always guaranteed"
@marktaylor6052
4 жыл бұрын
@@davidforgeas2235 - usual is good enough for me, as my target audience are PC users/
@DrGreenGiant
4 жыл бұрын
Interesting point of view here. For embedded stuff (my world), say we are receiving a bunch of bytes from the UART port, from a sensor say. So in the cpp code you typically have the bytes as a buffer and a length. Doing a bunch of memcpys to extract the data, rather than reinterpret cast would be a huge perfomance hit. What are your thoughts here?
@devluz
5 жыл бұрын
Could you go more into the bad side effects reinterpret cast might have?
@marioschmidt1251
5 жыл бұрын
I don't get it. How is memcpy better? I still bypass that classes constructor and still have to pray that the alignment is right. What kind of UB does reinterpret_cast invoke that memcpy doesn't?
@Kaznovx
5 жыл бұрын
The problem is with lifetime of an object. With reinterpret_cast you access an object that's never been created. With memcpy you work on existing objects.
@nilfrisk2
5 жыл бұрын
@@Kaznovx if you copy garbage over the entire byte-range of the object is it still a valid object? e.g is it better to use a corrupted object or a pointer to corrupted data
@marioschmidt1251
5 жыл бұрын
@@Kaznovx Why is it important to begin an object's lifetime? Memcpy overwrite all data of the object anyway (and therefore undoes all things at object creation).
@KarelDonk
5 жыл бұрын
Yeah copying random bytes into an object like that is dangerous unless you know what you're doing. It should also be UB if it's not already. And so it's not much different from using reinterpret_cast frankly, and so just fukkan use reinterpret_cast.
@davidforgeas2235
5 жыл бұрын
There's a tangible S object being used and we are allowed to write (or read) an object through char (or unsigned char) pointer as a special case of the aliasing rule. Thus we know the S* pointer we get is properly aligned and the memory area has got the right size.
@WheezardX
5 жыл бұрын
Sounds like you are fighting a theoretical problem. Can you give a concrete example of this being a problem? Otherwise I’ll focus my efforts on real problems
@TomaszWiszkowski
5 жыл бұрын
few things to add (that are missing here): 1. casting raw stream of bytes to any larger primitive (than chars / uint8s) is susceptible to big/little endian related bugs. those are really ugly to debug, especially if you're encoding lengths this way. 2. casting unaligned raw stream to larger primitives will on some CPUs cause an misaligned access violation exception (which is a hardware exception - a trap). This will _probably_ work for you in some cases, but will hurt badly if you need to work with these CPUs. 3. in all the cases, including one presented by Jason, this all falls apart if compiler introduces any padding, eg. to accelerate data access (because some CPUs issue more fetches for misaligned accesses, eg. 68020+), or to avoid misaligned access exception. One could argue these CPUs are rare or obsolete, but truth be told - these are still frequently used in embedded or industrial machines. it's very tempting to make this type of 'optimization', but fixing this later becomes a tedious, painful process.
@MaceUA
5 жыл бұрын
1. I don't think it is a bug related to reinterpret_cast'ing or memcpy'ing per se. Rather, It is a bug in the application's design. Receiving a stream of binary data from some source that might give it to you in a different endianness, and not checking this endianness by any means (e.g., by requiring some BOM-like thingy at the very beginning of such stream)... You could as well receive such "wrong"-endian bytes by reading a binary file -- no reinterpret_casts or memcpys there, but the same problem. 2. +++ 3. I believe it would work with such paddings properly if we treated our input bytes precisely as an array of struct S objects (unless the stream comes from a different application that was compiled by a different compiler / for different platform / etc.) But, of course, if we accessed the stream of struct S objects just as a raw stream of ints (or whatever underlying fields may be) -- then, indeed, paddings may mess things up heavily.
@frydac
5 жыл бұрын
Indeed, in such a case I would use a well defined serialization scheme/lib.. using memcpy or reinterpret_cast here would not even cross my mind, as it would surely fail at some point for the above reasons, or I feel I'm missing something.
@Rand0081
4 жыл бұрын
Thank you for showing memcpy getting optimized away, but you could go deeper with cases like wrapping or dealing with C APIs.
@beaujamo
5 жыл бұрын
This video is little misleading because there are no sufficient reason why or why not use an interpret cast. Seems like the memcpy solution is a defined behavior because it defined the memory to hold the copied bytes as opposed to binding the struct with two ints into an 8 byte memory when doing reinterpret_cast which could be wrong when an int does not correspond to 4 bytes of memory. I would hope you’ll do a follow up video clarifying or giving more reasons why not use reinterpret_cast.
@sander_bouwhuis
4 жыл бұрын
I came here to see a sensible alternative to reinterpret_cast, and didn't find it. Don't you have better alternatives than memcpy'ing everything? It seems like a confusing mess that makes code updates more difficult. I looked at std::bitcast and it basically does what you did with the memcpy. I hope someone can give us an examples where reinterpret_cast fails, but your version works.
@CallumPooleProgrammer
4 жыл бұрын
Question: If the same machine code is generated for both, how is one undefined and the other not when it does the same thing?
@allcreater_
3 жыл бұрын
UB means that compilers are free how to treat specific expressions so that concrete code may work different on different compilers or platforms or even in different context (hello, optimizations! :) ). Probably _exactly this_ code will work fine on all compilers and platforms, but it can lead to hard-to-find errors.
@brynyard
3 жыл бұрын
We do this all the time with large data structures, it's not just a matter of memcpy being "slow" when throwing around Gigabytes of memory because if the compile doesn't optimize it away the program usually won't be able to run at all. So what are we supposed to use instead, we currently have a mechanism that is working (even though the spec says it's undefined, the compiler does the right thing - for now)?
@just0x1b31
5 жыл бұрын
I've seen some people using unions to avoid using reinterpret cast, which is also an undefined behavior.
@Reddles37
2 жыл бұрын
This kind of thing really irritates me in C++. The reinterpret_cast version clearly states exactly what we want the program to do, but for some reason that's UB. So instead we're supposed to add a bunch of copies that we don't actually want the program to perform, and then rely on the compiler to figure out what we're doing and optimize it back into the original reinterpret_cast version behind the scenes. That's just dumb, if there is a problem with reinterpret_cast then it should just be fixed so it actually works reliably as you would expect.
@marktaylor6052
4 жыл бұрын
Code will behave well if it is defined in the right context. The 'solution' above would not work if the buffer comes from another thread and needs locking. The behaviour is still undefined. I see no issue at all with reinterpret_cast as long as it is coded carefully. Perfectionism can sometimes be cynicism in disguise.
@AleksyGrabovski
3 жыл бұрын
Why not just using union { char raw[sizeof(S)]; S s; } ? I think unions guarantee alignment.
@krzysztofmalysa
5 жыл бұрын
The problem with memcpy() is that GCC does not fully optimize it. I tried the above example and on GCC it produced 6 mov instead of 1. But the fact is that it will be 6 moves even if the array is bigger than 3.
@ArtemyVysotsky
5 жыл бұрын
I would like to see a solution that does not use reinterpret_cast for the case when calling Win32 API like QueryServiceConfig which expects a pointer to struct, but requires the pointer to point to buffer of the size received from the previous call to this API... So what most people do - they allocate a buffer of specified size and then cast it to the pointer to struct type before passing it to WinAPI function.
@akj7
5 жыл бұрын
Is this also valid for C? If yes, then all these functions of type: void function(void* value){ uint8_t* newValue = (uint8_t*)value;} aren't allowed. This means that memcpy itself isn't allowed
@markusbock3364
5 жыл бұрын
That's why memcpy is an intrinsic
@norbertlange3184
5 жыл бұрын
most of the time uint8_t is a unsigned char. There is a special rule in the standard that char aliases with everything, so the above is not UB aslong uint8_t is an unsigned char. Also you can always have compiler specific special tricks in the memcpy implementation (some optimization barriers), the library routines typically aren't part of your compilation unit either.
@phonlolol5153
5 жыл бұрын
Its not allowed to access arbitrary bytes. If you place the specific object into lifetime before you are gucci
@SeppyYT
5 жыл бұрын
Isn't reinterpret_cast used for type punning in sockaddr_in* to sockaddr* or in cases you need bitcast ?
@dirtyd2285
4 жыл бұрын
This is one of those things that just seems like it shouldn't and doesn't need to be the way that it is. These rules seem almost political or religious rather than technical. Strict aliasing rules should be relaxed in situations like this. It's obvious to the programmer, as well as the compiler that in update_data the struct pointer does in fact without a doubt alias char *blob, or whatever other pointer type you assign to it. Now if you go assign that struct pointer to some other pointer declared outside of the function then go and use it, all bets are off, aliasing rules apply. The reason being that inside the function it's easy for the compiler to tell that the pointers alias, but otherwise not so much. As for the object lifetimes, it's also obvious to the programmer as well as the compiler that the struct pointer has two int members, and they and the struct are POD types so their lifetimes should begin, whatever that means for a POD type, or just be assumed to already be "alive". This part actually seems like less of an actual problem to me. I think it's just call UB because some types might have invalid or trap representations on the machine and since the object wasn't explicitly constructed (in this function anyway) the compiler can't be sure that the memory being pointed to doesn't contain an invalid or trap representation etc. If I created the object somewhere else, or I know that it was created correctly on another machine and sent over the internet and stored in a correctly aligned char buffer, well then while C++ or the compiler might not know if a correctly constructed object lives there, I do.
@gtdcoder
5 жыл бұрын
What if you just want copy the ints out of the char* into another struct of type S? Is it ok to temporarily reinterpret_cast so you can do a member-wise copy(not a memcpy)?
@Sayuri998
5 жыл бұрын
So I'm curious. Can you actually use placement new to create a new object at the specifies bytes, then update the data, to avoid undefined behavior? If we don't initialize the ints in the struct, they should just pick up whatever is in that memory already. (Ignoring alignment issues here.)
@larswadefalk6423
3 жыл бұрын
Placement new is IMHO a very good alternative. But this thing is mostly just academic anyway. I don't see even in the most optimized embedded targets that you would trust the incoming data this way. It's usually a matter of interpreting what you actually have first. If something is read from a very simple hardware sensor you may have no means to really assert what you have is the right thing, but Jason here modifies data on the fly. It's usually not a good programming practice anyway. But I may be accepting it for the sake of explaining the difference.
@khatharrmalkavian3306
2 жыл бұрын
If you intend to work with an array of S then why does blob even exist? Generate the array of S and read the data into it.
@ultimatesoup
11 ай бұрын
There are definitely actual use cases for reinterpret_cast, specifically when dealing with old C APIs or when dealing directly with hardware. A use case I have is in writing a generic memory pool to prevent memory fragmentation and cache line misses. You reserve a bunch of data upfront and then when you allocate with the pool, you hand the next spot that has room off to the caller. Before you do that, you need to use reinterpret_cast to make sure you're giving the correct type back otherwise its useless. Since I just did the memory management myself in that function, I know its safe to cast to that object type. I create the new object on the stack by calling its constructor and then memmove it into place before returning
@dwarftoad
3 жыл бұрын
With -O3, it looks like the same code is also generated if you create a temporary struct, memcpy each int separately, update the value in the struct, and copy each int back into the blob. Same even if you build up some API functions around reading/writing each int from/to the buffer it should be similar performance I think?
@rajeshpawar1696
2 жыл бұрын
Sir I have a problem with reinterpret cast in my project. Can you help me to solve this.
@midgetlyness214
5 жыл бұрын
I don't quite follow the reasoning here. In my own use case where we do use reinterpret_cast is with the use of memory mapped files to share data across different processes. Process 1 populates a struct (trivially copyable) and then copies this entire structure to the memory we just mapped. Process 2 would then simply use reinterpret_cast to view / update that same memory where - according to my understanding at least - the struct does infact exist. Is this still considered UB?
@JakubSkowron
5 жыл бұрын
Yes, UB, since a verbatim copy in a new place (the shared memory) is not an object that begun its lifetime. Maybe you could use a custom operator new for this struct? It would solve the problem by placing the struct in shared memory from the beginning.
@midgetlyness214
5 жыл бұрын
@@JakubSkowronThanks for the idea. I still don't quite understand the fundamental difference between constructing the object there vs the verbatim copy. I guess an equivalent comparison would be auto ptr = malloc(sizeof(Struct)) vs auto ptr = new Struct(); Now I do understand that using malloc vs new has quite a few differences in how they operate (calling of the constructor etc etc). If we assume that our type Struct has 2 integer fields (like in this video) - and has been setup to not make use of any padding (by adding #pragma pack (1) - what I would like to know is whether there is a difference in how the memory gets allocated that could result in UB? Or maybe stating it otherwise - if I did allocate memory using auto ptr = malloc(sizeof(Struct)); and accessed the fields via a reinterpret_cast - this is then considered UB. What I would like to know is why? I'm not arguing - I'm just really curious and would like to learn more on this particular topic
@JakubSkowron
5 жыл бұрын
@@midgetlyness214 Take a look on proposed std::bless. Sad truth is the standard does not support this yet ( www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0593r2.html ) Unfortunately, without special additions to the standard described in the proposition (see: "A call to malloc, calloc, realloc, or any function named operator new or operator new[] implicitly creates objects in its returned storage", and "A new barrier operation [std::bless...] with semantics equivalent to a memmove with the same source and destination storage"), this simple code below is UB: // some program part in C S* s = (S*)malloc(sizeof(struct S)); // ... do something with s, // and give it to C++ s->a = 5; // UB in C++ You should for now use std::memcpy to some "blessed" buffer, i.e. somewhere where C++ compiler is sure some object is: // some program part in C S* s = (S*)malloc(sizeof(struct S)); // ... do something with s, // and give it to C++ S buf; std::memcpy(&buf, s, sizeof(S)); buf.a = 5; // not UB
@AlexCohnAtNetvision
5 жыл бұрын
@@JakubSkowron Will … `S* pbuf = new(s) S()` not do? or `S& buf = *(new(s) S())` ?
@JakubSkowron
5 жыл бұрын
@@AlexCohnAtNetvision new will do OK - it starts lifetime of an object. std::malloc will not do OK - at least until the mentioned proposition (p0593r2 Implicit creation of objects for low-level object manipulation) is introduced to the standard.
@keris3920
3 жыл бұрын
There are still a few examples of scenarios where there is no good alternative to reinterpret_cast. Consider the following: The standard makes the following guarantees about std::complex: 1. for any integer i and pointer to complex p, reinterpret_cast(p)[2*i] is the real part of the complex number 2. for any integer i and pointer to complex p, reinterpret_cast(p)[2*i + 1] is the imaginary part of the complex number In other words, std::complex is guaranteed to not have padding, and the order of the real/imaginary parts are well-defined. In the case of vectorization using simd intrinsics, std::complex must be vectorized as a float array. complex::real() returns a COPY of the real part, so it does not have the same address as the underlying real. complex::data() does not exist, so you cannot grab a pointer to T. The only good way to solve this is to do something like this: _mm_load_ps(reinterpret_cast(p)). But note that the standard guarantees this behavior! x86 sse integer load intrinsics require a user to input data types as const __m128i*. But to transform from something like std::int16_t* to __m128i* requires trickery. MSVC, libstdc++, etc do not have the same implementations for these vector types. The only way I've found to reliably do this in a cross-platform manner without paying for a bunch of copies (if you're using simd intrinsics, you most-certainly care about this, because you'll lose the performance gain and render the intrinsics pointless) is to reinterpret_cast(p). This currently works on MSVC, ICC, GCC, Clang, IAR, and 3 different implementations of the standard library that I've tried. I understand that the point of this video is to discourage the use of reinterpret_cast WHEN POSSIBLE, but I'd love to be able to get rid of it altogether at some point. std::memcpy and std::bit_cast solve a lot of this, but they don't fix everything.
@headlibrarian1996
5 жыл бұрын
A reinterpret_cast(char*) will usually fail with an unaligned access error on anything not an x86.
@denispriyomov6086
5 жыл бұрын
Unless you define struct as packed. Which is in fact required in the Jason's example as well as taking care of network to host & back. Otherwise it's not a valid example of network data processing.
@nmmm2000
5 жыл бұрын
assuming you have struct with tho int's, and there are no padding there (yes there will be no alignment), I can not imagine situation where reinterpret cast will fail. I can imagine cases when both reinterpret_cast and memcpy() can fail - for example read-only or protected memory. Or classes.
@davidforgeas2235
5 жыл бұрын
You can't create any object anywhere in memory. If the CPU needs the ints to be aligned, an arbitrary char pointer can't have that guarantee. If you start with a S object on the stack, you know the pointer you get is aligned.
@mariushusejacobsen3221
4 жыл бұрын
The optimizer. If you manipulate the object you mapped onto that buffer, the optimizer may assume you didn't change the buffer itself. If you then do something else with the buffer later - in another function, with inlining etc - it may assume the unchanged buffer was used, and perhaps even reorder this other use of the buffer before where the buffer was changed.
@davidforgeas2235
4 жыл бұрын
@@mariushusejacobsen3221 that's true in general but if you use char, unsigned char or std::byte then you're allowed to alias the two pointers meaning the compiler knows they point to the same place and what you describe doesn't happen.
@JohnSmith-one
4 жыл бұрын
Can I just count bytes? Like memcpy(blob+20,...)? Because it's unclear to me why I should create 3 objects on the stack and then copying to it and from it... Seems like a lot of unnecessary work. I understand that this is optimised out in this case, but all of this boilerplate code is really unclear
@jettire
Жыл бұрын
I feel the standard is starving the embedded community! The example above only allows conversions between homogeneous types (I.e: bit cast). What about the case where a microcontroller’s memory mapped address, a literal integer, is reinterpreted to a constexpr pointer of that same size? As an example a cortex m4 may have a peripheral uart tx data register 32 bits in width at 0xdeadbeef, i.e: constexpr std::uint32_t* uart_tx_data_reg = reinterpret_cast(0xdeadbeef); ? As it stands, it’s perfectly ok as dan saks points out in one of his embedded talks (“memory-mapped devices as objects” time stamp 20:18 KZitem). I agree and this is perfectly constexpr-able since this uart tx data reg address used above is not changing after compilation. If static cast were an option, I would use it in a heartbeat. Moreover if this reinterpret cast were to be used in a constexpr function, it is forbidden since it fails to compile. I honestly believe this particular construct should be an exception to use reinterpret cast In a constexpr function. This would yield runtime speed and optimization gains since the address is resolved at compile time even if this reinterpret cast is referenced inside another constexpr function.
@veetaha
5 жыл бұрын
Memcpy? Are you sure compiler can optimize out copying in all cases?
@Nobody1707
5 жыл бұрын
All of the major compilers have special case optimizations for memcpy.
@0xCAFEF00D
5 жыл бұрын
The question was about coverage not if it can do it.
@davidforgeas2235
5 жыл бұрын
The main point that's made is that memcpy is the correct way. The fact that it's optimised is a nice bonus to feel good about it.
@0xCAFEF00D
5 жыл бұрын
@@davidforgeas2235 Sometimes doing the copy isn't acceptable. It doesn't matter what's correct if it doesn't work.
@davidforgeas2235
5 жыл бұрын
@@0xCAFEF00D in principle when something is UB it doesn't work. Using memcpy is an easy way to avoid UB, but if you can prove your program is valid with reinterpret_cast then great. In this video he shows one obvious way to avoid UB. Given the questions here and the general understanding of the standard with regards to aliasing rules, alignment, separate compilation, it's fair to say proving reinterpret_cast is not UB is not trivial.
@michaelmahn4373
5 жыл бұрын
What about the OpenGL function glVertexAttribPointer? The last parameter is a void*, but really meant to be an int. Is reinterpret_cast not the right thing to do there?
@WarHero56
4 жыл бұрын
I've always wondered why that's a void pointer. Is it just a typo in the spec that doesn't really matter, or is there some reason for it?
@keris3920
4 жыл бұрын
@@WarHero56 void* is C-style type punning/polymorphism. It is just raw memory.
@simonmaracine4721
Жыл бұрын
@@WarHero56 Legacy reasons. In very old OpenGL versions, glVertexAttribPointer had slightly different functionality. The last parameter had another purpose.
@alexandernyberg8668
2 жыл бұрын
I feel like all this is much like getting the bits from a float where instead of giving us a way of doing something they simply declare the only good way of doing it to be invalid
@ub880
5 жыл бұрын
The given example with the struct and reinterpret_cast is dangerous for another reason: If the elements of the struct are smaller than the default alignment, there may be unused space in the struct (padding). Yes, __attribute__((packed)) may help.
@kiwischolz9811
5 жыл бұрын
I just found that exactly bug in my code a couple of days ago and I was thinking about that the whole episode. Although it didn't have anything to do with reinterpret_cast. I was doing a ifs.read(&s, sizeof(S)), which of course read more bytes than it should. At least I learned a thing about structs I'll never forget again.
@futube41
5 жыл бұрын
I dont see how this solves the Problem. If the target memory size is not really 3 but smaller this is also undefined behavior.
@wolfgangkundrus5551
5 жыл бұрын
With C++ you have to know what you are doing. Nothing wrong with that. Go use a different language, if that is not desired.
@planckc1
5 жыл бұрын
The title and the explanation are poorly worded, but the problem is real: compiler is free to assume pointers of different types point to different objects. So even an innocently looking code like this: "int foo(float f) { return *reinterpret_cast(&f); }" can produce unexpected results when optimizer decides there is no reason to initialize f since it is never used through a pointer of relevant type. And the problem is not limited to reinterpret_cast, you can create such pointers with static_cast too.
@conpdepab
2 жыл бұрын
And how's the best way to read from a binary file?
@larswadefalk6423
3 жыл бұрын
Just reading the title I was first thinking: "THIS is the one video I am going to come down on and complain". But after seeing the magics of the memcpy intrinsics being identified so smartly by the compiler, I am disappointed to say I have nothing to complain about. Jason delivers again. It would though been interesting to see how the bit cast really is intended to solve this. I think in most cases, we actually do copy things over to a local state when things come from the wire or a c/assembly interface. It is very hazardous anyway to modify things like that in place without really asserting the knowledge and trust of the outside of the function. Having not even mentioned the usual problems with endianness from either hardware or different architecture that usually has to be dealt with anyway.
@berylliosis5250
3 жыл бұрын
It's really weird to see people who think avoiding UB is a purity argument. Yes, to some extent avoiding UB at all costs is fruitless, and to some extent it's impossible to write some code without either invoking subtle UB or getting performance drops, but in most cases avoiding UB is _not_ some arbitrary pure code thing. If you write code with UB, that code is not guaranteed to be: - Cross-platform - Stable across compiler updates - Stable across compilation flags - Correctly behaviored in niche cases - Potentially not even the same between different compilations It's _not_ about purity. It's about protecting you from yourself - and from the malicious parts of the C++ standard.
@CobaiaHardcore
4 ай бұрын
I would love a review on this video. Does anyone know if there is a revised STOP USING reinterpret_cast?
@cppweekly
3 ай бұрын
I'm waiting for a compiler to support start_lifetime_as so we can discuss it better.
@madpuppet666
Жыл бұрын
nope on this one. Firstly, I'd never do memcpys that way - your debug builds will quickly become unusably slow. And secondly, that optimisation is gonna break on any realworld usages of casting where you tend to use the results over multiple scopes that can't all just be inlined.
@justinmontgomery2583
4 жыл бұрын
Just updated my code to remove all reinterpret_cast except for a cast to uintptr_t from pointer type in a serialization library I wrote.
@larswadefalk6423
3 жыл бұрын
Well, just make sure that your compiler actually optimize the memcpy intrinsics just as good as we saw here.
@JakubSkowron
5 жыл бұрын
How do I write this update_data function with additional int index parameter, when I don't know the size of blob (number of S elements)? Then I cannot use just fixed S obj[3], and dynamic allocation probably would not be optimized out.
@kallehuttunen7458
5 жыл бұрын
Jakub Skowron Just have one local S object and memcpy on top of it from the indexed address?
@JakubSkowron
5 жыл бұрын
@@kallehuttunen7458 then I would need to manually calculate the indexed address (using sizeof and alignof)
@piotrarturklos
5 жыл бұрын
If reinterpret_cast works in practice in some cases and the standard says it's UB, then it's not an existing practice that has been standardised. Standards are not supposed to work like that. I agree that reinterpret_cast is a minefield, but minefield is not a surface 100% filled with mines. There are clear spots. :) Something like reinterpret_cast is absolutely necessary (and arguably exists in practice) in C++, otherwise high-performance code cannot be written. It's not just a struct with 2 ints. We have arrays with tens of MB of ints and stuff that we have to process hundreds of times per second on low-end consumer devices. Whenever we pass data through a network or to a GPU, we may have to do reinterpretation.
@TheRealJman87
4 жыл бұрын
So using reinterpret_cast in my own code this way is bad, but when clang does it, it is okay?
@joelshuman6712
4 ай бұрын
Now that std::bit_set is here, can someone create a compiler explorer link that shows this example using std::bit_set? I noticed the bit_set video created used a different example.
@cppweekly
4 ай бұрын
What we really need to re-write this example is std::start_lifetime_as, but no compiler has implemented it yet!
@desertkun
5 жыл бұрын
Not writing a memcmp instead of memcpy and then debugging couple hours straight wondering what the hell is going on, why doesn't this work.
@TruthNerds
5 жыл бұрын
Back in the days of Borland C++, I spent a long time trying to fix a bizarre build error once until I discovered that, instead of #include, I had managed to write #inc1ude, which was practically indistinguishable in the DOS font.
@X_Baron
5 жыл бұрын
Reinterpret_cast seems to have a defined behavior for some specific niche uses only. Some other uses are, in practice, implementation defined and therefore often safe?
@paherbst524
3 жыл бұрын
Is that only because optimization it O3?
@keris3920
4 жыл бұрын
Just as an update, MSVC now supports std::bit_cast
@mba2ceo
Жыл бұрын
which is better ? file.write((char*)&z,sizeof(z)); or file.write(reinterpret_cast(&z),sizeof(z)); when writing to files
@cppweekly
Жыл бұрын
I should point out first that you are asking for trouble reading from / writing to files if you care about portability and code maintainability at all. But if you're going to, reinterpret_cast is the thing to use here.
@mba2ceo
Жыл бұрын
@@cppweekly but I need to move back the ptr for that :( Y ? please show code for both version for w/r binary. thank U gain
@NIDHINMS1993
5 жыл бұрын
will the destrucor of the array objects be invoked on exiting the function??
@Tomab89
5 жыл бұрын
Then I kind of wonder why we have the reinterpret_cast feature... what would be a true example of using it?
@npetrenko97
5 жыл бұрын
std::function uses it, I'm totally sure The code in the video is safe, provided the alignment requirements are satisfied
@npetrenko97
5 жыл бұрын
Hmm, Ctrl+f over sources doesn't help, so I might be wrong about std:: function
@xXH3ll5xB3llXx
5 жыл бұрын
You can resonably use reinterpret_cast in a few select places. A good example would be a std::allocator might internally allocate char * and then reinterpret_cast to T * to return the memory, this avoids the undefined behaviour because a) char * has a special exemption from aliasing rules and b) the T object is placement newed into the memory, officially begining the object's lifetime. Even still, this is only safe if the allocator is careful about alignment etc.
@npetrenko97
5 жыл бұрын
en.cppreference.com/w/cpp/types/aligned_storage
@norbertlange3184
5 жыл бұрын
Point 1: Ever thought about architectures without unaligned accesses? Those nifty optimization compilers can do, typically break down totally on those architectures, even if you manually tell the compiler to expect correctly aligned data. See: godbolt.org/z/vyT-u2 Point 2: how exactly would you use std::bitcast on char arrays/pointers (or to arrays)? S obj = std::bitcast(*reinterpret_cast(blob)) ? Point 3: the reinterpret_cast is *not* UB, dereferencing that pointer might be. (Which is necessary with bitcast?) ie. if you care for performance and correctness you would use something like the below, particularly if you have more complex structs. void update_data_l(char *blob, int new_value) { // Thats alot better than using packed structs: just define them correctly and check. static_assert(sizeof(S) == 8, "compiler added padding"); S *pLayout = reinterpret_cast(__builtin_assume_aligned(blob, 4)); memcpy(&pLayout[2].j, &new_value, sizeof(pLayout[2].j)); } Point 4: Fun fact: you could always use 'static_cast(static_cast(blob))', this is actually defined for pointers. Again, only accesses are UB, and they aren't when the types always agree (and are suitably aligned).
@cppweekly
3 жыл бұрын
It's not necessarily accurate to say "only accesses are UB." That is actually an implementation defined detail: eel.is/c++draft/basic.stc.general#4 "28) Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault."
@norbertlange3184
3 жыл бұрын
@@cppweekly you can call it unspecified, like the size of an int is. that's still different to undefined behaviour. Diverse contracts like having to cast to void * and back will make that implementaton defined detail specified that you can store the "abstract reference" of a similarly aligned type in a pointer.
@o_klk
5 жыл бұрын
The problem is that in debug mode compiler generates all the copy instructions for both memcpy calls. In some cases, debug build becomes totally unusable (for example, 6 fps in debug vs 60+ in release for FPS game is unacceptable).
@haydenknapp8521
5 жыл бұрын
Can you go over cpp 20 modules?
@ArjanvanVught
4 жыл бұрын
The memcpy does not make it all better. Consider this struct struct S { char c; int j; char d; char e; char f; }. Accessing the int j after the memcpy give undefined behavior.
@aerahtv0000
3 жыл бұрын
i still don't get it, if it compiles exactly the same assembly code, so how it can behave differently?
@simonmaracine4721
Жыл бұрын
Another compiler or version *could* generate code that behaves differently.
@LemonChieff
4 жыл бұрын
8:05 Technically it's still not not UB. There is no guarantee memcpy will exist in the global namespace, is there? Since you used and not
@nallid7357
3 жыл бұрын
memcpy is defined in the std namespace, unless you're not using c/c++, it will be defined whether you use string.h or cstring. cstring is literally string.h en.cppreference.com/w/cpp/header/cstring
@orocimarosay1447
5 жыл бұрын
So the problem only arises when you use structs with constructor or what ?
@davidforgeas2235
5 жыл бұрын
No. even *reinterpret_cast(char_ptr) = 0; is wrong.
@berndbaumanns55
5 жыл бұрын
@@davidforgeas2235 I would say it could be UB if char_ptr points to something which does not satisfy the requirements for int which are a proper alignment and size. But if both things are correct, it should be well defined.
@0xbaadf00d
5 жыл бұрын
In what specific case would the memcpy save us from a crash or memory corruption? I don't write serialization like this at all, since my code needs to run on multiple compilers and machines with different endianess.
@hongtaoliao6794
3 жыл бұрын
from any pointer to any pointer is never a good idea. especially with undefined behavior.
@AndrewHelgeCox
5 жыл бұрын
The only benefit I see in your solution is that it avoids the danger of unaligned access if the char buffer happens not to have been allocated to respect the alignment needs of the structs it holds.
@Popyshunchik
5 жыл бұрын
std::launder deserves to be mentioned here
@AndiAbrudan
5 жыл бұрын
Now I'm totally confused. Do we have std::reinterpret_cast, std::launder and std::bitcast all doing the same thing?
@Popyshunchik
5 жыл бұрын
Abrudan Andrei, if I understand things correctly, std::launder is the way to avoid undefined behavior while using reinterpret_cast
@MaceUA
5 жыл бұрын
@@Popyshunchik If I understand it correctly, std::launder is more about re-constructing objects on the same chunk of memory, so that the compiler won't mistakenly propagate some values from an old object when a new one has been constructed in its place. So I doubt it would help in this particular example. But I'm not sure, I haven't understood std::launder fully yet. I also hope for a more detailed explanation of std::launder from Jason.
@phonlolol5153
5 жыл бұрын
@@Popyshunchik No. You cant fix undefined behaviour which results from reinterpreting some storage where you dont bring an object (of the right type) into lifetime beforehand by just throwing std::launder at it. The object you are reinterpreting to MUST BE in its lifetime. There is no way around.
@johnjones8330
5 жыл бұрын
std::bit_cast not std::bitcast (fortunately)
@konradonbal2020
5 жыл бұрын
Aren't you at CppCon? ;)
@AndrewHelgeCox
5 жыл бұрын
Surely the "clean" solution is to get the data out of the binary blob from the wire into a structured in memory representation once, operate on it there as regular objects, and then splat it out to binary on the next IO write, not to update the char array blob in memory?
@homomorphic
5 жыл бұрын
Have never used reinterpret_cast.
@brewtaldeafmedal1262
2 ай бұрын
Congratulations!🦄
@homomorphic
2 ай бұрын
@@brewtaldeafmedal1262 you waited too long to congratulate me. I've used reinterpret cast a few times in the last 4 years ;-)
@AndrewHelgeCox
5 жыл бұрын
The compiler can only generate identical code here because it has the benefit of whole program analysis on such a trivial example. If the compilation unit doing the update was dealing with an arbitrary char pointer from any source, it should at least check that the pointer is 4 byte aligned before writing relative to it. In particular on architectures for which alignment of loads and stores is a hard requirement rather than just a performance benefit.
@dubble
Жыл бұрын
reinterpret_cast has well defined behaviour for integer-pointer conversions. And for many others, it might be undefined behaviour, but never fails on any compiler. Memcopying forth and back isn't an option if you cannot actually instanciate the type. What if you wish to access a member function with side effects, even if you were able to copy the object and invoke the member function, the result of that would be undefined behaviour outside of the object itself. Not using reinterpret_cast is a possibility if you only work within the boundaries of your own single application - once you interface with others there is no alternative other than writing inline assembly. And then again, undefined behaviour that *always* does what you tell it to do isn't really all that undefined.
@cppweekly
Жыл бұрын
No it doesn't. An integer is just like any other object in C++. You might want to think that you are playing with well defined behavior, but you are not. The only things allowed are to view via a char type.
@igorswies5913
3 жыл бұрын
I thought you're gonna convince people to use C style casts instead lol disappointed
@ariyowalker2968
3 жыл бұрын
By this logic, we should not use string_view as it references a string of byte that has no lifetime. Though, the char* buffer (the containing data structure) has a lifetime defined by the new and delete operations, similar to a string allocation and deletion. Thus, the struct S, used with the reinterpret_cast, would have a lifetime similar to a string view as both are dependents which depend on the lifetime of a larger data structure. Logically, we can use reinterpret_cast as a view into larger data structures without considering this undefined behavior. We must differentiate any undefined behavior specified in text and written by others from behavior that is defined under the laws of logic. Logic over interpretation!
@J.D-g8.1
2 жыл бұрын
i generally prefer type casting { void* OmniPointer; (SomeClass*) OmniPointer; } though reinterpret_cast(OmniPointer); is better when you want to write a longer sentence of code.
@jonesconrad1
5 жыл бұрын
whiteboarding is painfull, just use clion or something
2 жыл бұрын
It is technically an UB, however I would bet that every C++ compiler does the right thing (most likely due to inter-operability with C code, where type-punning is legal). My issue with memcpy() is that GCC does not optimize call to this function out on platforms that can not do unaligned memory access, like ARM Cortex M0, which significantly impacts performance on this low power device.
@cppweekly
2 жыл бұрын
This is when it most important that you *don't* use reinterpret_cast. On processors that don't support unaligned memory access, a reinterpret_cast can cause a CPU fault. I was actually doing some code reviews for a hardware manufacturer of M0 processors and pointed out this potential problem with the reinterpret_cast they were using and they thanked me because it had been a real problem for them in the past.
2 жыл бұрын
@@cppweekly Oh I did have a link with code example here but that got deleted, the point is to reinterpret_cast to the type wrapped in packed structure (you may add 'may_alias' attribute on GCC for good measure). In this case it does what you expect it to (at least when using GCC): it writes the data directly on platforms that can handle it, and copies it byte after byte inline on ones that can't. No unnecessary costly non-inlined memcpy() calls.
@tdp2110
5 жыл бұрын
I think your sizeofs in the last example are probably missing a ‘*3’
@amansaxena5898
5 жыл бұрын
Doesn't memcpy itself use reinterpret_cast (aka C cast) within it?
@MaceUA
5 жыл бұрын
The moment std::memcpy receives its arguments and they are converted to pointers to void -- then... yes, kind of. Not exactly reinterpret_cast, but something very similar. But the magic is not there. The key difference is that std::memcpy is *special* to the compiler. Unlike reinterpret_cast, std::memcpy acts as a barrier to the compiler, preventing it from any possible aliasing-related optimizations that can cause UB.
@Henrik0x7F
5 жыл бұрын
@@MaceUA Not really. You can implement memcpy yourself and it would work. The thing happening there is that you cast to char* which is always allowed.
@MaceUA
5 жыл бұрын
@@Henrik0x7F Yes and no. Indeed, you can implement memcpy yourself (you'll have to use a non-standard extension '__restrict', yet still that is possible in practice). But, anyway, the compiler will recognize that your code is actually a memcpy, and it will replace that code with a call/jmp to the intrinsic 'memcpy' which does the magic I wrote about.
@davidforgeas2235
5 жыл бұрын
@@Henrik0x7F I concur, the special thing with memcpy here is that there's a tangible S object being used and we are allowed to write (or read) an object through char (or unsigned char) pointer as a special case of the aliasing rule. Thus we know the S* pointer we get is properly aligned and the memory area has got the right size. What remains is declaring the object "alive" even though the constructor hasn't run which I guess is trivial because it's a POD.
@ie71773
5 жыл бұрын
Very interesting. Putting the first memcpy in a constructor and second memcpy in the destructor of an wrapper object, optimize and type safe the manipulation of blob for the entire scope. Only on clang 🤔
@progammler
5 жыл бұрын
Good luck trying this in HLS haha. It always depends on what you are writing the code for. You can't always rely on the compiler doing the thinking for you (unfortunately)...
@monkeeyo101
4 жыл бұрын
FOR S P E E D
@SteveLorimer
5 жыл бұрын
In the cppreference page for std::basic_istream::read, if I want to read an integer from a stream, it suggests I use reinterpret_cast: raw.read(reinterpret_cast(&n), sizeof n) en.cppreference.com/w/cpp/io/basic_istream/read
@Nyarmith
5 жыл бұрын
Never!!
@sneezingfrog
2 жыл бұрын
Placement new. Just sayin'.
@cppweekly
2 жыл бұрын
If you are using reinterpret_cast with placement new, then you are using placement new incorrectly. compiler-explorer.com/z/8vx1MYxc3 Placement new returns a new pointer of the correct type.
@sneezingfrog
2 жыл бұрын
@@cppweekly My point being that in many cases, e.g., interpreting data received from a network, you'll see things like reinterpret_cast, various abominations with unions, lots of paths leading to UB, etc. Often, you're dealing with (a) storage that's already properly aligned and (b) is known to contain the correct data. Placement new is your friend here.
Пікірлер: 222