CS50x 2024 Lecture 4 - Introduction to Memory and Pointers
Document Details
Uploaded by UncomplicatedClematis
Tags
Summary
This lecture introduces fundamental concepts in computer programming, focusing on memory and pointers. It covers hexadecimal representation and how data is stored in computer memory using C programming language examples.
Full Transcript
Welcome! In previous weeks, we talked about images being made of smaller building blocks called pixels. Today, we will go into further detail about the zeros and ones that make up these images. In particular, we will be going deeper into the fundamental building blocks that make up l...
Welcome! In previous weeks, we talked about images being made of smaller building blocks called pixels. Today, we will go into further detail about the zeros and ones that make up these images. In particular, we will be going deeper into the fundamental building blocks that make up les, including images. Further, we will discuss how to access the underlying data stored in computer memory. Pixel Art Pixels are squares, individual dots, of color that are arranged on an up-down, left-right grid. You can imagine as an image as a map of bits, where zeros represent black and ones represent white. RGB, or red, green, blue, are numbers that represent the amount of each of these colors. In Adobe Photoshop, you can see these settings as follows: Notice how the amount of red, blue, and green changes the color selected. You can see by the image above that color is not just represented in three values. At the bottom of the window, there is a special value made up of numbers and characters. 255 is represented as FF. Why might this be? Hexadecimal Hexadecimal is a system of counting that has 16 counting values. They are as follows: 0 1 2 3 4 5 6 7 8 9 a b c d e f Notice that F represents 15. Hexadecimal is also known as base-16. When counting in hexadecimal, each column is a power of 16. The number 0 is represented as 00. The number 1 is represented as 01. The number 9 is represented by 09. The number 10 is represented as 0A. The number 15 is represented as 0F. The number 16 is represented as 10. The number 255 is represented as FF , because 16 x 15 (or F ) is 240. Add 15 more to make 255. This is the highest number you can count using a twodigit hexadecimal system. Hexadecimal is useful because it can be represented using fewer digits. Hexadecimal allows us to represent information more succinctly. Memory In weeks past, you may recall our artist rendering of concurrent blocks of memory. Applying hexadecimal numbering to each of these blocks of memory, you can visualize these as follows: You can imagine how there may be confusion regarding whether the 10 block above may represent a location in memory or the value 10. Accordingly, by convention, all hexadecimal numbers are often represented with the 0x pre x as follows: In your terminal window, type code addresses.c and write your code as follows: #include int main(void) { int n = 50; printf("%i\n", n); } Notice how n is stored in memory with the value 50. You can visualize how this program stores this value as follows: The C language has two powerful operators that relate to memory: & Provides the address of something stored in memory. * Instructs the compiler to go to a location in memory. We can leverage this knowledge by modifying our code as follows: #include int main(void) { int n = 50; printf("%p\n", &n); } Notice the %p , which allows us to view the address of a location in memory. &n can be literally translated as “the address of n.” Executing this code will return an address of memory beginning with 0x. Pointers A pointer is a variable that contains the address of some value. Most succinctly, a pointer is an address in your computer’s memory. Consider the following code: int n = 50; int *p = &n; Notice that p is a pointer that contains the address of an integer n. Modify your code as follows: #include int main(void) { int n = 50; int *p = &n; printf("%p\n", p); } Notice that this code has the same effect as our previous code. We have simply leveraged our new knowledge of the & and * operators. To illustrate the use of the * operator, consider the following: #include int main(void) { int n = 50; int *p = &n; printf("%i\n", *p); } Notice that the printf line prints the integer at the location of p. int *p creates a pointer whose job is to store the memory address of an integer. You can visualize our code as follows: Notice the pointer seems rather large. Indeed, a pointer is usually stored as an 8-byte value. p is storing the address of the 50. You can more accurately visualize a pointer as one address that points to another: Strings Now that we have a mental model for pointers, we can peel back a level of simpli cation that was offered earlier in this course. Recall that a string is simply an array of characters. For example, string s = "HI!" can be represented as follows: However, what is s really? Where is the s stored in memory? As you can imagine, s needs to be stored somewhere. You can visualize the relationship of s to the string as follows: Notice how a pointer called s tells the compiler where the rst byte of the string exists in memory. Modify your code as follows: #include #include int main(void) { string s = "HI!"; printf("%p\n", s); printf("%p\n", &s); printf("%p\n", &s); printf("%p\n", &s); printf("%p\n", &s); } Notice the above prints the memory locations of each character in the string s. The & symbol is used to show the address of each element of the string. When running this code, notice that elements 0 , 1 , 2 , and 3 are next to one another in memory. Likewise, you can modify your code as follows: #include int main(void) { char *s = "HI!"; printf("%s\n", s); } Notice that this code will present the string that starts at the location of s. This code effectively removes the training wheels of the string data type offered by cs50.h. This is raw C code, without the scaffolding of the cs50 library. You can imagine how a string, as a data type, is created. Last week, we learned how to create your own data type as a struct. The cs50 library includes a struct as follows: typedef char *string This struct, when using the cs50 library, allows one to use a custom data type called string. Pointer Arithmetic You can modify your code to accomplish the same thing in a longer form as follows: #include int main(void) { char *s = "HI!"; printf("%c\n", s); printf("%c\n", s); printf("%c\n", s); } Notice that we are printing each character at the location of s. Further, you can modify your code as follows: #include int main(void) { char *s = "HI!"; printf("%c\n", *s); printf("%c\n", *(s + 1)); printf("%c\n", *(s + 2)); } Notice that the rst character at the location of s is printed. Then, the character at the location s + 1 is printed, and so on. String Comparison A string of characters is simply an array of characters identi ed by its rst byte. Earlier in the course, we considered the comparison of integers. We could represent this in code by typing code compare.c into the terminal window and writing code as follows: #include #include int main(void) { // Get two integers int i = get_int("i: "); int j = get_int("j: "); // Compare integers if (i == j) { printf("Same\n"); } else { printf("Different\n"); } } Notice that this code takes two integers from the user and compares them. In the case of strings, however, one cannot compare two strings using the == operator. Utilizing the == operator in an attempt to compare strings will attempt to compare the memory locations of the strings instead of the characters therein. Accordingly, we recommended the use of strcmp. To illustrate this, modify your code as follows: #include #include int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Compare strings' addresses if (s == t) { printf("Same\n"); } else { printf("Different\n"); } } Noticing that typing in HI! for both strings still results in the output of Different. Why are these strings seemingly different? You can use the following to visualize why: Therefore, the code for compare.c above is actually attempting to see if the memory addresses are different: not the strings themselves. Using strcmp , we can correct our code: #include #include int main(void) { // Get two strings char *s = get_string("s: "); #include #include #include int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Compare strings if (strcmp(s, t) == 0) { printf("Same\n"); } else { printf("Different\n"); } } Notice that strcmp can return 0 if the strings are the same. To further illustrate how these two strings are living in two locations, modify your code as follows: #include #include int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Print strings printf("%s\n", s); printf("%s\n", t); } Notice how we now have two separate strings stored likely at two separate locations. You can see the locations of these two stored strings with a small modi cation: char *t = get_string("t: "); // Print strings' addresses printf("%p\n", s); printf("%p\n", t); } Notice that the %s has been changed to %p in the print statement. Copying A common need in programming is to copy one string to another. In your terminal window, type code copy.c and write code as follows: #include #include #include #include int main(void) { // Get a string string s = get_string("s: "); // Copy string's address string t = s; // Capitalize first letter in string t = toupper(t); // Print string twice printf("s: %s\n", s); printf("t: %s\n", t); } Notice that string t = s copies the address of s to t. This does not accomplish what we are desiring. The string is not copied – only the address is. int main(void) { // Get a string string s = get_string("s: "); // Copy string's address string t = s; // Capitalize first letter in string if (strlen(t) > 0) { t = toupper(t); } // Print string twice printf("s: %s\n", s); printf("t: %s\n", t); } You can visualize the above code as follows: Notice that s and t are still pointing at the same blocks of memory. This is not an authentic copy of a string. Instead, these are two pointers pointing at the same string. Before we address this challenge, it’s important to ensure that we don’t experience a segmentation fault through our code, where we attempt to copy string s to string t , where string t does not exist. We can employ the strlen function as follows to assist with that: #include #include #include #include Notice that strlen is used to make sure string t exists. If it does not, nothing will be copied. To be able to make an authentic copy of the string, we will need to introduce two new building blocks. First, malloc allows you, the programmer, to allocate a block of a speci c size of memory. Second, free allows you to tell the compiler to free up that block of memory you previously allocated. We can modify our code to create an authentic copy of our string as follows; #include #include #include #include #include int main(void) { // Get a string char *s = get_string("s: "); // Allocate memory for another string char *t = malloc(strlen(s) + 1); // Copy string into memory, including '\0' for (int i = 0, n = strlen(s); i