CSC 180 Notes for Tutorial #7 Wednesday 30 October Monday 4 November Clean-up of topics not yet covered. ==================================== In this tutorial, I would like you present lots of little bits of information that hasn't come up in lecture yet, but which any educated C programmer should know. Most of the material comes from chapters 7 and 10. Other basic data types ====================== You have learned about the integer data type int , the floating point data types float and double and the character data type char. It turns out that C also has a few more variations on these types. You might never actually need to use these data types, but you should know that they exist. When you declare an "int" variable, the compiler sets aside an amount of space for you to store your integer value. On the ECF machines, you get 32 bits of storage to store your number. The first bit is used to represent the sign of the number, and the remaining 31 are used to store the magnitude of the number. Hence you can store numbers in the range -2^31 to +2^31 - 1 (we loose the 1 because 0 needs to be represented.) If you know that your integers will positive, you might not want to "waste" the bit that is used to represent the sign of the number. In this case you can create an "unsigned int" variable using the declaration unsigned int v; The range of an unsigned int on the ECF machines is 0 to 2^32 - 1. To read or write an unsigned int variable with scanf or printf, you need to use a special format that tells the scanf/printf function how to interpret the value in the variable that is passed to it. This format is %u. So you would write: scanf("%u", &v); or printf("%u", v); to read or print an unsigned integer value. ====== If you know that your variable that holds integer values will not be used to store a very large range, you might use a "short int" variable, as in: short int grade = 83; Short int variables may take less space than regular int variables. If you have a large number of integers to store (in an array, for example), you might care to save memory by using "short int" instead of "int" variables. You can also declare: unsigned short int to hold a short int that does not need a sign. And if you know that you will want to represent very large integer values, you can use a "long int" variable type, as in: long int bigNumber = 1000000000; Long int variables may take more space than regular int variables. You can also declare: unsigned long int to hold a long int that does not need a sign. Above, I say "may take less space" or "may take more space" because the amount of space set aside for an int variable depends on the computer being used. On older generation computers, the "bus" from the memory to the CPU could hold 16-bits. On those machines, "regular" ints are 16-bits long. "short int" variables are also 16-bits long. But "long int" variables have a length of 32-bit. On many current machines, the "bus" from the memory to the CPU can hold 32-bits. On those machines, a "short int" in 16-bits long, and "int" is 32-bits long and a "long int" is also 32-bits long. On the new 64 bit machines, short ints are 16 bits, ints are 32 bits and long ints are 64 bits in length. To pass short or long ints to scanf or printf, put the letter "h" (for half ?) or "l" (for long) in front of the "d" or "u", as in long int big; short int small; unsigned short int pos; printf("%ld\n", big); printf("%hd\n", small); printf("%hu\n", pos); How can you tell the size of an int on the machine you are using? ================================================================= (1) Look at the standard include file and ( iso stands for international standards organization ) How? Execute the linux command more /usr/include/limits.h or more /usr/include/iso/limits.h The standard include files may be found in the directory /usr/include. For example, the file that is included when you issue the statement #include is the file /usr/include/stdio.h (2) Or you can use the sizeof() function. It returns the size of its argument, in bytes (1 byte = 8bits). For example, on the ECF machines, the program: #include int main() { short int s; int r; printf("The size of a short int is %u\n", sizeof(s)); printf("The size of a regular int is %u\n", sizeof(r)); return 0; } prints: The size of a short int is 2 The size of a regular int is 4 Note that the print format is %u This is because the sizeof() function returns an unsigned integer, which makes sense since a size is nonnegative! exercise: Try out the sizeof() function on other data types. Other representations for integer values. ========================================= You are used to writing statements like: int course = 180; And expect the number 180 to be stored in the "box" that we call "course". The number 180 is represented in decimal here, but of course in memory it is stored in a binary representation. The decimal number 180 means 1 x 10^2 + 8 x 10^1 + 0 x 10^0 The position of each digit in the number affects the contribution. We could represent 180 with respect to any other positive integer base. For example, 180 in decimal is also 11 x 16^1 + 4 x 16^0, so its hexadecimal (base 16) representation is B4. (We use the letters A,B,...,F to represent the decimal numbers 10, 11, ..., 15, respectively). In C, you can express numbers in hexadecimal by writing "0x" in front of them. So the statement int course = 0xB4; assigns the decimal value 180 to course. Furthermore, we can write 180 in decimal as 2 x 8^2 + 6 x 8^1 + 4 x 8^0, so its octal (base 8) representation is 264. In C, you can express numbers in octal by writing "0" in front of them. So the statement int course = 0264 assigns the decimal value 180 to course. (Note! This means that you shouldn't start decimal integer constants with a 0. int grade = 0100 means that grade is assigned the decimal value 1 x 8^2 + 0 x 8^1 + 0 x 8^0 = 64 not one-hundred!) To read and write octal and decimal numbers, use the formats %o and %x respectively. Bigger floating point numbers ============================= On the machines that you are using, the sizeof(float) is 4 bytes and the sizeof(double) is 8 bytes. If your application requires a huge range of values (the range depends on the size of the exponent) or a huge amount of precision (the precision depends on the size of the mantissa) you can declare a "long double" variable, as in long double bigNumber; On the ECF machines, the sizeof(bigNumber) is 12 bytes. Don't use these extra long variables unless you really need them. They use more storage. And most CPUs don't perform arithmetic on them very quickly. My recent tests show that the multiplication of two long doubles takes about 200 times longer than the multiplication of two doubles. How big can numbers get? ======================== The above discussions suggest that numbers can't be arbitrarily large: What happens if you run the program: #include #include int main() { int i = INT_MAX; /* INT_MAX has the value of the largest int */ int j; for ( j = 0; j < 3; j ++ ) { printf("i = %d\n", i); i++; } return 0; } We get the output: i = 2147483647 i = -2147483648 i = -2147483647 The integers "overflow" the space set aside for them, and then "wrap around" to the smallest integer (-2147483648). Things are better in the floating-point case. We have: #include #include int main() { double d = DBL_MAX; /* DBL_MAX has the value of the largest double. */ int j; for ( j = 0; j < 3; j ++ ) { printf("d = %g\n", d); d = 2.0 * d; } return 0; } We get the output: d = 1.79769e+308 d = Inf d = Inf "Inf" stands for infinity! When we overflow floating-point variables, we get an approximation to infinity. static and automatic variables. ============================== Consider the following program: #include void count( void ); int main() { int i; for (i=0; i<5; i++) { count(); } return 0; } /* function that counts the number of times it has been called. */ void count( void ) { int call_counter = 0; call_counter += 1; printf("I've been called %d times.\n", call_counter); } What will the output be? Answer: I've been called 1 times. I've been called 1 times. I've been called 1 times. I've been called 1 times. I've been called 1 times. Why does the counter not increase by one each time? Well, each time the function count() is called, a box goes on the function stack that looks like: -------------------------------------------- | count() | | void | ------------ ---------| | | | int call_counter 0 | | | -------------------------------------------- The call_counter variable is created automatically each time the function is called and it is initialized to zero. When the function completes, the information about it and its variables goes away and is lost. If we want the variable call_counter to keep its value between each call, we need to tell the compiler to create one version of it that will not be destroyed when the function is finished. We can do this by declaring the variable to be "static" in the count() function, as in: static int call_counter = 0; Then the output is: I've been called 1 times. I've been called 2 times. I've been called 3 times. I've been called 4 times. I've been called 5 times. Our picture of the memory model changes to include a "static space" where all long-term variables are kept. They can only be used by the function that declared them. function stack -------------------------------------------- | count() | | void | ------------ ---------| | | | | -------------------------------------------- -------------------------------------------- | main() | | int | ------------ ---------| | | | | -------------------------------------------- static space: -------------------------------------------- | count() | | void | ------------ ---------| | | | int call_counter 0 | | | -------------------------------------------- Note that the static variable is only initialized once to 0.