|CSC 161||Grinnell College||Fall, 2011|
|Imperative Problem Solving and Data Structures|
The purpose of this lab is to learn about types and variables in C.
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.
In C there are 4 primitive types:
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.
To view this header type the following command into a terminal window:
You can scroll through the header like you would a man page. Type:
into the buffer and press return. It should then jump to where the size of a char is defined.
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.
Write a simple C program that declares a
int, and a
See what happens when you try to assign the value of each
one to one of the other types.
char7 to an
double7.12 to an
char58 to a
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.
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.
In C, it is possible to add an integer value to a char value.
c = c + 7;
printf("c + 7 = %c\n", c);
Add 7 to c again (you can just copy the two lines again) and a print out the value.
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.
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.
You will notice it is now a zero again.
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:
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
limits.h header for the size of
char. Do you have any idea why 128 and 255 would
be the limits for
char variables respectively?
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
* 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.
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.
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.
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