Review Session 2 CS 202 Feb 9 & Feb 11 2015 1. C Pointers 2. Linked List 3. Shell 4. Lab/Homework Q&A --------------------------------------------- 1. C Pointers Review - C pointers - Symbol '*' in C - multiplication - pointer type declaration - dereferencing, and & is referencing [handout int n = 10; int *ptr = &n; // 'int *' together here is declaring variable 'ptr' // of type 'int *', pointer of int. // &n references n, resolved in the address of n, // which is a 'int *' type. int m = n * *ptr; // first '*' means multiplication, second '*' // means dereferencing. '*ptr' is the 'int value' // pointed by 'ptr'. *ptr = m; // '*' means dereferencing, it will set the 'int' // pointed by 'ptr' to 3. printf("n: %d\n", n); // What should be the output? end] n: 100 - Arrays vs. Pointers [on the board char str[4]; // array declaration char *ptr; // pointer declaration +-----+ | *** | +-----+ +--+--+--+--+ | str | --> | | | | | +-----+ +--+--+--+--+ | ptr | +-----+ end] - The difference of 'str' and 'ptr' is 'str' is pointing to the beginning of 4 contiguous chars, while 'ptr' is not. [on the board // if we have: ptr = str; // then we can use 'ptr' as 'str' // since now we have: +-----+ | *** | +-----+ +--+--+--+--+ | str | --> | | | | | +-----+ / +--+--+--+--+ | ptr | -/ +-----+ // So if we do: ptr[0] = 'a'; // then the output will be 'a' printf("first char: %c", str[0]); end] - If a pointer is passed into a function as a parameter, the function can make change to the value pointed by the pointer. [handout void func(int *ptr) { *ptr = 3; // recall, '*' here means dereferencing } int i = 10; int *p = &i; // recall, 'int *' here is declaration func(p); // What is the value of i now? Why? end] i should be 3, because p and ptr have the same value, pointing to the same address. [on the board +-----+ | i |<-\<-\ +-----+ + + | p | -/ / +-----+ / | ptr | --/ +-----+ end] - NULL terminated string [on the board] char str1[] = "abc"; char str2[8]; +------+ | **** | +------+ +-+-+-+----+ | str1 | --> |a|b|c|NULL| +------+ +-+-+-+----+ | ptr2 | \ +-+-+-+-+-+-+-+-+-+-+ +------+ \->| | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+ strcpy(str2, str1); // str1, str2 are pointers to the beginning of an string (first char), // the end of the string will be denoted by a NULL char // for strcpy (string copy), NULL will be the last char copied to str2. // So we should have +------+ | **** | +------+ +-+-+-+----+ | str1 | --> |a|b|c|NULL| +------+ +-+-+-+----+ | ptr2 | \ +-+-+-+----+-+-+-+-+-+-+ +------+ \->|a|b|c|NULL| | | | | | | +-+-+-+----+-+-+-+-+-+-+ printf("str2: %s\n", str2); // output is "str2: abc", again NULL is the end // if treat str2 as and array, it still points to the beginning of // 10 chars, not 4 chars. The remaining unused cells are still there. end] - Besides to terminate an array of chars (string), NULL can also be used to terminate an array of pointers, we'll see it soon. - Pointer of pointer type [on the board int n = 0; int *ptr = &n; int **ptr_ptr = &ptr; int ** int * int +---------+ +-----+ +---+ | ptr_ptr | -> | ptr | -> | n | +---------+ +-----+ +---+ end] [handout // argv points to an array of 10 entries, each entry is a 'char *' type char *argv[10]; // If we only want to use less than 10 entries in the array, // we can use NULL-terminated method here. NULL-terminated array (each entry is 'char *' type) +------+ +---+---+---+----+---+---+---+---+---+---+ | argv | -> | x | x | x |NULL| | | | | | | +------+ +---+---+---+----+---+---+---+---+---+---+ | | | +----------------+ | +---------------+ v v v NULL-terminated string (each entry is 'char' type) +---+---+---+----+ +---+---+---+----+ +---+---+---+----+ | a | b | c |NULL| | f | o | o |NULL| | b | o | o |NULL| +---+---+---+----+ +---+---+---+----+ +---+---+---+----+ // So that: printf("argv[0]: %s\n", argv[0]); // "abc" printf("argv[1]: %s\n", argv[1]); // "foo" printf("argv[2]: %s\n", argv[2]); // "boo" printf("argv[0][1]: %c\n", argv[0][1]); // 'b' (char type) end] - Pointer arithmetic [on the board int array[12]; array + i == &(array[i]) // So if array is 0xFFFFFF00 // then array + 1 should be 0xFFFFFF04, since sizeof(int) == 4 // instead of 0xFFFFFF01 end] 2. Linked List - A data structure, contains multiple entries (nodes), each entry has a pointer to the next entry. The last entry has a NULL next pointer. [on the board head (1st node) 2nd node 3rd node 4th node +------------+ +------------+ +------------+ +------------+ | node value | +->| node value | +->| node value | +->| node value | +------------+ / +------------+ / +------------+ / +------------+ | next |-+ | next |-+ | next |-+ | next |->NULL (end) +------------+ +------------+ +------------+ +------------+ end] - Insert a node (illustrate using the above graph - Remove a node (ditto) - Traverse the list - Example of traversal and insertion in C Go over 2.1 & 2.2 code [handout struct node_t { int id; char name[10]; struct node_t *next; }; /* Iterate through the sorted list (by node_t::id), and find the position after * which the new 'node' should be inserted in the list. Ensure that the list * is kept sorted. Return the element just before the newly inserted one. * If the new 'node' should be inserted at the beginning, return NULL. */ node_t * find_insert_pos(node_t *head, node_t *node) { if (head == NULL) return NULL; node_t *ret = NULL; // 2.1 your code here while (head != NULL) { // traverse using head if (head->id >= node->id) { break; } else { ret = head; head = head->next; } } return ret; } /* insert a new 'node' into the list 'head', return the new head of the list. node_t * insert(node_t *head, node_t *node) { if (head == NULL) return node; // find the proper position to insert this node pair. node_t *pos = find_insert_pos(head, node); // 2.2 your code here if (pos == NULL) { // insert before head node->next = head; head = node; } else { // insert after pos node->next = pos->next; pos->next = node; } return head; } int main(void) { node_t *student_list = NULL; // init first student Alice node_t *student_1 = (node_t *)malloc(sizeof(node_t)); student_1->id = 1002; strcpy(student_1->name, "Alice"); student_1->next = NULL; // init second student Bob node_t *student_2 = (node_t *)malloc(sizeof(node_t)); student_2->id = 1000; strcpy(student_2->name, "Bob"); student_2->next = NULL; student_list = list_insert(student_list, student_1); student_list = list_insert(student_list, student_2); // now we should have a student list: // <1000, Bob> -> <10002, Alice> -> NULL ... free resources ... return 0; } end] 3. Shell - Shell commands use 'man xxx' to see their usages. - Shell implementation [handout // input buffer to store one line input char input[BUFSIZ]; while (1) { parsestate_t parsestate; command_t *cmdlist; // Print the prompt printf("cs202$ "); // Wait and read one line of input fgets(input, BUFSIZ, stdin); // Parse input and build the command list parse_init(&parsestate, input); // The parsed command will be stored as an linked list in cmdlist cmdlist = cmd_line_parse(&parsestate, 0); if (!cmdlist) { printf("Syntax error\n"); continue; } // Execute parsed commands, from the head of the linked list to the end cmd_line_exec(cmdlist); // Free the cmdlist structure cmd_free(cmdlist); } end] Basic structure of the code logic. It contains three major components - Wait and read input from user; - Parse the one line input into command(s). Store them in a linked list, each command is a linked list node. - Execute the command(s) in the linked list one by one, from the head to the end. Note the connector between two commands (| && ; etc) will be stored in the node storing the left side command. [handout static pid_t cmd_exec(command_t *cmd, int *pass_pipefd); int cmd_line_exec(command_t *cmdlist) { int pipefd = STDIN_FILENO; // read end of last pipe while (cmdlist != NULL) { /*** your code ***/ // Hint: utilize cmd_exec with the aid of pipefd cmdlist = cmdlist->next; } } end] - cmd_line_exec(command_t *) will be a while loop traverse the command(s) linked list, and for each command, invoke cmd_exec(); - Pipe implementation (tricky part) Chanllenge of implementing pipe is there could be many commands and multiple pipes. - Pipe is implemented with the help of the pipefd variable in cmd_line_exec. It can be used to pass values through multiple invocation to cmd_exec - review key features of fork, exec, pipe & dup2 [on the board fork int exec (question: what should exec return? will not return on success, -1 on error) int pipe(int *pipefd) int dup2(int oldfd, int newfd); end] - fork: init a child process, open fd(s) will be inherited by child process - exec: the calling process will wipe out all its own memory, except the opened file descriptors will be kept. The code invoking exec will never get resumed. - pipe(int *pipefd): fd should be declared as [on the board int fds[2]; +-----+ +---+---+ | fds | -> | | | +-----+ +---+---+ end] So that "fds" will pointing to an array of two integer entries. the function pipe() takes this pointer as an argument, opens two file descriptors (one for read fds[0], one for write fds[1]) and put them in the array, so that the calling function can get these two file descriptors. - dup2(int oldfd, int newfd): dup2 makes newfd the copy of oldfd. * If newfd is already in use, dup2 will first close it * - Get back to pipe implementation [on the board +-------------+ +-----+ | pass_pipefd | -> | pfd | // the address of pfd (int type) gets +-------------+ +-----+ // passed into multiple invocations to cmd_exec init: *pass_pipefd == pfd == 0 |? COMMAND_ONE | COMMAND_TWO | COMMAND_THREE | v +----------+ | init: | | pfd <- 0 | +----------+ // NOT ON BOARD: procedure of cmd_exec(COMMAND_ONE) // pre-exec COMMAND_ONE: pfd == 0 // since there is a pipe next to COMMAND_ONE, open a pipe by invoking pipe // fork child, child sets write side of '|1' to be itself // NOTE: child will inherit the files opened by 'pipe' in the parent // child exec C1 // pfd <- rside of '|1' For COMMAND_ONE (cmd_exec(COMMAND_ONE, pass_pipefd): before: *pass_pipefd == pfd == 0 |? COMMAND_ONE | COMMAND_TWO | COMMAND_THREE | | v v +----------+ +----------+ | init: | | 1. | | rside: - | | rside: | | wside: - | | wside: C1| +----------+ +----------+ after: *pass_pipefd == pfd == rside of '|1' // NOT ON BOARD: procedure of cmd_exec(COMMAND_TWO) // pre-exec COMMAND_TWO: pfd == rside of '|1' // since there is a pipe next to COMMAND_TWO, open a pipe by invoking pipe // fork child, child set read side of '|1' (get from pfd) and write side of '|2' to be itself // child exec C2 // pfd <- rside of '|2' For COMMAND_TWO (cmd_exec(COMMAND_TWO, pass_pipefd): before: *pass_pipefd == pfd == rside of '|1' |? COMMAND_ONE | COMMAND_TWO | COMMAND_THREE | | | v v v +----------+ +----------+ +----------+ | init: | | 1. | | 2. | | rside: - | | rside: C2| | rside: | | wside: - | | wside: C1| | wside: C2| +----------+ +----------+ +----------+ after: *pass_pipefd == pfd == rside of '|2' // NOT ON BOARD: procedure of cmd_exec(COMMAND_THREE) // pre-exec COMMAND_THREE: pfd == rside of '|2' // since there is *NOT* a pipe next to COMMAND_THREE, pfd will be set to 0 at the end // fork child, child set read side of '|2' to be itself // child exec C3 // pfd <- 0 For COMMAND_THREE (cmd_exec(COMMAND_THREE, pass_pipefd): before: *pass_pipefd == pfd == rside of '|2' |? COMMAND_ONE | COMMAND_TWO | COMMAND_THREE | | | v v v +----------+ +----------+ +----------+ | init: | | 1. | | 2. | | rside: - | | rside: C2| | rside: C3| | wside: - | | wside: C1| | wside: C2| +----------+ +----------+ +----------+ after: *pass_pipefd == pfd == 0 end]