Storing an integer and bit in a single word - c

I was reading through a presentation on the implementation of malloc, and on slide 7 it suggests storing a regions size and availability in a single word to save space. The alternative is to use two words, which is wasteful as the availability bit only needs to be 0 or 1.
This is the given explanation:
If blocks are aligned, low-order address bits are always 0
Why store an always-0 bit?
Use it as allocated/free flag! When reading size word, must mask out this bit
http://courses.engr.illinois.edu/cs241/sp2012/lectures/09-malloc.pdf
But I'm not really understanding how this works and how it could be implemented in C. Why is one bit of the size integer always 0?

If blocks are aligned, low-order address bits are always 0
This is the key to understanding what it going on. Many CPUs require that multibyte primitive values be stored at addresses divisible by the number of bytes in the primitive: 16-bit primitives need to be stored at even addresses; 32-bit ints need to be stored at addresses divisible by four, and so on. An attempt to access an int through a pointer that corresponds to an odd address results in a bus error.
In systems like that malloc must always return an address suitable for storing any primitive supported by the given CPU. Therefore, if CPU supports 32-bit integers, all addresses returned by malloc must be divisible by 4. Such addresses are said to be aligned. To comply, malloc implementations pad sizes blocks requested by the program by 0 to 3 bytes at the end to have length divisible by 4. As a consequence of this decision, the last two bits of an address of an aligned block will always be zero. An implementation of malloc can use these bits for its own purposes, as long as they are "masked out" before returning the result to callers.

malloc(3) (as specified by Posix) should
return a fresh block of memory; or NULL on failure; the returned pointer is not an alias of any other pointer in the program
return a suitably aligned block of memory. Alignment constraints are compiler, ABI, and processor specific. (Often, the alignment should be two words).
The size is not always zero. (actually, it is never zero). You could round it up to a multiple of two words, and use the last bit as a used/free bit.
However, pointers returned by malloc should be suitably aligned, e.g. to 8 bytes. So their bottom 3 bits are zero, and the allocated size in bytes of the malloc-ed zone is a multiple of 8 bytes (above the requested size passed to malloc), so the last 3 bits are zero (and you could use the last bit for other purposes, e.g. a used/free bit).

Why is one bit of the size integer always 0?
I see why this is confusing, but I don't think that's what they're saying on slide 7. They're saying that the low-order address bits are always 0.
Memory addresses of objects are aligned to specific boundaries, which means objects are aligned to a memory address that is a multiple of their size.
So a 64-bit integer is aligned to an eight-byte boundary;
0x7fff315470d8
If a pointer is always aligned to an eight-byte boundary, then the low-order three bits are always zero. ie: 0x816 = 10002
Basically, you can stick whatever you want in those low-order bits, so long as you take them out before dereferencing the pointer. In the 64-bit case you have 3 bits that are always 0, so you can store 3 "flags". In the case of this power point, they're saying take that lowest bit and use it for an "allocated" flag. Stick a 1 in it as long as the memory is allocated, mask it out when you send the pointer to the user.

Related

Data layouts used by C compilers (the alignment concept)

Below is an excerpt from the red dragon book.
Example 7.3. Figure 7.9 is a simplification of the data layout used by C compilers for two machines that we call Machine 1 and Machine 2.
Machine 1 : The memory of Machine 1 is organized into bytes consisting of 8 bits each. Even though every byte has an address, the instruction set favors short integers being positioned at bytes whose addresses are even, and integers being positioned at addresses that are divisible by 4. The compiler places short integers at even addresses, even if it has to skip a byte as padding in the process. Thus, four bytes, consisting of 32 bits, may be allocated for a character followed by a short integer.
Machine 2: each word consists of 64 bits, and 24 bits are allowed for the address of a word. There are 64 possibilities for the individual bits inside a word, so 6 additional bits are needed to distinguish between them. By design, a pointer to a character on Machine 2 takes 30 bits — 24 to find the word and 6 for the position of the character inside the word. The strong word orientation of the instruction set of Machine 2 has led the compiler to allocate a complete word at a time, even when fewer bits would suffice to represent all possible values of that type; e.g., only 8 bits are needed to represent a character. Hence, under alignment, Fig. 7.9 shows 64 bits for each type. Within each word, the bits for each basic type are in specified positions. Two words consisting of 128 bits would be allocated for a character followed by a short integer, with the character using only 8 of the bits in the first word and the short integer using only 24 of the bits in the second word. □
I found about the concept of alignment here ,here and here. What I could understand from them is as follows: In word addressable CPUs (where size is more than a byte), there certain paddings are introduced in the data objects, such that CPU can efficiently retrieve data from the memory with minimum no. of memory cycles.
Now the Machine 1 here is actually a byte address one. And the conditions in the Machine 1 specification are probably more difficult than a simple word addressable machine having word size of say 4 bytes. In such a 64 bit machine, we need to make sure that our data items are just word aligned ,no more difficulty. But how to find the alignment in systems like Machine 1 (as given in the table above) where the simple concept of word alignment does not work, because it is byte addressable and has much more difficult specifications.
Moreover I find it quite weird that in the row for double the size of the type is more than what is given in the alignment field. Shouldn't alignment(in bits) ≥ size (in bits) ? Because alignment refers to the memory actually allocated for the data object (?).
"each word consists of 64 bits, and 24 bits are allowed for the address of a word. There are 64 possibilities for the individual bits inside a word, so 6 additional bits are needed to distinguish between them. By design, a pointer to a character on Machine 2 takes 30 bits — 24 to find the word and 6 for the position of the character inside the word." - Moreover how should this statement about the concept of the pointers, based on alignment is to be visualized (2^6 = 64, it is fine but how is this 6 bits correlating with the alignment concept)
First of all, the machine 1 is not special at all - it is exactly like a x86-32 or 32-bit ARM.
Moreover I find it quite weird that in the row for double the size of the type is more than what is given in the alignment field. Shouldn't alignment(in bits) ≥ size (in bits) ? Because alignment refers to the memory actually allocated for the data object (?).
No, this isn't true. Alignment means that the address of the lowest addressable byte in the object must be divisible by the given number of bytes.
Additionally, with C, it is also true that within arrays sizeof (ElementType) will need to be greater than or equal to the alignment of each member and sizeof (ElementType) be divisible by alignment, thus the footnote a. Therefore on the latter computer:
struct { char a, b; }
might have sizeof 16 because the characters are in distinct addressable words, whereas
struct { char a[2]; }
could be squeezed into 8 bytes.
how should this statement about the concept of the pointers, based on alignment is to be visualized (2^6 = 64, it is fine but how is this 6 bits correlating with the alignment concept)
As for the character pointers, the 6 bits is bogus. 3 bits are needed to choose one of the 8 bytes within the 8-byte words, so this is an error in the book. An ordinary byte would select just a word with 24 bits, and a character (a byte) pointer would select the word with 24 bits, and one of the 8-bit bytes inside the word with 3 bits.

How do pointers reference multi-byte variables?

I am confused as to how C pointers actually reference the memory address of a variable. I am probably missing something here, but if, for example an int is 32 bits (like in C), then this would be stored in 4 bytes.
If I am not mistaken then each memory address tends to be a byte in size, as these are generally the smallest units of addressable memory. So if an int takes up 4 bytes, then wouldn't it have 4 memory addresses? (as it is stored over 4 8-bit memory addresses).
If this is the case, then how come a pointer only holds one memory address? (or rather only displays one when printed, if it holds more?). Is this simply the first address that stores the int? (assuming they are stored contiguously).
I have tried to find answers online but this has only led to further confusion.
Yes, technically, there would be four addressable bytes for the int you describe. But the pointer points to the first byte, and reading an int from it reads that byte and the subsequent three bytes to construct the int value.
If you tried to read from a pointer referring to one of the other three bytes, at the very least you'd get a different value (because it would read the remains of the one int, and additional bytes next to it), and on some architectures which require aligned reads (so four byte values must begin at an address divisible by four), your program could crash.
The language tries to protect you from reading a misaligned pointer like that; if you have an int*, and add 1 to it, it doesn't increment the raw address by one, it increments it by sizeof(int) (for your case, 4), so that pointers to arrays of int can traverse the array value by value without accidentally reading a value that's logically partially from one int, and partially from its neighbor.
Pointer points to the starting address of your type, if you google "pointer size" it will show you it is generally dependent to your cpu architecture, not to your primitive type or object.
What is the size of a pointer?
which will hopefully support your thoughts although the question is about c++
One byte is the smallest addressable unit, but that doesn't mean an address is only one byte. Otherwise you'd only have 256 bytes you could address! Pointers are typically either 4 or 8 bytes on size
The address of a variable refers to the address of it's first byte. The remaining bytes are understood to immediately follow those, and the number of bytes are part of the datatype.
The specifics depend on the actual architecture of the machine (what kind a CPU, what kind of memory, etc) so I am assuming you care about a modern 32 bit processor where both an int and a pointer take four bytes of memory. Keep in mind that the same ideas apply when integers are two bytes and when pointers are 8 bytes but we have to focus on just one set of examples.
Having said all that, you are completely correct that an int uses four contiguous bytes of memory which means it has four separate memory addresses and that a pointer holds only one address - it is the address of the first byte of the int.
So the CPU has an instruction for reading an int. The instruction takes the address of the first byte of the int and reads an entire int - all four of them. And that's why you only need one address to read an entire int. So int i = 42 reads a four byte integer into i and the value is interpreted to mean the number 42.
But a pointer is also an integer where the value is a memory address, so it can be read exactly the same way. So int *p = 42 reads a four byte integer into p and the value is interpreted to mean memory address 42.
All this gets complicated when you start taking about the order the bytes are stored in so we won't talk about that (however, if you want to find out the term is endianness - see https://en.wikipedia.org/wiki/Endianness)

Does malloc() have a maximum return value?

Does the size_t value of the virtual memory pointer returned by malloc() have an upper boundary?
I am wondering whether I can safely set the most significant bit of a 64 bits pointer to indicate that this is not a pointer but a literal integer instead.
malloc returns a void* not an integer. Casting a pointer to an integer is not giving you the (virtual memory) address, but some value that has to adhere to the semantics as defined in the C language standard (0 for a null pointer and adding and subtracting is related to pointer arithmetic), but that's about it.
You must make no assumptions whatsoever about the values of pointers-cast-to-integers other than that. As a matter of fact a C implementation may very well be in its right to tag non-null pointer cast to integer with some internal information in the upper bits.
As #datenwolf's answer states, you can't make any assumptions about how malloc is providing you the memory address. The MSB may well contain important bits that you could overwrite, if you attempted to use them to store meta data. I have worked on a 32-bit system that returned addresses with bits set in the MSB of addresses (not from malloc, but other system specific memory allocation functions).
However, it is guaranteed that malloc will return an address that is suitably aligned for your system. For example, on a 32-bit system, you'll get a 4-byte aligned pointer, and on 64-bit, you'll get an 8-byte aligned pointer. This means that you are guaranteed that the lower 2 or 3 bits respectively will be zero. You could increase the number of guaranteed bits by using memalign instead. It essentially is the same effect as storing meta data in the most significant bit. To get/set the literal, you can just up/down shift it into the remaining bits.
However, I wouldn't suggest either method. Save yourself some heartache, and allocate just a little more memory to store the flag. Unless you've got billions of them, it's really not worth it.
As for the size_t is concerned it can hold a value defined by limits.h
#ifndef SIZE_MAX
#ifdef _WIN64
#define SIZE_MAX _UI64_MAX
#else
#define SIZE_MAX UINT_MAX
Where _UI64_MAX and UINT_MAX are defined as followes.
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define _UI64_MAX 0xffffffffffffffffui64
And as for the malloc() is concerned, on a 32 bit Windows it can return any (address) value within zero to 2 GB user-mode address space and on a 64 bit Windows it can return any (address) value within zero to 8 TB user-mode address space.
Again, on a 32 bit system, from WinNT 4 onwards it introduced a boot option /3G. With this, malloc() can return any (address) value within zero to 3 GB user-mode address space.
For more details have a look at Mark Russinovich's article here.

How to prevent arithmetic operations between different kinds of integers?

I am working on a debugger (in C) which deals with embedded system whose memory has an addressable size of 16-bits. This means that at address x you have a 16-bits value, at (x + 1) you have another 16-bits value, etc. When working with data that comes from the target memory, I therefore have to convert the lengths between target bytes lengths and host bytes lengths. If I want to read 10 (16-bit) bytes from the target, I therefore have to allocate a 20 (8-bit) bytes in the debugger, which runs on the host.
A big number of bugs come from the fact that I forget to do these conversion, and I do semantically invalid arithmetic operations, such as adding an host memory address (i.e. the buffer allocated on my host) to a length in target bytes lengths. This makes no sense, and the length should be converted to an equivalent host bytes length prior to the addition (effectively multiply the length by 2).
My question is: is there any way that I can get the compiler to protect me from my own mistakes?
Both length types are integers. Is there way, for example, through type magic, to tell the compiler that this kind of integer can't be added to this other type of integers, for example?
Thanks!

Map memory to another address

X86-64, Linux, Windows.
Consider that I'd want to make some sort of "free launch for tag pointers". Basically I want to have two pointers that point to the same actual memory block but whose bits are different. (For example I want one bit to be used by GC collection or for some other reason).
intptr_t ptr = malloc()
intptr_t ptr2 = map(ptr | GC_FLAG_REACHABLE) //some magic call
int* p = int*(ptr);
int* p2 = int*(ptr2);
*p = 10;
*p2 = 20;
assert(*p == 20)
assert(p != p2)
On Linux, mmap() the same file twice. Same thing on Windows really, but it has its own set of functions for that.
Mapping the same memory (mmap on POSIX as Ignacio mentions, MapViewOfFile on Windows) to multiple virtual addresses may provide you some interesting coherency puzzles (are writes at one address visible when read at another address?). Or maybe not. I'm not sure what all the platform guarantees are.
More commonly, one simply reserves a few bits in the pointer and shifts things around as necessary.
If all your objects are aligned to 8-byte boundaries, it's common to simply store tags in the 3 least-significant bits of a pointer, and mask them off before dereferencing (as thkala mentions). If you choose a higher alignment, such as 16-bytes or 32-bytes, then there are 3 or 5 least-significant bits that can be used for tagging. Equivalently, choose a few most-significant bits for tagging, and shift them off before dereferencing. (Sometimes non-contiguous bits are used, for example when packing pointers into the signalling NaNs of IEEE-754 floats (223 values) or doubles (251 values).)
Continuing on the high end of the pointer, current implementations of x86-64 use at most 48 bits out of a 64-bit pointer (0x0000000000000000-0x00007fffffffffff + 0xffff800000000000-0xffffffffffffffff) and Linux and Windows only hand out addresses in the first range to userspace, leaving 17 most-significant bits that can be safely masked off. (This is neither portable nor guaranteed to remain true in the future, though.)
Another approach is to stop considering "pointers" and simply use indices into a larger memory array, as the JVM does with -XX:+UseCompressedOops. If you've allocated a 512MB pool and are storing 8-byte aligned objects, there are 226 possible object locations, so a 32-value has 6 bits to spare in addition to the index. A dereference will require adding the index times the alignment to the base address of the array, saved elsewhere (it's the same for every "pointer"). If you look at things carefully, this is simply a generalization of the previous technique (which always has base at 0, where things line up with real pointers).
Once upon a time I worked on a Prolog implementation that used the following technique to have spare bits in a pointer:
Allocate a memory area with a known alignment. malloc() usually allocates memory with a 4-byte or 8-byte alignment. If necessary, use posix_memalign() to get areas with a higher alignment size.
Since the resulting pointer is aligned to intervals of multiple bytes, but it represents byte-accurate addresses, you have a few spare bits that will by definition be zero in the memory area pointer. For example a 4-byte alignment gives you two spare bits on the LSB side of the pointer.
You OR (|) your flags with those bits and now have a tagged pointer.
As long as you take care to properly mask the pointer before using it for memory access, you should be perfectly fine.

Resources