6 ms·
People tell me that the problem with this code is that casting one pointer type to another and then dereferencing is undefined behaviour. The problem is that C
by abainbridge 6y ago
People tell me that the problem with this code is that casting one pointer type to another and then dereferencing is undefined behaviour. The problem is that C code seems to do this all the time. For example, fread() in the standard library takes a void* as its first parameter. So at first glance, most code that uses fread() must exhibit undefined behaviour. eg this function:
int foo(FILE *f) {
int val;
fread(&val, sizeof(val), 1, f);
return val;
}
Closer reading of the spec says something about it being fine to cast, say, an int* to a float* and then cast back before dereferencing it. In this example, we're OK, because we don't reference via the void* . But the fread implementation must do, right?
I'm left feeling like it is impossible to implement fread() in C and that the standard library's API is no longer considered a good example of how to construct an API.
I guess the original motivation for it being UB to cast is to do with alignment constraints on some hardware. For example, a machine might be able to read an int8_t from any alignment but might require 4-byte alignment for int32_t. If so, then casting a non-aligned int8_t* to int32_t* and deferencing it would indeed fail on that hardware.
But some would argue that it is not right that GCC 7 breaks such code on a machine where that problem doesn't exist.
- chrisseaton 6y ago> The problem is that C code seems to do this all the time. People should write these bits in assembly (as little as possible, but doing the casting and things), and then keep their C code well-defined using these low-level library routines.
- abainbridge 6y agoWith this machine I propose, that requires 4-byte alignment for int32_t, then even if I wrote the code in assembly, I'd just get a unaligned access fault from the hardware. So my code still wouldn't work. I'd rather the spec was changed to reflect this reality. Instead of calling it undefined behaviour, it should say something more specific, like, "dereferencing a pointer that isn't correctly aligned may cause your program to abort". The key difference is that the alignment (usually) cannot be determined at compile time, so the compiler would still have to generate code that worked if the alignment was correct at run time. I think this is what many people either expect or would like the compiler to do. With that change to the spec, I could continue to write my code in portable C, and I'd only get an abort on the target with this alignment constraint.
- chrisseaton 6y ago> it should say something more specific, like, "dereferencing a pointer that isn't correctly aligned may cause your program to abort" What if on some other machine it doesn't cause the program to abort but instead returns a broken result? How do you cover that in the spec? > But some would argue that it is not right that GCC 7 breaks such code on a machine where that problem doesn't exist. Right, so let's write these low-level things in C and not worry about the case of 'where the problem doesn't exist'.
- abainbridge 6y ago> How do you cover that in the spec? OK, how about, "dereferencing a pointer that isn't correctly aligned doesn't work on some hardware". I see that this is a hard area for the spec to do well. But the current situation isn't viable. I can't tell whether I can safely use fread for heavens sake. I feel like dereferencing an incorrectly aligned pointer could be a runtime error, just like divide by zero or accessing memory that doesn't exist. Maybe that would be an improvement from the current status. Although I'm not sure the spec _could_ be changed like this at this point in time.
- chrisseaton 6y ago> I feel like dereferencing an incorrectly aligned pointer could be a runtime error By checking the alignment on every access? That'd be far too expensive if the machine you're on doesn't trap.
- abainbridge 6y agoNo. Letting the hardware just do its thing. Like an out of bounds memory access or divide by zero. Editing here because it looks like we've hit the max reply depth. This wouldn't be like UB in the same way that code that could generate an out-of-bounds memory accesses is not UB. The critical thing is that the compiler should assume that the hardware error does not occur.
- sharpneli 6y agoAs long as fread internally writes into it as char* it’s fine. char* is the special case that aliases everything else. The ”Just dumb bytes” kind of pointer if you will.
- abainbridge 6y agoYou are correct. However, C programmers expect to be able to cast a void* to something other than a char* and use it. It is kind of necessary because the language doesn't support generics. I feel that the reasons for calling this UB are not as good as the reasons for making it valid code.
- sharpneli 6y agoThe reasons for that are performance. So that one doesn't have to assume everything aliases everything else. It could be more clear though, like having explicit alias qualifier which would make it alias all and by default nothing aliases, or vice versa. Right now there is no easy way to make two unrelated types alias, one has to go via char* or use unions. You can cast void* to something else and it will work, but you can't expect to get a random void* and presume it has some valid well defined data for you to read. You must know where it came from and what's been done to it. As a rule of thumb if you just write dumb bytes it's char*. If it's anything else you must know what you're doing and there are ways to perform type punning that's within the spec.
- abainbridge 6y ago> The reasons for that are performance. So that one doesn't have to assume everything aliases everything else. Can you expand on that? I don't understand. Edit: I see the answer in one of your other comments: > If len and array are both of the same type then the compiler must assume they can alias (or one of them is char*). One has to explicitly state with restrict that they cannot alias. I'd happily take that hit. I can put restrict in the hot paths of my code. I'm tempted to say I want restrict to be the default, but I haven't thought that through.
- 6y ago
- armitron 6y agoThere is no undefined behavior in your example period. The fread implementation sees dest as a void pointer and will access it going through char*, which is explicitly allowed.