4 ms·
> Therefore the existence of the q pointer does not tell the compiler what type actually exists at memory address p/q. Is it invalid to construct q from p? I wo
by wizeman 4y ago
> Therefore the existence of the q pointer does not tell the compiler what type actually exists at memory address p/q. Is it invalid to construct q from p? I wouldn't think so as long as they are in the same allocation, but maybe I'm forgetting a rule.
But then the question is, how would you construct an allocation where both q[-1] and *p are guaranteed to be valid objects of their respective types, using standard C? (i.e. without relying on implementation-specific behavior).
> Surely there's a way to allocate two different types up against each other, though?
If by "against each other" you mean without padding between them, then I think the C standard allows that but there's no reliable/portable/guaranteed way to do it (that I know of).
By that I mean that specific implementations can guarantee that, either with struct packing or, in some cases, by carefully aligning them within a struct or a union. But this depends on the implementation/architecture, so it's not strictly portable/standard C, I think.
> Even if you don't make an actual struct, you can grab some char and say that X bytes are size_t and Y bytes are something else, can't you?
I'm not sure that is possible according to the standard, unless they already were objects of those types. See this[0]:
> It is always presumed that a char* may refer to an alias of any object. It is therefore quite safe, if perhaps a bit unoptimal (for architecture with wide loads and stores) to cast any pointer of any type to a char* type.
> The converse is not true. Casting a char* to a pointer of any type other than a char* and dereferencing it is usually in violation of the strict aliasing rule.
> As noted by Pinskla it is not deferencing a char* per se that is specifically recognized as a potential alias of any object, but any address referring to a char object. This includes an array of char objects
That said, I think for converting a bunch of chars to another type it's possible to get around that with memcpy() in some cases, but I'm not sure it would be helpful in this scenario.
[0] https://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html https://cellperformance.beyond3d.com/articles/2006/06/unders...
- Dylan16807 4y agoHow about this: We can make a struct and then have a function that bails out if for some reason the fields are not adjacent in the struct. Possibly even at compile time. In practice, this will work on basically any compiler. Once we know we have this struct, are there any remaining barriers to the cast and -1 access? >> Casting a char* to a pointer of any type other than a char* and dereferencing it is usually in violation of the strict aliasing rule. How does that logic work with malloc, which unlike calloc takes no item size and simply gives you a specific number of bytes?
- wizeman 4y ago> Once we know we have this struct, are there any remaining barriers to the cast and -1 access? I think the only problem is that malloc() wouldn't know the type of the non-size_t field, so it couldn't construct such a struct to do a conforming allocation which would allow calloc() to do those operations. But if the compiler doesn't have access to the malloc() implementation (assuming it doesn't know that malloc() is a special function), then maybe that doesn't invoke undefined behavior? I'm not sure, actually! But I think that this whole saga is actually a consequence of the standard being flawed, in that I think it's not possible to create an implementation of malloc() using strict standard C (but I could be wrong, I'm not exactly an expert on the C standard). > How does that logic work with malloc, which unlike calloc takes no item size and simply gives you a specific number of bytes? The return value of malloc() is treated specially by the standard, I believe. Which is why musl has to use the `-ffreestanding` and `-fno-builtin` flags, I think (to avoid optimizations by the compiler due to special knowledge of standard functions), which are non-standard. Well, musl also uses '-fno-strict-aliasing' (also a non-standard flag) which probably is what actually allows it to implement malloc() without invoking undefined behavior. I think this is also why GCC provides a "malloc" attribute which you can use in your own functions, so that they are treated specially (like malloc()), but I believe that this is also non-standard.
- Dylan16807 4y agoWhat I mean is that malloc doesn't know the size of any of the fields. Let's assume for the sake of simplicity that `sizeof element` is the same as `sizeof size_t` or a multiple of it. We don't need to assume this, it just makes the math nicer for the moment. I do: char* buffer = malloc((count + 1) * sizeof element); Or I do: char* buffer = calloc(count + 1, sizeof element); How is C supposed to know what goes in that buffer? Maybe it's an array of elements. Maybe it's an array of size_t[sizeof element / sizeof size_t]. Maybe it's an array of union { element, size_t[sizeof element / sizeof size_t]}. If I want to store an `element` at a location `sizeof element` bytes into the buffer, how can it say no? If I want to store a `size_t` at a location `sizeof element - sizeof size_t` bytes into the buffer, how can it say no? If I want to do both, how can it say no?