Data Types: ranges and interpretations
Recall, that data in memory looks like this:
| Address 0x4872 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
|---|
Our bits can be set to whatever we choose! And we describe those bits using data types:
An int is 4 bytes, and we interpret the following data as the value 3:
| 1st byte | 2nd byte | 3rd byte | 4th byte |
|---|---|---|---|
| 00000000 | 00000000 | 00000000 | 00000011 |
| (we have 2^0 + 2^1, which is 3) |
A float is 4 bytes, and we interpret the following data as the value 3.0f:
| Sign (1 bit) | Exponent (8 bits) | Mantissa/Significand (23 bits) |
|---|---|---|
| 0 | 10000000 | 10000000000000000000000 |
(we’re not gonna get into this right now… but this is 3.0f in binary, trust me)
Integral types (this is specific to MSVC)
| Type | Size in bytes |
|---|---|
| char | 1 |
| short | 2 |
| int | 4 |
| long long | 8 |
Integral types (more detailed)
| Type | Size in bytes | Binary Range | Decimal Range |
|---|---|---|---|
| char | 1 | 10000000 to 01111111 | -128 to 127 |
| unsigned char | 1 | 00000000 to 11111111 | 0 to 255 |
| short | 2 | 1000000000000000 to 0111111111111111 | -32768 to 32767 |
| unsigned short | 2 | 0000000000000000 to 1111111111111111 | 0 to 65535 |
| int | 4 | 10000000000000000000000000000000 to 01111111111111111111111111111111 | -2,147,483,648 to 2,147,483,647 |
| unsigned int | 4 | 00000000000000000000000000000000 to 11111111111111111111111111111111 | 0 to 4,294,967,295 |
| signed long long | 8 | 1 followed by 63 zeros to 0 followed by 63 ones | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
| unsigned long long | 8 | [ 64 zeros ] to [ 64 ones ] | 0 to 18,446,744,073,709,551,615 |
Floating point types (these are only signed)
| Type | Size in bytes |
|---|---|
| float | 4 |
| double | 8 |
| (not going to dive into floating point precision here) |
OK, so we have a bunch of different types. And the integral types can be signed/unsigned. Depending on if they’re signed or unsigned, we interpret the data a different way. Great, neat, wow!
To reiterate: Variables hold VALUES. Values are just bits that we interpret a certain way. Variables exist at some LOCATION in memory. For example:
//This is somewhere in memory, and holds a value
int playerHealth = 50;
//Same here!
float playerSpeed = 7.5f
//And same with this array!
char myLetters[26];QUESTION
What is the line of code for setting the first element of the array above to the value of ‘a’? What is the line of code for setting the last element of the array above to the value of ‘z’?
ANSWER
myLetters[0] = 'a';myLetters[25] = 'z';
The only thing we can say about these variables, in relation to their memory, is that they are SOMEWHERE in our program’s memory. The only guarantee about memory location is that we know all of the elements in the character array exist next to each other, sequentially, in memory. But we have no knowledge or guarantee about where playerHealth, playerSpeed, or ‘myLetters’ are located in relation to each other at all.
Memory Addresses
So, what do memory addresses look like? We actually have an operator that can tell us the memory address of a variable. We’ve used it before:
//Get user input to store into our in variable, x
int x = 0;
scanf("%d", &x);What if we wanted to know what the memory address of x was? Just for fun? There’s a couple ways we can find out. Maybe the easiest way is to pull up the Visual Studio Debugger, or by inspecting our variables in the Locals, Watch, or Memory windows.
If we wanted to know without using external tools, we could just print out the memory address! There’s a format specifier for that:
int x = 0;
printf("%p", &x);When this code runs you’ll likely see a very large hexadecimal number. And it’s a memory address. Cool?
What if we wanted to have variables that could store a memory address as a value? Similar to how we store integers, floats, etc as values? Well we can totally do that!
Remember (in general):
An integer variable is 4 bytes in size, and the bits inside are interpreted as an integer value (Examples: 72, 2000000, -465) (meaning we can do a conversion between binary and integer)
A float variable is 4 bytes in size, and the bits inside are interpreted as a floating point value (Examples: 3.14f, 0.0001f, -2000.084f) (meaning we can do a conversion between binary and floating point)
A char is 1 byte in size, and the bits inside are interpreted as a (smaller) integer value (from 0 to 255, if unsigned) (from -128 to 127) (meaning we can do a conversion between binary and a char value)
So knowing the above, what can we say about a variable that holds a memory address? Well the very least we know is that we can interpret the bits in the variable as an address! There’s some way to convert binary to “memory address”.
We know how to get the memory address value… but how do we CREATE a variable that holds a memory address?
We do so like this:
int* myFirstPointer;Wait, what the heck? What’s going on here? What is this new sorcery? We’re defining a new variable, called myFirstPointer. This variable is NOT an integer type. Instead, it is a new type: int pointer.
| integer type | integer pointer type |
|---|---|
int x; | int* myPointer; |
OK, what’s an int pointer? It’s a variable that stores an address as a value! And not just any address… the address of where an integer is located in memory! So, we’ve had the peanut butter all along:
//Address operator, used on int x. The value of this expression is
//a memory address, but we don't use it, it's just discarded
//because we're not storing it off anywhere
int x;
&x;Now we’ve got the jelly:
//This variable stores a memory address to an int
int* myFirstPointer;Let’s smoosh together this PBJ!
/*Line 1*/ int x;
/*Line 2*/ int* myFirstPointer;
/*Line 3*/ myFirstPointer = &x;QUESTION
What is the value of
myFirstPointeron line 2, when it is created? (Hint: you should be able to answer this without any knowledge of what pointers are!)
ANSWER
Undefined! We didn’t assign any value to it, so it’s likely whatever garbo was lying around in memory.
QUESTION
What is the value of
myFirstPointeron line 3? (Hint: let’s assume, for fun, that our integer x is located at the memory address 0x00b7fbfc) (Hint: I am giving you a super easy T-ball question, it isn’t a trick, the answer is above this text, oh look there it goes, goodbye answer!)
ANSWER
The value of
myFirstPointeris 0x00b7fbfc
Crazy sauce! We know where our integer variable, x, is located in memory. We are storing that location (memory address) as a value in myFirstPointer. Remember, there’s nothing SPECIAL about this yet. myFirstPointer is just another variable, storing another value. It just so happens that the value we’re storing in myFirstPointer is a memory address, which is just a number.
Let’s go over that slowly, again, to make sure we’ve got it.
An int pointer is ???(we don’t know yet) bytes in size, and the bits inside are interpreted as a memory address. (meaning we can do a conversion between binary and a memory address).
Multiple pointer variables can point at the same memory location (meaning, the VALUE of those pointer variables can be the same).
Let’s say it a different way:
Two different integer variables, x and y, can both have the value 5. Two different integer pointer variables, pX and pY, can both have the value 0x00000008.
Alright, so WHY is any of this interesting? So we can store memory addresses in variables, that doesn’t seem very useful. Well, pointers are actually incredibly powerful, due to something called the indirection operator.
Before we introduce the indirection operator, let’s take it slow:
int x = 5;
int* pX = &x;We’ve got an integer, x, and an integer pointer, pX. We say that pX POINTS at x (meaning, the value of pX is the memory location of the variable x). Cool, same stuff we’ve done before.
So let’s introduce our new operator below:
*pX = 10;WHAT THE HECK IS GOING ON!?!?!
The line of code above is using the indirection operator (also called dereference operator). This operator allows us to read/write the value of x, using pX. That last part might sound confusing, so let’s say it again, one more time:
//int x, setting its value to 5
int x = 5;
//int pointer, pX, whose value is the address of x
//we say that pX is POINTING at x
int* pX = &x;
//we are using the indirection operator to treat pX as if it
//were x, so we can read/write the data at x's memory location
*pX = 10;So after that last line of code above runs, the value of x will be 10! Isn’t that wild? We’ve changed the value of x without ever using the actual variable x!
The next section of pointer notes will go into further detail about how we can use this operator to our advantage!