Homework 4 Solutions

  1. You are given the following declarations and statements:
    	int *num_array;
    	int **char_array;
    	
    	num_array = (int *)malloc(sizeof(int) * 15);
    	char_array = (char **)malloc(sizeof(char *) * 10);
    	char_array[3] = (char *)malloc(sizeof(char) * 30);
    	
    malloc returns the decimal addresses of 100, 300, and 500 respectively.

    1. What is the value of num_array? 100

    2. What is the value of num_array+4? 116--the answer is derived by noting that the expression involves pointer arithmetic. Since num_array points to integers, the 4 will be multiplied by the number of bytes in an integer, which is also 4. The result, 16, is added to the contents of num_array, which is 100, to arrive at the final result of 116.

    3. What is the value of &num_array[6]? 124--the answer is derived by noting that the expression asks for the address of num_array[6]. Since num_array points to integers, the address of num_array[6] is at 100 + 6(4) = 124.

    4. What is the value of char_array? 300
    5. What is the value of char_array[3]? 303--the answer is derived by noting that the expression involves pointer arithmetic. Since char_array points to characters, the 3 will be multiplied by the number of bytes in a character, which is 1. The result, 3, is added to the contents of char_array, which is 300, to arrive at the final result of 303.

    6. What is the value of &char_array[3][2]? 502--the answer is derived by noting that the expression asks for the address of char_array[3][2]. This address can be derived from pointer arithmetic:
      		address = char_array[3] + 2 = 500 + 2(1) = 502
      		

  2. Is the following set of statements valid? Why or why not?
    	char **array;
    	array = (char **)malloc(sizeof(char *) * 20);
    	strcpy(array[5], "brad");
    	
    Solution: The final statement is invalid because array[5] does not point to a legitimate memory address. The malloc on the previous line allocates a memory block that can hold 20 character pointers but it does not initialize these pointers. After the malloc, the block of memory assigned to array looks like:
    	array ----> -----
    		    | ? |
    		    -----
    		    | ? |
    		    -----
    		    | ? |
    		    -----
    		    |...|
    		    -----
    		    | ? |
    		    -----
    	
    The statement would have been ok if it had been written as:
    	array[5] = strdup("brad");
    	
    In this case the strdup would have allocated enough memory to hold the string "brad."

  3. Is the following set of statements valid if I want to create a dynamic array of 30 strings, each of which is 10 characters long? Why or why not?
    	char *array;
    	array = (char *)malloc(sizeof(char) * 300);
    	
    If you think it is valid, write two statements that assign:
    1. assign the character string "brad" to the 5th array element.
    2. accesses the 3rd character in the 5th array element and assigns that character to a variable declared as "char letter". You may not say:
      	letter = 'a';
      	
      I want to see the array notation you would use to access the third character.
    Solution: Technically the statements could be used to create a dynamic array of 30 strings, each of which is 10 characters long but I would not do it this way. The strings would start at array locations 0, 10, 20, 30, 40, etc. I could write the above two statements as:
    	strcpy(array[50], "brad");
    	letter = array[52]
    	
    but this notation is awkward and prone to error. When I have an array of strings I want to be able to access it using the following, more natural notation:
    	strcpy(array[5], "brad");
    	letter = array[5][2];
    	
    In order to use the array in this manner, I would have needed to use the following set of statements to create the array:
    	char **array;    // declare an array of char * pointers
    
    	// get storage for 30 string pointers 
    	array = (char **)malloc(sizeof(char *) * 30);
    
    	// allocate 10 character memory blocks for each of the
    	// pointers to point to
    	for (i = 0; i < 30; i++)
    	    array[i] = (char *)malloc(sizeof(char) * 10);
    	

  4. The following problem will give you practice with typedefs:
    1. Write a typedef statement that declares a struct with the following three fields:

      • a field of type double named "gross_pay,"
      • a string capable of holding a string of up to 20 characters named "last_name," and
      • a field of type int named age.
      You may call the struct whatever you like or you may make it be anonymous. The name of the type should be "student.".
      Solution:
      	typedef struct {  // this struct is an anonymous struct
      	    double gross_pay;
      	    char last_name[20];
      	    int age;
      	} student;
      	

    2. Write a declaration that creates a variable named "new_student" that is a pointer to a "student.".
      Solution:
      	student *new_student;
      	
    3. Write a statement that mallocs a student and assigns the result to new_student.
      Solution:
      	new_student = (student *)malloc(sizeof(student));
      	

    4. Write a printf statement that prints the following string in a left-justified field of 20 spaces:
      	char *name;
      	
      Solution:
      	printf("%-20s", name);
      	

    5. You are given the following declarations:
      	struct employee {
      	    int age;
      	    char last_name[20];
      	}
      	struct employee *a;   
      	int *b;
      	char name[20];
      	char *save_name;
      	void *void_ptr;
      	
      Place a check mark next to any line you think might cause a compiler error and explain why. Place an X next to any line that you think could prove problematic at runtime and explain why. You may assume that all variables that are assigned to another variable have been given malloc'ed pieces of memory.
      	void_ptr = (void *)b; 
      	 Not only is the statement correct but it uses good form by type
      	    casting b to be a void *. 
      
      	void_ptr = (void *)name; save_name = (char *)void_ptr;
      	 Another perfect set of statements. name is properly
      	    cast to a (void *) and then the (void  *) is cast
      	    back to a (char *)  for assignment to save_name.
      
      	_/ void_ptr = (void *)a; void_ptr->age = 30;
      	 You know that the void_ptr points to an employee struct
      	    but the compiler is not that smart. To the compiler 
      	    a (void *) is a generic pointer and you cannot access
      	    any field through a void pointer. You would have to
      	    cast the void_ptr back to an employee struct in order
      	    to be able to access the age field.
      
      	X void_ptr = (void *)b; name = (char *)void_ptr;
               The compiler is happy because you have cast the pointers
      	    to the right types. However the second assignment makes
      	    no sense. You are assigning an integer pointer to a
      	    variable that expects a character pointer. When the
      	    program tries to interpret name as a character string,
      	    it will get garbage.
      
      	void_ptr = a;
      	 A trick question of sorts. Good form dictates that you
      	    cast a to a (void *) before assigning it to void_ptr
      	    but the compiler will accept the above statement and
      	    your program will work perfectly at run-time. The
      	    rationale for the compiler accepting the statement is
      	    that you are assigning a restricted type, an (int *),
      	    to a less restricted type, a (void *) or more generally,
      	    a generic pointer. If you want an analogy, think of
      	    assigning an integer to a floating point variable. You
      	    do not have to perform a cast because you are assigning
      	    a more restricted type, an int, to a less restricted
      	    type, a floating point number. In doing so you lose
      	    no precision. The same can be said of assigning a typed
      	    pointer to a void pointer. 
      	
    6. Write a program using the tokengen library described in the class notes that formats a file so that all lines contain 80 characters or less. If a token is the string "<p>" then you will output a blank line and start a new paragraph. In effect you are writing an extremely simple html formatter. Basically you will read words using the token library and keep printing them on the current line until you encounter a word whose length would cause the line to exceed 80 characters. When you encounter such a word you output a newline character and start a new line. The words on a line should be separated with a single space.
      Solution:
      #include <stdio.h>
      #include <string.h>
      #include "tg.h"
      
      // define the extent of a line here so that it can be easily changed
      // in the future
      #define MARGIN 80
      
      main() {
          TokenGen *tg;
          char *token;
          int col = 0;
      
          // open a token generator that reads from stdin and read the
          // first token
          tg = new_tokengen(0);  
          token = tokengen_get_token(tg);
      
          while(token != 0) {
      	// if the token is <p> start a new paragraph
      	if (strcmp(token, "<p>") == 0) {
      	    printf("\n\n");
      	    col = 0;
      	}
      	// do not print a space before the first token on a line
      	else if (col == 0) {
      	    printf("%s", token);
      	    col = strlen(token);
      	}
      	else {
      	    // before printing the token on the current line make
      	    // sure that it can fit on the current line. The reason
      	    // we add 1 to strlen(token) is because we must include
      	    // the space between the previous word and token
      	    col += (1 + strlen(token));
      	    if (col > MARGIN) {
      		// if the token needs to be printed on the next
      		// line, start a new line but do not read a new
      		// token since we still need to process this token
      		// in the next loop iteration.
      		col = 0;
      		printf("\n");
      		continue; // don't read another token
      	    }
      	    else {
      		printf(" %s", token);
      	    }
      	}
      	token = tokengen_get_token(tg);
          }
      }