CS 61C Spring 2024 | Lab 2: C Debugging

839 阅读13分钟

Setup

In my cs61c directory, pull the files for this lab with:

$ git fetch starter main
$ git merge FETCH_HEAD

Exercise 1: Pointers to Stack vs Heap

Edit ex1_ptr_heap_stack.c using your editor of choice and fill in the blanks.

My answer:

#include <stdio.h>
#include <stdlib.h>

int* int_on_stack() {
  // Allocates memory on the stack for an integer
  int x = 5;

  // Returns a pointer that points to the number 5
  return &x;
}

int* int_on_heap() {
  // TODO: allocate memory on the heap for an integer
  int * ptr_to_5 = malloc(sizeof(int));

  // TODO: store the number 5 in memory you just allocated
  *ptr_to_5 = 5;

  // Returns a pointer that points to the number 5
  return ptr_to_5;
}

int main() {
  int* ptr_to_stack = int_on_stack();
  int* ptr_to_heap = int_on_heap();

  printf("ptr_to_stack is the address %p\n", ptr_to_stack);
  printf("ptr_to_heap is the address %p\n", ptr_to_heap);

  return 0;
}

Compile and run the program and check that the output matches what you expect. You will see a compiler warning. What does it mean?

My answer:

$ gcc ex1_ptr_heap_stack.c && ./a.out 
ex1_ptr_heap_stack.c: In function ‘int_on_stack’:
ex1_ptr_heap_stack.c:9:10: warning: function returns address of local variable [-Wreturn-local-addr]
    9 |   return &x;
      |          ^~
ptr_to_stack is the address (nil)
ptr_to_heap is the address 0x61618d2c92a0

Read the output of gcc. Note that it throws an error about the address of a stack variable being returned. Similarly, the output of the program should show address of the stack variable as (nil), indicating that it is not usable. This is because x cannot be accessed outside of the function int_on_stack using a pointer. In the future, make sure to allocate memory on the heap if you'd like to use it later.

Exercise 2: Compiler Warnings and Errors

Compiler warnings are generated to help you find potential bugs in your code. Make sure that you fix all of your compiler warnings before you attempt to run your code. This will save you a lot of time debugging in the future because fixing the compiler warnings is much faster than trying to find the bug on your own.

  1. Read over the code in ex2_compiler_warnings.c.
  2. Compile your program with gcc -o ex2_compiler_warnings ex2_compiler_warnings.c. You should see 3 warnings.
  3. Read the first line of the first warning. The line begins with ex2_compiler_warnings.c:13:22, which tells you that the warning is caused by line 13 of ex2_compiler_warnings.c. The warning states that the program is trying to assign a char to a char *.
  4. Open ex2_compiler_warnings.c and navigate to the line that's causing the warning. It is trying to assign a char to a char *. The compiler has pointed this out as a potential error because we should not be assigning a char to a char *.
  5. Fix this compiler warning.
  6. Recompile your code. You can now see that this warning does not appear anymore and there are 2 warnings left.
  7. Fix the remaining compiler warnings in ex2_compiler_warnings.c.

My answer:

  • Compile Warnings:
$ gcc ex2_compiler_warnings.c        
ex2_compiler_warnings.c: In function ‘make_course’:
ex2_compiler_warnings.c:13:22: warning: assignment to ‘char *’ from ‘char’ makes pointer from integer without a cast [-Wint-conversion]
   13 |     new_course->name = *name;
      |                      ^
ex2_compiler_warnings.c:15:12: warning: returning ‘struct Course **’ from a function with incompatible return type ‘struct Course *’ [-Wincompatible-pointer-types]
   15 |     return &new_course;
      |            ^~~~~~~~~~~
ex2_compiler_warnings.c:15:12: warning: function returns address of local variable [-Wreturn-local-addr]
  • No bug version:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct Course {
    int id;
    char *name;
};

struct Course *make_course(int id, char *name) {
    struct Course *new_course = malloc(sizeof(struct Course));
    new_course->id = id;
    new_course->name = name;

    return new_course;
}

int main() {
    struct Course *cs161 = make_course(161, "Computer Security");
    printf("Welcome to CS%d: %s!\n", cs161->id, cs161->name);

    return 0;
}

Exercise 3: Intro to GDB

In this section, you will learn the GDB commands startstepnextfinishprint, and quit. This section will resolve bug(s) along the way. Make sure to fix the bug(s) in the code before moving on.

The table below is a summary of the above commands

CommandAbbreviationDescription
startN/Abegin running the program and stop at line 1 in main
stepsexecute the current line of code (this command will step into functions)
nextnexecute the current line of code (this command will not step into functions)
finishfinexecutes the remainder of the current function and returns to the calling function
print [arg]pprints the value of the argument
quitqexits gdb

You should be filling in ex3_commands.txt with the corresponding commands. Please only use the commands from the table above. For correctness, we will be checking the output of your ex3_commands.txt against a desired output.  We'd recommend opening two SSH windows so you can have the commands file and the cgdb session at the same time. Even though you are adding to ex3_commands.txt, please check your work by actually running these commands in cgdb.

  1. Compile your program with the -g flag. This will include additional debugging information in the executable that CGDB needs.

    gcc -g -o pwd_checker test_pwd_checker.c pwd_checker.c
    
  2. Start cgdb. Note that you should be using the executable (pwd_checker) as the argument, not the source file (pwd_checker.c).

    cgdb pwd_checker
    

    You should now see CGDB open. The top window displays our code and the bottom window displays the console.

For each of the following steps, add the CGDB commands you execute to ex3_commands.txt. Each command should be on its own line. Each step below will require one or more CGDB commands.

  1. Start your program so that it's at the first line in main, using one command.
  2. The first line in main is a call to printf. We do not want to step into this function. Step over this line in the program.
  3. Step until the program is on the check_password call. Note that the line with an arrow next to it is the line we're currently on, but has not been executed yet.
  4. Step into check_password.
  5. Step into check_lower.
  6. Print the value of password (password is a string).
  7. Step out of check_lower immediately. Do not step until the function returns.
  8. Step into check_length.
  9. Step to the last line of the function.
  10. Print the return value of the function. The return value should be false.
  11. Print the value of length. It looks like length was correct, so there must be some logic issue on line 24.
  12. Quit CGDB. CGDB might ask you if you want to quit, type y (but do not add y to ex3_commands.txt).

At this point, your ex3_commands.txt should contain a list of commands from the steps above. You don't need to add anything from the steps below to your ex3_commands.txt.

My answer:

  • ex3_commands.txt:
# You should be editing this file for exercise 3 of lab 2.
# Make sure each command is on its own line
# Lines starting with # are comments, feel free to add any to document your commands

# Please add your commands below this line

start
n
n
n
n
s
s
p password
fin
n
s
n
n
n
p meets_len_req
p length
q

  • GDB output:
$ gdb pwd_checker -q
Reading symbols from pwd_checker...
(gdb) start
Temporary breakpoint 1 at 0x1174: file test_pwd_checker.c, line 6.
Starting program: /home/dsy/Code/cs61c/lab02/pwd_checker 

Temporary breakpoint 1, main () at test_pwd_checker.c:6
6           printf("Running tests...\n\n");
(gdb) n
Running tests...

8           const char *test1_first = "Abraham";
(gdb) n
9           const char *test1_last = "Garcia";
(gdb) n
10          const char *test1_pwd = "qrtv?,mp!ltrA0b13rab4ham";
(gdb) n
11          bool test1 = check_password(test1_first, test1_last, test1_pwd);
(gdb) s
check_password (first_name=0x55555555601a "Abraham", last_name=0x555555556022 "Garcia", password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham")
    at pwd_checker.c:83
83          lower = check_lower(password);
(gdb) s
check_lower (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:48
48          while (*password != '\0') {
(gdb) p password
$1 = 0x555555556029 "qrtv?,mp!ltrA0b13rab4ham"
(gdb) fin
Run till exit from #0  check_lower (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:48
0x00005555555556b5 in check_password (first_name=0x55555555601a "Abraham", last_name=0x555555556022 "Garcia", 
    password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:83
83          lower = check_lower(password);
Value returned is $2 = true
(gdb) n
84          length = check_length(password);
(gdb) s
check_length (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:23
23          int length = strlen(password);
(gdb) n
24          bool meets_len_req = (length <= 10);
(gdb) n
25          return meets_len_req;
(gdb) n
26      }
(gdb) p meets_len_req 
$3 = false
(gdb) p length 
$4 = 24
(gdb) q
A debugging session is active.

        Inferior 1 [process 30677] will be killed.

Quit anyway? (y or n) y
  1. Fix the bug on line 24.
  2. Compile and run your code.
  3. The program still fails. Open and step through cgdb again, you should see that check_number is now failing. We will address this in the next exercise.

My answer:

  • fixed code:
/* Returns true if the length of PASSWORD is at least 10, false otherwise */
bool check_length(const char *password) {
    int length = strlen(password);
    bool meets_len_req = (length >= 10);
    return meets_len_req;
}

  • compile and run:
$ gcc -g -o pwd_checker test_pwd_checker.c pwd_checker.c && ./pwd_checker 
Running tests...

pwd_checker: test_pwd_checker.c:12: main: Assertion `test1' failed.
[1]    32580 IOT instruction (core dumped)  ./pwd_checker
  • GDB output:
$ gdb pwd_checker -q
Reading symbols from pwd_checker...
(gdb) start
Temporary breakpoint 1 at 0x1174: file test_pwd_checker.c, line 6.
Starting program: /home/dsy/Code/cs61c/lab02/pwd_checker 

Temporary breakpoint 1, main () at test_pwd_checker.c:6
6           printf("Running tests...\n\n");
(gdb) n
Running tests...

8           const char *test1_first = "Abraham";
(gdb) n
9           const char *test1_last = "Garcia";
(gdb) n
10          const char *test1_pwd = "qrtv?,mp!ltrA0b13rab4ham";
(gdb) n
11          bool test1 = check_password(test1_first, test1_last, test1_pwd);
(gdb) s
check_password (first_name=0x55555555601a "Abraham", last_name=0x555555556022 "Garcia", password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham")
    at pwd_checker.c:83
83          lower = check_lower(password);
(gdb) n
84          length = check_length(password);
(gdb) n
85          name = check_name(first_name, last_name, password);
(gdb) n
86          number = check_number(password);
(gdb) s
check_number (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:60
60          while (*password != '\0') {
(gdb) n
61              if (check_range(*password, 0, 9)) {
(gdb) n
64              ++password;
(gdb) n
60          while (*password != '\0') {
(gdb) fin
Run till exit from #0  check_number (password=0x55555555602a "rtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:60
0x00005555555556ed in check_password (first_name=0x55555555601a "Abraham", last_name=0x555555556022 "Garcia", 
    password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:86
86          number = check_number(password);
Value returned is $1 = false
(gdb) q
A debugging session is active.

        Inferior 1 [process 33118] will be killed.

Quit anyway? (y or n) y

Exercise 4: More GDB

In this section, you will learn the gdb commands breakconditional breakrun, and continue. This section will resolve bug(s) along the way. Make sure to fix the bug(s) in the code before moving on.

The table below is a summary of the above commands

CommandAbbreviationDescription
break [line num or function name]bset a breakpoint at the specified location, use filename.c:linenum to set a breakpoint in a specific file
conditional break (ex: break 3 if n==4)(ex: b 3 if n==4)set a breakpoint at the specified location only if a given condition is met
runrexecute the program until termination or reaching a breakpoint
continueccontinues the execution of a program that was paused

You should be filling in ex4_commands.txt with the corresponding commands. Please only use the commands from the table above and the table for exercise 2. For correctness, we will be checking the output of your ex4_commands.txt against a desired output.  We'd recommend opening two SSH windows so you can have the commands file and the cgdb session at the same time. Even though you are adding to ex4_commands.txt, please check your work by actually running these commands in cgdb.

  1. Recompile and run your code. You should see that the assertion number is failing

  2. Start cgdb

    cgdb pwd_checker
    

For each of the following steps, add the CGDB commands you execute to ex4_commands.txt. Each command should be on its own line. Each step below will require one or more CGDB commands.

  1. Set a breakpoint in our code to jump straight into the function check_number using the function name (not the filename or line number). Your breakpoint should not be in check_password.

  2. Run the program. Your code should run until it gets to the breakpoint that we just set.

  3. Step into check_range.

  4. Recall that the numbers do not appear until later in the password. Instead of stepping through all of the non-numerical characters at the beginning of password, we can jump straight to the point in the code where the numbers are being compared using a conditional breakpoint. A conditional breakpoint will only stop the program based on a given condition. The first number in the password 0, so we can set the breakpoint when letter is '0'Break on line 31 if the letter is '0' .

    We are using the single quote because 0 is a char.

  5. Continue executing your code after it stops at a breakpoint.

  6. The code has stopped at the conditional breakpoint. To verify this, print letter.

    It should print 48 '0' which is a decimal number followed by it's corresponding ASCII representation. If you look at an ASCII table, you can see that 48 is the decimal representation of the character 0.

  7. Let's take a look at the return value of check_rangePrint is_in_range. The result is false. That's strange. '0' should be in the range.

  8. Let's look at the upper and lower bounds of the range. Print lower.

  9. Print upper.

  10. Ahah! The ASCII representation of lower is \000(the null terminator) and the ASCII representation of upper is \t. It looks like we passed in the numbers 0 and 9 instead of the characters '0' and '9'!

  11. Quit CGDB. CGDB might ask you if you want to quit, type y (but do not add y to ex4_commands.txt).

At this point, your ex4_commands.txt should contain a list of commands from the steps above. You don't need to add anything from the steps below to your ex4_commands.txt.

My answer:

  • ex4_commands.txt:
# You should be editing this file for exercise 4 of lab 2.
# Make sure each command is on its own line
# Lines starting with # are comments, feel free to add any to document your commands

# Please add your commands below this line

b check_number
r
n
s
b 31 if letter == '0'
c
p is_in_range
p lower
p upper
q

  • GDB output:
$ gdb pwd_checker -q
Reading symbols from pwd_checker...
(gdb) b check_number 
Breakpoint 1 at 0x15f5: file pwd_checker.c, line 60.
(gdb) r
Starting program: /home/dsy/Code/cs61c/lab02/pwd_checker 

Breakpoint 1, check_number (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:60
60          while (*password != '\0') {
(gdb) n
61              if (check_range(*password, 0, 9)) {
(gdb) s
check_range (letter=113 'q', lower=0 '\000', upper=9 '\t') at pwd_checker.c:30
30          bool is_in_range = (letter > lower && letter < upper);
(gdb) b 31 if letter == '0'
Breakpoint 2 at 0x555555555543: file pwd_checker.c, line 31.
(gdb) c
Continuing.

Breakpoint 2, check_range (letter=48 '0', lower=0 '\000', upper=9 '\t') at pwd_checker.c:31
31          return is_in_range;
(gdb) p is_in_range 
$1 = false
(gdb) p lower 
$2 = 0 '\000'
(gdb) p upper 
$3 = 9 '\t'
(gdb) q
A debugging session is active.

        Inferior 1 [process 33886] will be killed.

Quit anyway? (y or n) y
  1. Fix the bug.
  2. Compile and run your code. There's one more error, which you will find in exercise 5.

My answer:

  • fixed code:
/* Returns true if PASSWORD contains at least one number, false otherwise */
bool check_number(const char *password) {
    while (*password != '\0') {
        if (check_range(*password, '0', '9')) {
            return true;
        }
        ++password;
    }
    return false;
}

  • compile and run:
$ gcc -g -o pwd_checker test_pwd_checker.c pwd_checker.c && ./pwd_checker
Running tests...

pwd_checker: test_pwd_checker.c:12: main: Assertion `test1' failed.
[1]    34650 IOT instruction (core dumped)  ./pwd_checker

Exercise 5: Debug

  1. Debug check_upper on your own using the commands you just learned. The function appears to be returning false even though there's an uppercase letter. Hint: the bug itself may not be in check_upper itself.

My answer:

  • GDB output:
$ gdb pwd_checker -q                                                     
Reading symbols from pwd_checker...
(gdb) b check_upper 
Breakpoint 1 at 0x1555: file pwd_checker.c, line 36.
(gdb) r
Starting program: /home/dsy/Code/cs61c/lab02/pwd_checker 

Breakpoint 1, check_upper (password=0x555555556029 "qrtv?,mp!ltrA0b13rab4ham") at pwd_checker.c:36
36          while (*password != '\0') {
(gdb) n
37              bool is_in_range = check_range(*password, 'A', 'Z');
(gdb) s
check_range (letter=113 'q', lower=65 'A', upper=90 'Z') at pwd_checker.c:30
30          bool is_in_range = (letter > lower && letter < upper);
(gdb) b 31 if (letter >= 'A' && letter <= 'Z')
Breakpoint 2 at 0x555555555543: file pwd_checker.c, line 31.
(gdb) c
Continuing.

Breakpoint 2, check_range (letter=65 'A', lower=65 'A', upper=90 'Z') at pwd_checker.c:31
31          return is_in_range;
(gdb) p is_in_range 
$1 = false
(gdb) fin
Run till exit from #0  check_range (letter=65 'A', lower=65 'A', upper=90 'Z') at pwd_checker.c:31
0x0000555555555572 in check_upper (password=0x555555556035 "A0b13rab4ham") at pwd_checker.c:37
37              bool is_in_range = check_range(*password, 'A', 'Z');
Value returned is $2 = false
(gdb) n
38              if (is_in_range) {
(gdb) n
41              ++password;
(gdb) n
36          while (*password != '\0') {
(gdb) n
37              bool is_in_range = check_range(*password, 'A', 'Z');
(gdb) q
A debugging session is active.

        Inferior 1 [process 34971] will be killed.

Quit anyway? (y or n) y
  • fixed code:
/* Returns true if LETTER is in the range [LOWER, UPPER], false otherwise */
bool check_range(char letter, char lower, char upper) {
    bool is_in_range = (letter >= lower && letter <= upper);
    return is_in_range;
}

  • compile and run:
$ gcc -g -o pwd_checker test_pwd_checker.c pwd_checker.c && ./pwd_checker
Running tests...

Congrats! You have passed all of the test cases!

Exercise 6: Using Valgrind to find segfaults

There's a bug in ex6_valgrind, let's see how we can detect it with valgrind.

  1. Compile ex6_valgrind.c. Notice that there are no compiler errors or warnings, and we're using the -g flag in case we need to debug this program in the future.

    gcc -g -o ex6_valgrind ex6_valgrind.c
    
  2. Run ex6_valgrind. Notice that the program doesn't throw any errors.

  3. Run valgrind on ex6_valgrind. You should see that there are 2 errors.

  4. Read the valgrind output carefully. In ex6_answers.txt, answer the following questions. Please don't change the formatting of the file. For question 1 through 7, we are referring to the first valgrind error (an invalid write error).

    1. How many bytes are the invalid write? (The answer should be a number without any units)
    2. Which function caused the invalid write? (The answer should be the name of the function)
    3. Which function called the answer to question 2? (The answer should be the name of a function)
    4. Which file did the call occur in? (The answer should be the name of a file)
    5. Which line did the call occur on? (The answer should be a number)
    6. How many bytes were actually allocated? (The answer should be a number without any units)
    7. How many bytes should have been allocated? Feel free to read the code. (The answer should be a number without any units)
    8. Are there any memory leaks? (The answer should be Yes or No)
    9. How many bytes were leaked? Write 0 if there are no memory leaks. (The answer should be a number without any units)

My answer:

  • valgrind output:
$ valgrind ./ex6_valgrind 
==26527== Memcheck, a memory error detector
==26527== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==26527== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==26527== Command: ./ex6_valgrind
==26527== 
==26527== Invalid write of size 1
==26527==    at 0x484C3DE: strcpy (vg_replace_strmem.c:561)
==26527==    by 0x10919F: copy_str (ex6_valgrind.c:7)
==26527==    by 0x1091BC: main (ex6_valgrind.c:12)
==26527==  Address 0x4a7504c is 0 bytes after a block of size 12 alloc'd
==26527==    at 0x4843788: malloc (vg_replace_malloc.c:442)
==26527==    by 0x109188: copy_str (ex6_valgrind.c:6)
==26527==    by 0x1091BC: main (ex6_valgrind.c:12)
==26527== 
==26527== Invalid read of size 1
==26527==    at 0x484C274: strlen (vg_replace_strmem.c:502)
==26527==    by 0x4908687: puts (ioputs.c:35)
==26527==    by 0x1091CC: main (ex6_valgrind.c:13)
==26527==  Address 0x4a7504c is 0 bytes after a block of size 12 alloc'd
==26527==    at 0x4843788: malloc (vg_replace_malloc.c:442)
==26527==    by 0x109188: copy_str (ex6_valgrind.c:6)
==26527==    by 0x1091BC: main (ex6_valgrind.c:12)
==26527== 
hello world!
==26527== 
==26527== HEAP SUMMARY:
==26527==     in use at exit: 12 bytes in 1 blocks
==26527==   total heap usage: 2 allocs, 1 frees, 1,036 bytes allocated
==26527== 
==26527== LEAK SUMMARY:
==26527==    definitely lost: 12 bytes in 1 blocks
==26527==    indirectly lost: 0 bytes in 0 blocks
==26527==      possibly lost: 0 bytes in 0 blocks
==26527==    still reachable: 0 bytes in 0 blocks
==26527==         suppressed: 0 bytes in 0 blocks
==26527== Rerun with --leak-check=full to see details of leaked memory
==26527== 
==26527== For lists of detected and suppressed errors, rerun with: -s
==26527== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
  • ex6_answers.txt
1. 1
2. strcpy
3. copy_str
4. vg_replace_strmem.c
5. 561
6. 1,036
7. 13
8. Yes
9. 12

Exercise 7: Memory Management

This exercise uses ex7_vector.hex7_test_vector.c, and ex7_vector.c, where we provide you with a framework for implementing a variable-length array. This exercise is designed to help familiarize you with C structs and memory management in C.

  1. Try to explain why bad_vector_new() is bad. We have provided the reason here, so you can verify your understanding

    The vector is created on the stack, instead of the heap. All memory stored on the stack gets freed as soon as that function finishes running, so when the function returns, we lose the vector we constructed.

  2. Fill in the functions vector_new()vector_get()vector_delete(), and vector_set() in ex7_vector.c so that our test code ex7_test_vector.c runs without any memory management errors.

    Comments in the code describe how the functions should work. Look at the functions we've filled in to see how the data structures should be used. For consistency, it is assumed that all entries in the vector are 0 unless set by the user. Keep this in mind as malloc() does not zero out the memory it allocates.  vector_set should resize the array if the index passed in is larger than the size of the array.

  3. Test your implementation of vector_new()vector_get()vector_delete(), and vector_set() for correctness.

    gcc -g -o ex7_vector ex7_vector.c ex7_test_vector.c
    ./ex7_vector
    
  4. Test your implementation of vector_new()vector_get()vector_delete(), and vector_set() for memory management.

    valgrind ./ex7_vector
    

Any number of suppressed errors is fine; they do not affect us.

Feel free to also use CGDB to debug your code.

My answer:

  • ex7_vector.c
/* Create a new vector with a size (length) of 1 and set its single component to zero... the
   right way */
/* TODO: uncomment the code that is preceded by // */
vector_t *vector_new()
{
    /* Declare what this function will return */
    vector_t *retval;

    /* First, we need to allocate memory on the heap for the struct */
    retval = malloc(sizeof(vector_t));

    /* Check our return value to make sure we got memory */
    if (retval == NULL)
    {
        allocation_failed();
    }

    /* Now we need to initialize our data.
       Since retval->data should be able to dynamically grow,
       what do you need to do? */
    retval->size = 1;
    retval->data = malloc(sizeof(int));

    /* Check the data attribute of our vector to make sure we got memory */
    if (retval->data == NULL) {
        free(retval);				//Why is this line necessary?
        allocation_failed();
    }

    /* Complete the initialization by setting the single component to zero */
    *(retval->data) = 0;

    /* and return... */
    return retval; /* UPDATE RETURN VALUE */
}

/* Return the value at the specified location/component "loc" of the vector */
int vector_get(vector_t *v, size_t loc)
{

    /* If we are passed a NULL pointer for our vector, complain about it and exit. */
    if (v == NULL)
    {
        fprintf(stderr, "vector_get: passed a NULL vector.\n");
        abort();
    }

    /* If the requested location is higher than we have allocated, return 0.
     * Otherwise, return what is in the passed location.
     */
    /* YOUR CODE HERE */
    if (loc < v->size) {
        return v->data[loc];
    }
    return 0;
}

/* Free up the memory allocated for the passed vector.
   Remember, you need to free up ALL the memory that was allocated. */
void vector_delete(vector_t *v)
{
    /* YOUR CODE HERE */
    free(v->data);
    free(v);
}

/* Set a value in the vector, allocating additional memory if necessary.
   If the extra memory allocation fails, call allocation_failed(). */
void vector_set(vector_t *v, size_t loc, int value)
{
    /* What do you need to do if the location is greater than the size we have
     * allocated?  Remember that unset locations should contain a value of 0.
     */

    /* YOUR CODE HERE */
    if (loc < v->size) {
        v->data[loc] = value;
    } else {
        int new_size = loc + 1;
        int *new_data = malloc(new_size * sizeof(int));
        if (new_data == NULL) {
            allocation_failed();
        }
        for (int i = 0; i < loc; i++) {
            if (i < v->size) {
                new_data[i] = v->data[i];
            } else {
                new_data[i] = 0;
            }
        }
        new_data[loc] = value;
        free(v->data);
        v->size = new_size;
        v->data = new_data;
    }
}

Exercise 8: Double Pointers

Edit ex8_double_pointers.c using your editor of choice and fill in the blanks.

Compile and run the program and check that the output matches what you expect.

My answer:

  • ex8_double_pointers.c
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    char* name;
} Student;

Student* create_student_1(int id) {
  // TODO: allocate memory to store a Student struct
  Student * student_ptr = malloc(sizeof(Student));

  // TODO: set student_ptr's id to the id argument of this function
  student_ptr->id = id;

  return student_ptr;
}

void create_student_2(Student** student_double_ptr, int id) {
  // TODO: fill the space that student_double_ptr points to with the address of
  //       some memory large enough for a Student struct
  // Hint: you may need to use the dereference operator here
  *student_double_ptr = malloc(sizeof(Student));

  // TODO: set student_double_ptr's id to the id argument of this function
  (*student_double_ptr)->id = id;
}


int main() {
  // TODO: use create_student_1 to create a pointer to a Student struct
  //       where the student has id of 5
  Student * student1_ptr = create_student_1(5);

  // TODO: print the id of the student that student1_ptr points to
  printf("Student 1's ID: %d\n", student1_ptr->id);

  // TODO: create a pointer that can point to a Student struct
  //       do not allocate any memory
  Student * student2_ptr;

  // TODO: use create_student_2 to populate the student2_ptr
  //       where the student has id of 6
  // Hint: compare the type of student2_ptr with the type of
  //       the argument for create_student_2
  create_student_2(&student2_ptr, 6);

  // TODO: print the id of the student that student2_ptr points to
  printf("Student 2's ID: %d\n", student2_ptr->id);

  // Free everything allocated with `malloc`
  free(student1_ptr);
  free(student2_ptr);

  return 0;
}

Exercise 9: Putting It All Together

Here's one to help you in your interviews. In ex9_cycle.c, complete the function ll_has_cycle() to implement the following algorithm for checking if a singly-linked list has a cycle.

  1. Start with two pointers at the head of the list. One will be called fast_ptr and the other will be called slow_ptr.
  2. Advance fast_ptr by two nodes. If this is not possible because of a null pointer, we have found the end of the list, and therefore the list is acyclic.
  3. Advance slow_ptr by one node. (A null pointer check is unnecessary. Why?)
  4. If the fast_ptr and slow_ptr ever point to the same node, the list is cyclic. Otherwise, go back to step 2.
  5. If the list has a cycle, return 1. Else, return 0.

If you want to see the definition of the node struct, open ex9_cycle.h.

Action Item

Implement ll_has_cycle(). Once you've done so, you can execute the following commands to run the tests for your code. If you make any changes, make sure to run ALL of the following commands again, in order.

gcc -g -o ex9_test_cycle ex9_test_cycle.c ex9_cycle.c
./ex9_test_cycle

Here's a Wikipedia article on the algorithm and why it works. Don't worry about it if you don't completely understand it.

My answer:

#include <stddef.h>
#include "ex9_cycle.h"

int ll_has_cycle(node *head)
{
    /* TODO: Implement ll_has_cycle */
    node *fast_ptr = head, *slow_ptr = head;
    while (fast_ptr != NULL && fast_ptr->next != NULL)
    {
        fast_ptr = fast_ptr->next->next;
        slow_ptr = slow_ptr->next;
        if (fast_ptr == slow_ptr)
        {
            return 1;
        }
    }
    return 0;
}