CSC 161 Grinnell College Fall, 2011
 
Imperative Problem Solving and Data Structures
 
 

Laboratory Exercise: Types and Variables

Goals

The purpose of this lab is to learn about types and variables in C.

Introduction

To program in C, you must be familiar with the few primitive types of variables provided to manage data with. Further, C allows for many implicit and explicit type conversions between these storage classes. In this lab you will perform different legal, illegal, and unusual operations, and examine the results.

Primitive Types

Exercise 1

In C there are 4 primitive types:

char
int
float
double

Each of these primitive types is represented by a number of bytes. Each byte contains 8 bits. A bit is a single 1 or 0 at the machine level. Since each type represents different data, each type has a different size (in bytes) that it needs to be in order to properly contain its data. Standard values for the size of each type can be found in the <limits.h> header.

  1. To view this header type the following command into a terminal window:

    less /usr/include/limits.h

    You can scroll through the header like you would a man page. Type:

    /char

    into the buffer and press return. It should then jump to where the size of a char is defined.

    1. What is the size of a char?

    As you scroll down you will notice more constants defined for ints. You will also notice constants defined for the varying types of chars and ints. In C you can specify a short or long (or even a long long) integer and whether you want it signed or unsigned. There are further modifiers but they are not relevant at the moment.

    1. What is the integer type that allows you to store the largest number?
    2. What number is that?
  2. Write a simple C program that declares a char, an int, and a double . See what happens when you try to assign the value of each one to one of the other types.

    1. Assign an int to a char.
    2. Assign a char 7 to an int.
    3. Assign double 7.12 to an int.
    4. Assign char 58 to a double.

You may recall there is a fourth type float that you didn't use in the last few steps. doubles are almost always preferred to floats. A double is, in fact, just a long float. Since space is not really a consideration for programs doing floating point arithmetic, doubles are the more common type. In fact, compilers today rarely even do single precision floating point arithmetic. Most just convert all floating point numbers in math operations to double precision values before performing operations on them for simplicity.

Conversion Errors

Download the program conversion-errors.c and compile and run it. In its current state, the program simply highlights the importance of initializing variables. It declares one of each of the primitive types of variables and displays their contents before and after initialization.

Exercise 2 -- Unexpected Results

Adding ints to chars

In C, it is possible to add an integer value to a char value.

  1. Add the following lines of code after the /* Add code here */ comment:
c = c + 7;
printf("c + 7 = %c\n", c);
  1. Save, compile, and run the program again. What is the value of c?

Add 7 to c again (you can just copy the two lines again) and a print out the value.

  1. What is printed?
  2. Is that what you expected?

Set c back equal to zero and print out the result:

c = 0;
printf("char c = %c\n", c);

You may notice the print statement doesn't print out c as being zero.

  1. Why is it blank to the right of the equals sign?

Behind the scenes, a char is really just a one byte integer. This is why an expression such as: c + 7; is legal. The only difference is how printf interprets the value stored in the char variable c.

  1. Set c equal to 48 and print it again.

You will notice it is now a zero again.

Overflow Errors

Exercise 3

Now that you know chars are really just ints behind the scenes, look at what happens when we assign bigger values to chars and unsigned chars. Add an unsigned char uc to your program and initialize it to the tilde character. Also set c equal to the tilde character and add print statements to display the values as integers rather than characters:

unsigned char uc = c = '~';
printf("char c = %d\n", c);
printf("unsigned char uc = %d\n", uc);

Now add one to c and uc. Print the results:

c += 1;
uc += 1;
printf("char c = %d\n", c);
printf("unsigned char uc = %d\n", uc);

Again, add one to both variables and print the results again (just copy and paste the four lines again). Why is c different from uc?

Now, set both c and uc equal to 255. Print out the values by copying the same two printf lines you have been using. Again, add one to each variable and print out the result What do you notice?

Experiment with different values for c and uc:

  1. Try values greater than 256.
  2. Try floating point values.
  3. Try putting a negative number in uc.

You will notice many unexpected results. In general, c will only behave as expected for values between -128 and +127 inclusive. uc will only behave as expected for values ranging from 0 to 255. Think back to the value in the limits.h header for the size of a char. Do you have any idea why 128 and 255 would be the limits for char and unsigned char variables respectively?

Casting

As you may have encountered in the previous exercise, c is pretty forgiving as to what types can be assigned to each other. Whether you realized it or not, the assignment operator, =, converts between certain types silently because they are easily compatible -- such as chars and ints. Even in the case of types that are less compatible such as ints and doubles, if you try to assign a double value to an int variable, the compiler does not complain. It simply drops the floating point (the decimal). If you didn't try that above, try it now.

i = 2.7;
printf("int i = %lf", i);

The process of converting between variable types is called casting. If you don't have to say anything for the process to take place, it is called implicit casting. This is the case with all the math operations in c. The +, -, *, /, and = operators all implicitly cast across primitive types. The rules for implicit casting can be found in K&R appendix A6 (specifically section 5).

The other type of cast is an explicit cast. This is where you tell c exactly what you want to happen. The line:

int x = (int) 3.14;

tells the compiler (but more importantly anyone reading the code) that it is explicitly intended to convert the double (3.14) to an integer and store it in the space x which is available for integer data.
* Note the f after 3.14 in the code above. You can postfix numeric values to explicitly state what type you would like the compiler to interpret them as. For example, 100f results in 100.000000 and 32l means 32 should be read as a long.

You may wonder why explicit casting is necessary if the compiler already does it implicitly. As hinted at, it is usually an effective way of informing anyone reading your code that you mean to do something that may be questionable such as dropping the decimal from a number. The second reason is that casting is necessary when dealing with more complicated data such as pointers and structs. The compiler will not let you convert between certain types unless you explicitly tell it that is what you want to do.

Finally, there is a danger in casting. As you experienced when playing with signed and unsigned characters, types have sizes. If you try to put a number that is bigger than the size of the type you are casting it to, you will get undesired results. Also, if you cast signed numbers to unsigned types, you can encounter odd results. Remember, a signed type has one less bit (to make room for the sign bit) to represent data with.

Exercise 4

  1. Declare a signed int and initialize it to a negative number greater than INT_MIN from the limits header (so between INT_MIN and 0).
  2. Then, declare an unsigned int.
  3. Cast your signed integer to an unsigned integer and assign its value to your unsigned int variable.
  4. Finally, print the two values and examine the results.

If you called your signed integer s, your explicit cast and print statements would look something like this:

unsigned int u = (unsigned int) s;

printf("signed int s = %d\n", s);
printf("unsigned int s = %u\n", u);

* Note the use of %u to specify an unsigned int in the last printf statement.

  1. Explain why the values print out as they do.

ASCII Table -- If time permits..

Recall earlier that setting a char equal to 0 is not the same as setting it equal to '0'. As you may know, this is because the zero in single quotes is actually interpreted as the character zero. The character zero has the integral value 48. That means setting a char equal to 48 is the same as setting it equal to '0'.

Write a simple program that prints out all the ASCII characters. If you know how to use a for loop, this would be an ideal time to do so -- lest you insert upwards of 95 print statements into your code. * Note that while chars can range from 0 to 127, the printable characters start at 32.

Reminder: Complete Evaluation Form

When you have finished this lab, be sure to fill out its evaluation form in the "Lab Evaluation" section for CSC 161 on Pioneer Web