News for hackers. Stuff that matters.
Re:Why Does Calloc Exist? by Dylan16807 on Sunday November 13, 2022 @09:55PM (#33589756)
What 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?

Re:Why Does Calloc Exist? by salawat on Monday November 14, 2022 @09:28AM (#33594364)
Read the source.

It returns a pointer to a chunk of memory of sufficent fore size_t (unsigned, positive whole number of elements) where each of those elements is size_t (unsigned, positive whole number) in size within the limits of your computationally representable chunk of numbers.

No code "knows" squat about the implementation. You can know. You're the programmer, you can read the code. The computer just does. Even if that causes demons to fly out your nose.

Friends don't let friends write code without checking whether it'll cause demons to fly out their nose first. If they ain't your friend though, there's always checking ERRNO.

Re:Why Does Calloc Exist? by wizeman on Monday November 14, 2022 @10:33AM (#33595452)
I think you may be right and I was wrong.

I think I was operating under more conservative assumptions about what is actually legal to do in C.

I actually had to go and read the standard because the strict aliasing rules can be very tricky and it's not always clear what is legal to do in every situation. Even then, it's hard to understand the wording of the standard and its implications, especially since there are multiple rules coming into play and there's no clear explanation of how they interact with each other.

In this specific case that we're talking about, I think you can indeed allocate a buffer, store the malloc-returned pointer in a `char *` variable, and then store an element and a size_t wherever you want within the buffer (but if you want to store them or access them using a properly-typed pointer rather than memcpy() then the address needs to respect the alignment of the type, I think).

But I think this is only legal because the addresses within a buffer returned by malloc are not considered to have a declared type and that the type of some address within the buffer becomes established whenever you assign or copy some value to it, so it doesn't actually necessarily become established based on the valid conversions between pointer types like I thought it was.

I'm still not exactly 100% certain about what the expression we're talking about is actually undefined behavior or not, because on one hand, the standard clearly says that dereferencing a pointer which points one past the end of an array is undefined behavior (and `p` could be considered such a pointer), but on the other hand, it's hard to see how this can be the case when `p` can be a perfectly valid pointer to some other value within the same allocation.

The rule which says that it's undefined behavior to dereference a pointer that points one past the end of an array (which can be a single value) is inside the pointer arithmetic section. So my interpretation is that, if there is a valid and properly-typed value stored at the address of a pointer that also points to one past the end of an array, it's only undefined behavior to dereference such a pointer if the address of that pointer was obtained through pointer arithmetic based on a pointer that also pointed to the array.

So, if my interpretation is correct, I think in this case the rules are subtle: it seems that dereferencing `((size_t *) p)[-1]` is legal if there already is a `size_t` stored at that address (I think?). But it doesn't seem to be legal to dereference `(((size_t *) p)[-1])[1]` (unless `p` points to some non-first element of an array). However, it would be legal to dereference `*((size_t *) p)`, but only if the address represented by `p` already contains a `size_t` there, I think (??).