Quick review of the first section of Pointer notes:

When we assign a data type to a variable, that tells us:

  1. The amount of memory we’re looking at (size in bytes)
  2. How to interpret the memory at that location

Recall that the following binary can be interpreted as 3:

1st byte2nd byte3rd byte4th byte
00000000000000000000000000000011

And the following binary can be interpreted as 3.0f:

Sign (1 bit)Exponent (8 bits)Mantissa/Significand (23 bits)
01000000010000000000000000000000

Also recall: integral values in C are: char, short, int, long long

And that we can have signed and unsigned integral values.

True or False

  1. An unsigned integer and a signed integer can represent the same number of values
  2. An unsigned int and signed int are the same size in memory
  3. An unsigned int and signed int can represent the same range of values (meaning the VALUES they represent are all THE SAME)
  4. The conversion from binary to unsigned int is the same conversion used for binary to signed int
  5. An unsigned int is intended to only represent positive values
  6. The previous true/false answers are the same for chars, shorts, and long longs.

Answers

1.True (they’re the same size) 2. True (they’re the same size) 3. False (unsigned range is 0 on up, signed range includes negatives) 4. False (the range of values is different, so conversion is different) 5. True 6. True (same for the other 3 integral types)

Knowing data types, their sizes, and interpretations is important when trying to understand pointers. Integers are represented by 32 bits of binary, and we can convert those 32 bits into a whole-number integer value. Floats are represented by 32 bits of binary, and we can convert those 32 bits into a floating-point number (IEEE 754, a standard for floating point arithmetic, illuminates how to do this).

Similarly, a pointer is a variable whose value is a memory address (a number). We can convert the bits of the value stored in a pointer variable into a memory address.

We already know how to get a memory address value: we use the address operator on a variable. We can store that value in a pointer. The pointer’s type must match the type we used the address operator on (said another way: when we use the address operator on an int variable, we store the memory address in an int pointer):

int x = 825;
 
int* pX = &x;

When the value of a pointer is a memory address of a variable, we say the pointer “points” to that variable. We can have more than one pointer “pointing” at a memory location:

int x = 825;
 
//All of these point to the same place, 
//meaning the value stored in each of 
//these pointer variables is the same
int* pX = &x;
int* p1 = &x;
int* p2 = p1;
int* p3 = p2;

We could even make an array full of pointers, all pointing to the same location in memory:

int myFace = 82;
 
int* fingers[100];
 
for(int i = 0; i < 100; ++i)
{
	fingers[i] = &myFace;
}

We also learned of two very powerful things pointers allow us to do: the indirection operator, and getting around C’s convention of “pass by value”.

The indirection operator allows us to treat a pointer as the thing it points to.

Question

What is the value of x after the code below runs?

int x = 10;
int * pX = &x;
(*pX) *= 10;

Answer

100 The value of x is 10. We then set a pointer, pX, equal to the address of x. Finally, we use the indirection operator to treat pX as if it were x, and then multiply it’s value by 10, storing that value back in x.

Remember not to be confused by the 3 different meanings behind the asterisk (*) in C!

  1. Multiplication
  2. Declaring a pointer variable
  3. Dereferencing a pointer (indirection operator)

Question

What does it mean that C functions can only pass by value?

Answer

When parameters are passed to a function, the values are copied; we cannot “pass” a variable directly to a function

We get around this by passing the location of variables instead, and then altering the value at that location in memory via the dreference operator. You can see a non-working example below in BadSwap, and a working example in GoodSwap.

//Remember, this doesn't accomplish anything, and is total garbage
void BadSwap(int a, int b);
{
	int swap = a;
	a = b;
	b = swap;
}
 
//Swaps values of whatever integers a and b point at, for real
void GoodSwap(int* a, int* b);
{
	int swap = *a;
	*a = *b;
	*b = swap;
}
 

Arrays and Pointers: Function Calls

There’s some interesting stuff we didn’t dive into when we originally talked about arrays. Remember, arrays are just lists of stuff:

//Create an array
int myIntegers[10] = {0};
 
//Use a loop to access the elements
for(int i = 0; i < 10; ++i)
{
	myIntegers[i] = i;
}
 
//access an individual element
myIntegers[7] = 77777;
 

We didn’t talk about passing arrays to functions, but you can totally do that. Let’s complete the following function, for fun!

 
int SumArray(int myArray[], int size)
{
	//What goes here?
}
 

So, we have a function, we pass an array to it, and we sum the values and return that sum. Great! What if we did something a bit different? (a bit contrived, but roll with it)

 
void FillWithZeros(int myArray[], int size)
{
	for(int i = 0; i < size; ++i)
	{
		myArray[i] = 0;
	}
}
 
int main(void)
{
	int originalArray[5] = {1, 2, 3, 4, 5};
	for(int i = 0; i < 5; ++i)
	{
		printf("%d ", originalArray[i]);
	}
	
	printf("\n");
	FillWithZeros()
	for(int i = 0; i < 5; ++i)
	{
		printf("%d ", originalArray[i]);
	}
}
 

Now, there shouldn’t be any real reason to use FillWithZeros. Remember, we’re passing by value! That means we theoretically would have copied our array into this function, set all the elements in the copy to zero, then just discarded that whole thing when we returned from the function (remember, the return type of FillWithZeros is void). If that’s true, then the two print loops in our main function will print the same thing …except they don’t?

1 2 3 4 5
0 0 0 0 0

What the heck is going on here? Why does it look like the original array is being altered? That shouldn’t be possible! Well, let’s think about this a bit.

We know that in C, we only pass arguments/parameters by value. What does this actually mean? Basically, we copy the arguments to some other place in memory, then run the instructions for the function we just called.

Say our function takes a float, and an int as arguments. How much memory do we need to copy?

float (4 bytes) + int (4 bytes) => 8 bytes

OK, that doesn’t seem too bad! So, what would happen if your function had to copy an array? Say it only has 5 integers:

int(4 bytes) * 5 => 20 bytes

Still not terrible! But what if you had an array of 100000 elements?

int(4 bytes) * 100000 => 400000 bytes, 0.4MB

Holy mega-bytes, Batman! It wouldn’t make much sense to copy this much memory around just for a function call. But then, how would we ever use arrays in functions at all?

Well we just confirmed via our code and printed output that we’re NOT copying arrays around when passing them into functions. So what’s actually happening? Turns out, when you pass an array to a function, you’re really just passing a pointer to the start of the array! Why copy 100000 elements-worth of data when you can just pass the address in memory where that data starts, and just access it directly?

So turns out, these are the exact same thing:

//Passing in an int pointer
int SomeFunction( int* someAddress, int size);
 
//Passing in an int array
int SomeFunction( int someAddress[], int size);

But that raises some more questions… if the two function declarations above are essentially the same thing… can we treat pointers and arrays like they’re the same?

Array Subscript Notation

Recall, with arrays, that we use the square brackets (called subscript notation) to access individual elements:

 
int myIntegers[10] = {0};
 
myIntegers[0] = 2749;
 
int whatever = myIntegers[3];

Let’s think about what the subscript notation (square brackets) is actually DOING when we access array elements. With that last line of code above, we are looking at the 4th space in a chunk of memory that contains 10 integers. All of the elements in the array are right next to each other in memory.

Well, think about what we’d get if we did the following:

int myIntegers[10];
 
int* firstElement = &(myIntegers[0]);

Don’t panic! This looks complicated and weird, but it isn’t so bad. We’ve seen all this stuff before. We’re just using a lot of operators at once. Let’s break it down below:

//Splitting up the stuff above
 
//Create an array of 10 integers, all next to each other in memory
int myIntegers[10];
 
//Create an integer pointer, whose value will be a memory address
int* firstElement;
 
//Access the first element of the array. This represents an actual 
//location in memory, where we can store integer values.
//For all intents and purposes, this is an integer variable.
myIntegers[0];
 
//The address operator gives us the address of this integer. 
//Namely, it's the address of the first element in our array.
&(myIntegers[0]);
 
//We're setting the value of our pointer to be the memory address of
//the first element in our integer array
firstElement = &(myIntegers[0]);
 

So, putting it back together:

//Create an integer array
int myIntegers[10];
 
//Create a pointer, and set its value to be the address 
//of the first element in our integer aray
int* firstElement = &(myIntegers[0]);

Sweet! Now we know how to describe what we’re doing above, and it makes sense. OK, so… what if we did this?

//Create an integer array
int myIntegers[10];
 
//Create a pointer, and set its value to be the address 
//of the first element in our integer aray
int* firstElement = &(myIntegers[0]);
int* secondElement = &(myIntegers[1]);
int* thirdElement = &(myIntegers[2]);

Let’s assume/contrive that the memory value of firstElement, after we set it above, is 200 (our int starts at byte 200). What would be the value of secondElement? Of thirdElement? (Hint: remember that ints are 4 bytes in size, and we measure memory addresses in bytes).

You could think of myIntegers[1] as going to the memory address where the array starts, then moving forward 4 bytes to look at the next int in memory. Similarly, you could think of myIntegers[2] as going to the address where the array starts, then moving forward 8 bytes to look at that int in memory:

int myIntegers[10];
myIntegers[0]; //Start of array
myIntegers[1]; //Start of array + 4 bytes
myIntegers[2]; //Start of array + 8 bytes
myIntegers[3]; //Start of array + 12 bytes

We could also do the same thing for a char array:

char myChars[10];
myChars[0]; //Start of array
myChars[1]; //Start of array + 1 bytes
myChars[2]; //Start of array + 2 bytes
myChars[3]; //Start of array + 3 bytes

So, you could think of the subscript operator as something like:

myIntegers[i] ---------> Go to address of myIntegers + (i * size of data type)  

Cool! Neat! What does this have to do with pointers?

Pointer Arithmetic

Well, it turns out we can do pointer addition, like so:

int x = 76;
int* pX = &x;
++pX;

WHAAAAAAAT? WHAT IS GOING ON? We’re using the ++ operator on a pointer? What the heck?

So, to break down what’s happening above: we’re creating an integer, x. Then, we create an int pointer pX and store the address of x in it. Cool, we’ve soon that before. Finally, we… increment the value of pX? What does this mean, and why would we even do it?

Well, in this context this behavior is actually worse than useless, it’s sorta harmful. We’ve changed the value of pX, and it is no longer pointing to something that makes any sense to point at. But what if there were a context in which this behavior were valid, and useful?

int myIntegers[10];
 
//Create a pointer, and set its value to be the address 
//of the first element in our integer aray
int* pElement = &(myIntegers[0]);
++pElement;

So, we have an array, we set our pointer to point at the address of the first element. Then, we increment the pointer, which… we still haven’t explained what that’s actually doing. It changes the value of the pointer, meaning it is no longer pointing at the first element in the array. So… where is it pointing then? What does adding 1 to a pointer actually do?

Answer: it depends on the TYPE of the pointer!

Consider the following:

int myIntegers[10];
char myChars[10];
 
int* pInt = &(myIntegers[0]);
++pInt;
 
char* pChar = &(myChars[0]);
++pChar;
 

Let’s assume/contrive that the memory value of pInt is 200, before we increment it. What would be the value after we increment it?

Let’s further assume/contrive that the memory value of pChar is 500, before we increment it. What would be the value after we increment it?

It turns out, incrementing a pointer will shift the memory address forward X bytes, where X is the size of the data type it points to.

For our integer example above, the value of pInt after incrementing would be 204. For our char example above, the value of pChar after incrementing would be 501.

Hmm… where have we seen something like this before?

int myIntegers[10];
 
myIntegers[0]; // Assume at address 200
myIntegers[1]; // Address 204
 
int* pInt = &(myIntegers[0]); //Address 200
++pInt; //Address 204

So that means… incrementing a pointer is the same as using the square brackets? OK? Well it ALSO turns out, that we can do the following:

int myIntegers[10];
 
myIntegers;

Wait, so myIntegers can just… hang out? As an expression? What’s the value of this expression? Well earlier we mentioned that when we’re passing an array into a function, we’re actually passing the address of where the array starts (the first element). Which means myIntegers is just the address of the first element of the array! So the two lines below are the same:

int myIntegers[10];
 
//THE TWO LINES BELOW ARE THE SAME!
myIntegers;
&(myIntegers[0]);
 

Which FURTHER means:

int myIntegers[10];
 
myIntegers[1] = 365;
 
*(myIntegers+1) = 100;
 

The value of the second element of the myIntegers array after this code runs will be… 100!

There will be situations where it is more convenient to use pointer arithmetic, and other situations where array subscript notation will be easier. You’ll run into these situations over time, but a good rule of thumb would be: if you already have what you KNOW to be an array, stick to subscript notation. If you have a chunk of memory that represents something that is NOT an array, but you need to navigate within those addresses, use pointer arithmetic. If you aren’t sure what the sentences above are trying to say, don’t worry too much; we’ll be encountering these situations soon!