C Primer (Part 2)
This primer contains more information about certain types in C including more information about pointers and arrays.
Contents:
Pointers
Adding “*” to a variable declaration makes the variable type a pointer.
int anInt;
int* anIntPointer;
Applying the “&” operator to a variable returns its address.
int anInt;
int* anIntPointer = &anInt;
int** pointerSquared = &anIntPointer
Applying the “*” operator to a pointer derefrences the pointer and recovers the value it points to.
int anInt = 343;
int* anIntPointer = &anInt;
printf("The int hidden in my little pointy pointer is: %d\n", *anIntPointer);
Arrays
Arrays can actually act as pointers!
Arrays are blocks of contiguous memory used to store multipe elements of the same type. Each “slot” in the array is then an object stored in one of these memory locations. Because the slots are back-to-back, the object stored at location 0 in the array is then also the memory location of the array itself.
Consider the following code snippet (modified from the Pointers, Arrays, Structures website):
#include <stdio.h>
#include <stdlib.h>
int main(){
int myArray[5];
int* myPointer = myArray;
for (int i = 0; i < 5; ++I){
myArray[i] = 2*i;
}
*myPointer = 2023;
}
Pointer Arithmetic
Remember pointers are just addresses which you can dereference. The name of an array behaves like the address of the first element of the array in memory. And addresses are just large numbers. As with any number, we can use arithmatic operations on pointers to reach nearby places in memory.
Let’s look at how we would access the 5th element of an array of 7 characters:
int main() {
char myArray[7] = {'h', 'e', 'l', 'l', 'o', '!', '\0'};
char fifthChar = myArray[4];
}
Since myArray
is the address of the first element in the array, we can instead get the address of the fifth char in the array by just adding 4 bytes to it (each char is a byte long):
char* fifthCharAddress = myArray + 4;
Then all we need to do to recover the fifth character is dereference this memory location:
char fifthChar = *fifthCharAddress;
Or, to shorten the code involved, we can substitute the fifthCharAddress
variable like so:
fifthChar = *(myArray + 4);
In fact, underneath the hood, indexing an array with the []
operator really desugars into an addition and then dereferencing operation like we just did.
String Literals
A string literal is a sequence of chars enclosed in double quotation marks, for example:
char* aWord = "cs0300";
char aPhrase[] = "cs0300 TAs are great...right?";
The compiler automatically adds a terminating NUL character (‘\0’) to string literals. String literals are not modifiable.
char* aWord = "cs0300";
aWord[0] = 'a';
char anotherWord[] = "Hi!";
anotherWord[0] = 'A';
Structures
Structures are a great way of storing multiple pieces of information in one instance. The below example gives way of defining a struct and how to use it.
#include <stdio.h>
struct point2D {
float x;
float y;
};
float distance(struct point2D p1, struct point2D p2) {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) +
(p1.y - p2.y) * (p1.y - p2.y));
}
int main() {
struct point2D p1;
p1.x = 0.5f;
p1.y = 12.5f;
struct point2D p2 = { 20.4f, -9.0f };
printf("The distance from point 1 to point 2 is %f", distance(p1, p2));
}
Declarations vs. Definitions
Since C programs are compiled from top to bottom, the order we define functions in matters. Consider the following example program:
int multiply(int x, int y) {
int ans = 0;
for (int i = 0; i < x; i++) {
ans = add(ans, y);
}
return y;
}
int add(int x, int y) {
return x + y;
}
If you tried to compile this, GCC would spit out an error saying it doesn’t know what the add function is on line 6. This is because we define it on line 11. Function declarations are useful because they enable us to tell the compiler about a function before we define it. Consider this alternate program:
int multiply(int x, int y);
int add(int x, int y);
int multiply(int x, int y) {
int ans = 0;
for (int i = 0; i < x; i++) {
ans = add(ans, y);
}
return y;
}
int add(int x, int y) {
return x + y;
}
This program correctly compiles because our declarations on lines 4 and 5 warn the compiler about the functions before we implement them. Now when it sees the add
function being used on line 11, it won’t freak out even though it hasn’t seen its definition yet.
Header files are files with the extension .h
. These files are technically the exact same as source (.c
) files, except by convention, we put all of our declarations in header files and all definitions in source files.
We call pairs of header and source files modules in C. For example, we could refactor the code in the last section to be a module:
int multiply(int x, int y);
int add(int x, int y);
#include "example.h"
int multiply(int x, int y) {
int ans = 0;
for (int i = 0; i < x; i++) {
ans = add(ans, y);
}
return y;
}
int add(int x, int y) {
return x + y;
}
You might notice the #include
preprocessor directive at the top of example.c
. This line tells the compiler to literally paste the contents of example.h
right on top of the #include
line in example.c
. (You can do this with any file, not just a ‘.h’ file.)
If we have a completley different file which needs to use the functions declared in example.h
, we can use this include directive like so:
#include "example.h"
int main(){
int twelve = add(4, multiply(4, 2));
}
To compile this file, we need to let the compiler know where the contents of example.h
are implemented, and the linker will take care of everything for us:
C Primer (Part 2)
This primer contains more information about certain types in C including more information about pointers and arrays.
Contents:
Pointers
Adding “*” to a variable declaration makes the variable type a pointer.
Applying the “&” operator to a variable returns its address.
Applying the “*” operator to a pointer derefrences the pointer and recovers the value it points to.
Arrays
Arrays can actually act as pointers!
Arrays are blocks of contiguous memory used to store multipe elements of the same type. Each “slot” in the array is then an object stored in one of these memory locations. Because the slots are back-to-back, the object stored at location 0 in the array is then also the memory location of the array itself.
Consider the following code snippet (modified from the Pointers, Arrays, Structures website):
Pointer Arithmetic
Remember pointers are just addresses which you can dereference. The name of an array behaves like the address of the first element of the array in memory. And addresses are just large numbers. As with any number, we can use arithmatic operations on pointers to reach nearby places in memory.
Let’s look at how we would access the 5th element of an array of 7 characters:
Since
myArray
is the address of the first element in the array, we can instead get the address of the fifth char in the array by just adding 4 bytes to it (each char is a byte long):Then all we need to do to recover the fifth character is dereference this memory location:
Or, to shorten the code involved, we can substitute the
fifthCharAddress
variable like so:In fact, underneath the hood, indexing an array with the
[]
operator really desugars into an addition and then dereferencing operation like we just did.String Literals
A string literal is a sequence of chars enclosed in double quotation marks, for example:
The compiler automatically adds a terminating NUL character (‘\0’) to string literals. String literals are not modifiable.
Structures
Structures are a great way of storing multiple pieces of information in one instance. The below example gives way of defining a struct and how to use it.
Declarations vs. Definitions
Since C programs are compiled from top to bottom, the order we define functions in matters. Consider the following example program:
If you tried to compile this, GCC would spit out an error saying it doesn’t know what the add function is on line 6. This is because we define it on line 11. Function declarations are useful because they enable us to tell the compiler about a function before we define it. Consider this alternate program:
This program correctly compiles because our declarations on lines 4 and 5 warn the compiler about the functions before we implement them. Now when it sees the
add
function being used on line 11, it won’t freak out even though it hasn’t seen its definition yet.Header Files
Header files are files with the extension
.h
. These files are technically the exact same as source (.c
) files, except by convention, we put all of our declarations in header files and all definitions in source files.We call pairs of header and source files modules in C. For example, we could refactor the code in the last section to be a module:
You might notice the
#include
preprocessor directive at the top ofexample.c
. This line tells the compiler to literally paste the contents ofexample.h
right on top of the#include
line inexample.c
. (You can do this with any file, not just a ‘.h’ file.)If we have a completley different file which needs to use the functions declared in
example.h
, we can use this include directive like so:To compile this file, we need to let the compiler know where the contents of
example.h
are implemented, and the linker will take care of everything for us:Acknowledgments and Extra Materials: Pointers, Arrays, Structures, Header Files