Teach Yourself C in 24 Hours

Background
I began programming by accident in 1965. My father brought home a “programmed instruction” which promised to teach RPG if followed. I took the programmed instruction and I guess that I then knew RPG. The answer to the question my father asked me was “Probably not”. The question was “Could an average factory manager take this programmed instruction and create reports to answer his own questions about his or her quality/work-in-progress/personnel data?”. Programming a computer then and now requires a very clear idea of what you want for results. And sufficient skill and experience with the tools you have available to produce results.

Along the way I used BASIC on HP Timesharing 2000, BASIC-PLUS on RSTS/E, Multi-User BASIC on DEC RT11, RT-11 Assembler, COBOL on DOS for S/360, COBOL,FORTRAN, and Assembler on DOS/VS, DOS/VSE, MVS, MVS/ESA, and z/OS for a living. I written programs in QBASIC and VisualBasic for fun on PC-DOS, Windows 3.1, Windows 95, 98, 2000, and XP. Our home computer is now running Debian Linux and I thought that this would be a good time to learn a new paradigm so I said to myself, “self, you need to learn glade and C++”. Well after much struggle I said: “The glademm for this version is thoroughly broken. The next level requires a new release of GTK which breaks something my son uses so I am stuck if I am going to continue to develop as we almost never boot Windoze any more.” I guess I will buy a book and learn C even though I said that I was going to use C++ and never learn any of the bad habits that C programmers have inflicted on the world. Never say a word you can’t eat.

The Book That I Bought

I bought Tony Zheng’s Teach Yourself C in 24 Hours Second Edition. This book is now in the 6th edition and has different authors. Hopefully some of these remarks will help even readers of the current, improved, edition.

The book is divided into “Hours”. These roughly correspond to the amount a college professor/instructor might cover in a classroom hour. Then there would be homework and/or lab assignments so the book would probably make a good classroom text, especially in a junior college or high school situation. I would suggest that you allow about 3 hours to spend on each “hour” of the book. This will give you time to read the chapter (scan, then read for understanding), answer the questions in the quiz then check your answers, and finally do the exercises. This is clearly a case of you get out what you put in so do not skimp on the exercises. Do each one for best results.

If I were an editor at SAMS, I would create a “current edition” of TYCI24H with GNU/Linux. What follows is the material I would add if I were the author/editor team.
Hour 1 Taking the First Step
What Tony Zheng does not say explicitly in his introductory material is that the Dennis Ritchie that created C also created Unix with Ken Thompson. Brian Kehrigan, who is mentioned, has created a book with Rob Pike called the “Unix Programming Environment”.

But I am using the Debian distribution of Linux. What do I need to do to develop in this environment?

My Debian has already been used for development so I may have added things that are not there initially. Learn to use

synaptic

package installer to install program packages if you do not find a program that I mention. If something you are looking for is not found, look for it by name or look for it when you list items that contain “dev”. So lets begin with the first programming example.

When Tony Zheng says Microsoft Visual C++ or Borland, you could use:

kate MyFirstProgram.c &

kate

” is a GUI text editor that is almost always there in KDE based systems. There are others (kwrite). Among Unix users, favorite editor is like religion. There is no editor which is right for everyone in all situations.

MyFirstProgram.c

” is the name that you will be saving your first program. The “.c” on the end is important because it will gives clues to the compiler. It may also give clues to the editor to turn on formatting aids.

“&” is a character that tells the shell to create a separate processes (kate) and return control to this command session. This allows you to use the terminal session window for other things while the editor is open. Kate does this by design with or without the & but not all programs do.

Once kate is running, you can edit, save, and view multiple files. Use Kate’s help for more information on Kate.

Kate also has a view feature which will display line numbers just like the examples. It can be toggled with F11 or use the view menu. Kwrite is similar. The GNOME equivalent is gedit.

When you get to Build Menu, we begin to go astray. We are not in the Windows C++ Integrated Development Environment (IDE) but in the Linux/GNU flexible and free software environment. With this freedom we need to take some additional steps. Type in the program as explained in the text make sure that you type it correctly. Use kate’s “File” menu to “Save” and then return to the command line window. Type:

gcc MyFirstProgram.c

gcc is the GNU project’s universal compiler. Based on the dot type, in this case .c, it will make some assumptions and compile and link your program with whatever defaults.

The examples which follow were copied from the log stream of the Linux terminal session window. In my system, the terminal prompt is set to display the hostname (myhome:) and the path (~/Projects/c) and a $-sign. What you need to type is after the $-sign.

At this point we have “nearly infinite degrees of freedom” depending on your system, your typing skills, and celestial mechanics. When I tried it, I got:

myhome:~/Projects/c$ gcc MyFirstProgram.cMyFirstProgram.c: In function `main':

MyFirstProgram.c:6: error: parse error before "return"

I knew instantly that I had left off the semicolon after the right parenthesis on the “printf” statement. Would you have guessed that? I didn’t think so. But leaving off the semicolon (or putting one in where it does not belong) are common errors.

The C language is, like most computer languages, very sensitive to syntax, spelling, and punctuation. Particularly when the punctuation is wrong, the compiler scanner gets lost and runs into the next statement or word that it recognizes. The compiler then reports the error. Usually you can narrow it down by looking at that words in the reported error and perhaps the message before that. Sometimes you just have to struggle. Be patient. Look closely. Try doing it a different way. Save the changes and try again. Look through all the messages to see if you see anything obvious that you can fix. But often, just fixing one early mistake will result in a clean compile when the number of messages seemed to be a hopeless mess.

I went back to kate, put on the missing semicolon, and saved again. Returning to the command line, I listed the files in my directory with the ls command. the ~ (tilde) means home directory and /c is a directory I created with the mkdir command to hold my C programs. Remember that Linux is case sensitive. kate saves the previous version of your file in a version with a ~ (tilde) appended to the name. So MyFirstProgram.c~ is a backup version. If you mess up your regular version and accidentally save it, you can

cp MyFirstProgram.c~ MyFirstProgram.c

to clobber the bad version with the saved version.

~/c$lsMyFirstProgram.c MyFirstProgram.c~

~/c$ gcc MyFirstProgram.c

~/c$

No messages. Nothing happened. Linux only reports problems. If the expected happens, you just get the prompt. No messages. No OK. Just the prompt, ready for another command.

But you can verify that something happened. Use the ls command again.

~/c$ ls
MyFirstProgram.c MyFirstProgram.c~ a.out

Where did that a.out come from. “a.out” is the default program output from gcc. If you do not name the program, it will be named a.out, and marked as executable. If you are using a “colors” option, the execute bit setting may give the executable program a different color in the

ls

listing.

Try to execute that default name:

~/c$ abash: a: command not found

Linux is case sensitive and needs correct spelling. Some other operating systems will guess at a.exe, a.cmd, a.bat trying to find something that matches your request. Linux will give you EXACTLY what you ask for. Be precise and it will serve you well.

~/c$ a.outHowdy, neighbor! This is my first C program.

Or maybe it isn’t. “a.out” could have come from anywhere in the path. To make sure that you get your own personal local version use:

~/c$ ./a.out

The ./ will explicitly execute the version in the current working directory.

Someday soon, all of these things will be transparent and you will not think about them. Then the you will be part of your own Personal Integrated Environment. When such enlightenment occurs, enjoy the experience.

You probably wanted your program to have an executable name like “MyFirstProgram”. To do this with gcc, just specify the -o name switch.

~/c$ gcc -o MyFirstProgram MyFirstProgram.c~/c$ ls

MyFirstProgram MyFirstProgram.c~ a.out

MyFirstProgram.c

Now we see the program with the specified name. Note that it has no “extension” or “filetype”. The “.exe” on the end of the program in the example in the book has no meaning to Linux or Unix. The program’s executablility is set from execute bits in the directory, set by the linker when it was created.

Use the “long” form of directory listing with the -l switch:

~/c$ ls -ltotal 44

-rwxr-xr-x 1 cbcalvin cbcalvin 12148 Jun 26 20:18 MyFirstProgram

-rw-r--r-- 1 cbcalvin cbcalvin 102 Jun 26 18:48 MyFirstProgram.c

-rw-r--r-- 1 cbcalvin cbcalvin 101 Jun 26 18:32 MyFirstProgram.c~

-rwxr-xr-x 1 cbcalvin cbcalvin 12148 Jun 26 18:52 a.out

We see that, by default, the executable bit (x) is set for me(—x——), my group(——x—), and everybody (———x). This is shown in the permissions string at the left of the entry in the long form (—x–x–x). If you can read it, you can execute it. At least until it gets changed.

You may want to get rid of that default program name – a.out

~/c$ rm a.out

to remove it. Doing the ls again:

~/c$ ls

MyFirstProgram MyFirstProgram.c~ MyFirstProgram.c

Any questions? Ask the man. Or better, info. The man pages were the traditional documentation for all Unix systems. The man command “man whatever” would produce a listing of the documentation for whatever. More recently, info is the way to go. The info system, where available, is scrollable, up and down as well as providing menu selection and sub-menu items. You need not access the manual sequentially if you use the info pages. Just use “info whatever”.

And if you are like me, you have memory failure. The apropos command helps locate information that is maybe only partially remembered. The command “apropos something” will search all of the info/man files for something and give you a one-liner on each to refresh your memory.
Hour 2 Writing Your First C Program
Now that we have created the source program in the example and saved it as MyFirstProgram, we could:

1. Copy it to the program name in the example in the second chapter. Then there will be two identical copies of the program under each name. The Linux command to do this is cp.

2. Rename the program to the name used in the second chapter. The Linux command to do this is mv for move.

3. Or we could create a link, creating an alias or pointer from a new name to the original name. The Linux command to do this is ln.

cp MyFirstProgram.c 02L01.c

or

mv MyFirstProgram.c 02L01.c

or

ln -s MyFirstProgram.c 02L01.c

The first will result in two directory entries and two separate copies of the program.

The second will result in one directory entry (under the new name 02L01.c) and only one copy, the original, under a new name.

The last will create an additional symbolic directory name. The original directory and program file are unchanged.

myhome:~/Projects/c$ lsMyFirstProgram.c

Create a copy of the original program:

myhome:~/Projects/c$ cp MyFirstProgram.c 02L01.cmyhome:~/Projects/c$ ls

02L01.c MyFirstProgram.c

myhome:~/Projects/c$ ls -l

total 8

-rw-r--r-- 1 cbcalvin cbcalvin 102 Jun 26 22:22 02L01.c

-rw-r--r-- 1 cbcalvin cbcalvin 102 Jun 26 18:48 MyFirstProgram.c

There are now two copies of the program. Each of them has 102 bytes. I will remove the copy so things are like they were before.

myhome:~/Projects/c$ rm 02L01.c

And rename (or move) the original to a new name:

myhome:~/Projects/c$ mv MyFirstProgram.c 02L01.cmyhome:~/Projects/c$ ls

02L01.c

I will rename it back so that things are like they were so I can show that the effect is similar:

myhome:~/Projects/c$ mv 02L01.c MyFirstProgram.cmyhome:~/Projects/c$ ls

MyFirstProgram.c

Now I will create a link to the original program with a new name:

myhome:~/Projects/c$ ln -s MyFirstProgram.c 02L01.cmyhome:~/Projects/c$ ls

02L01.c MyFirstProgram.c

Looks the same as the first but the long listing will highlight the difference.

myhome:~/Projects/c$ ls -ltotal 4

lrwxrwxrwx 1 cbcalvin cbcalvin 16 Jun 26 22:16 02L01.c -> MyFirstProgram.c

-rw-r--r-- 1 cbcalvin cbcalvin 102 Jun 26 18:48 MyFirstProgram.c

Note the 02L01.c is only 16 bytes long and that before the permissions string there is an l. Note too that following its name there is a -> and the name of file which it is a link to. The link name can be used as an alias wherever the target name would be used. The results should be the same.

So now we can get back to the story in the book. Just to keep things tidy and avoid confusion, I have moved the programs from chapters other than the current chapter to another location. Actually, I just hid them by renaming them with the mv command to a name that is the same but preceded by a “.”. This hides a file from the default ls command. To see the hidden files, specify the -a (all) or -A (almost all) option.

Do the exercises at the end of chapter 2.

The answers to exercises 1, 2, and 3 can be found in Appendix B. If you compile exercise 4 you will get no warnings. Turn on the warning messages with -Wall (warn all) and you get the error described. Even more surprising, exercise 5 compiles without any reported errors without -Wall. This is a more severe error but the gcc figures out that you meant to include stdio and quietly supplies it for you. This is too liberal for my taste, but it does make life simpler if a bit sloppy.

Since there are now bunch of things to get right every time I compile a new program, I created a simple shell script. A shell script is a set of commands and perhaps logic which allows complex sets of commands to be run. I may discuss this more later but for now I will just list the script file info with ls and the script itself with the cat command.

myhome:~/Projects/c$ ls -l xit.sh-rwxr-xr-x 1 cbcalvin cbcalvin 86 Jul 2 22:36 xit.sh

myhome:~/Projects/c$ cat xit.sh

#!/bin/sh

# a shell script to build the examples in Teach Yourself C

gcc -Wall -o $1 $1.c

Note that this script is read-able and executable (r-x) for self, group, and everybody else. It is modifiable only by self (rwx).

The initial comment #!/bin/sh is a signal to the shell that this script is to be executed in a shell. The next line is comment describing the script’s function. Last is the compiler command that we used above. The difference here is that instead of an actual name, we have a special argument name $1. This stands for the first word following the command. Since we are taking the defaults for all of the options except for the -Wall we can get away with just one argument. This script will insure that the output executable is named the same as the .c file used to create it.
Hour 3 Learning the Structure of a C Program
This chapter teaches the basic structure of a C program or function. The listing in Listing 3.1 is actually the same program as Listing 2.1. This throws off the numbering of the other listings in this chapter. My numbers correspond to the Listing number, not the program id Zhang assigns.

So the Listing 3.2 I have created is 03L02.c for the function. And Listing 03L03.c is just a main program that #includes “03L02.c”. For me, this too is part of program structuring. Since we have already used the #include preprocessor directive, it might be good at this time to point out that if the name is in angle brackets, then the include path is searched. Some documentation refers to this as “all the usual places”. If you want your file to be found in the current directory, put the name in double-quotes “my-name-goes-here”.

/* 03L02.c: This function adds two integers and returns the result */int integer_add(int x, int y)

{

int result;

result = x + y;

return result;

}
/* 03L03.c: Calculate an addition and print out the result */#include <stdio.h>

#include "03L02.c"

int main()

{

int sum; sum = integer_add(5,12);

printf("The addition of 5 and 12 is %d.n", sum);

return 0;

}

And the same for exercises 4 and 5. Again, 03X05 is just a main program that includes the function instead of copying the whole function text into the program it is just #include-ed.
Hour 4 Understanding Data Types and Keywords
Review the list of keywords. While it is useful to have all of the words in one place, it might also be useful to have them by description to make it easier to related

I created this table

auto Storage class specifierbreak Statement

case Statement

char Type specifier

const Storage class modifier

continue Statement

default Label

do Statement

double Type specifier

else Statement

enum Type specifier

extern Storage class specifier

float Type specifier

for Statement

goto Statement

if Statement

int Type specifier

long Type specifier

register Storage class specifier

return Statement

short Type specifier

signed Type specifier

sizeof Operator

static Storage class specifier

struct Type specifier

switch Statement

typedef Statement

union Type specifier

unsigned Type specifier

void Type specifier

volatile Storage class modifier

while Statement

Then sorted it with the GNU utility sort

~/$ sort -b -k 2 C_keywords.txt > c_keywords_sorted2.txt~/$ cat c_keywords_sorted2.txt

default Label

sizeof Operator

break Statement

case Statement

continue Statement

do Statement

else Statement

for Statement

goto Statement

if Statement

return Statement

switch Statement

typedef Statement

while Statement

const Storage class modifier

volatile Storage class modifier

auto Storage class specifier

extern Storage class specifier

register Storage class specifier

static Storage class specifier

char Type specifier

double Type specifier

enum Type specifier

float Type specifier

int Type specifier

long Type specifier

short Type specifier

signed Type specifier

struct Type specifier

union Type specifier

unsigned Type specifier

void Type specifier

for your reference.

If your program uses this

printf("convert the value of c1 %d to character: %c.n", c1, c1);

statement , it becomes quite clear that the char type is -numeric- and that the output format is dependent on what is coded in the printf statement.

This also means that the only change between listing examples 04L01 and 04L02 is the way the the values for C1 and C2 are coded. With this form, the student can be invited to try a number of character values or numeric values and test the result.

When discussing the int type, Zhang invites the student to determine the maximum value for the int data type. This is an opportunity for a little discussion of storage length, sizeof(), and range of values.

The following two programs were found at:

http://home.att.net/~jackklein/c/inttypes.html#c_prog

/* A Program To Display Integer Type Information With Any C Compiler. */#include <stdio.h>

#include <limits.h>

volatile int char_min = CHAR_MIN;

int main(void)

{

printf("nnnnn Character Typesn");

printf("Number of bits in a character: %dn",CHAR_BIT);

printf("Size of character types is %d byten",(int)sizeof(char));

printf("Signed char min: %d max: %dn",SCHAR_MIN, SCHAR_MAX);

printf("Unsigned char min: 0 max: %un",(unsigned int)UCHAR_MAX);

printf("Default char is ");

if (char_min < 0)

printf("signedn");

else if (char_min == 0)

printf("unsignedn");
elseprintf("non-standardn");

printf("n Short Int Typesn");

printf("Size of short int types is %d

bytesn",(int)sizeof(short));

printf("Signed short min: %d max: %dn",

SHRT_MIN, SHRT_MAX);
printf("Unsigned short min: 0 max: %un",(unsigned int)USHRT_MAX);

printf("n Int Typesn");

printf("Size of int types is %d bytesn",

(int)sizeof(int));

printf("Signed int min: %d max: %dn",

INT_MIN, INT_MAX);

printf("Unsigned int min: 0 max: %un",

(unsigned int)UINT_MAX);

printf("n Long Int Typesn");

printf("Size of long int types is %d bytesn",

(int)sizeof(long));

printf("Signed long min: %ld max: %ldn",

LONG_MIN, LONG_MAX);

printf("Unsigned long min: 0 max: %lun",

ULONG_MAX);

return 0;

}

A Program To Display Integer Type Information With A C99 Conforming Compiler.

If your complains about _Bool or long long when compiling this program it does not support these C99 data types.

#include <stdio.h>
#include <limits.h>
volatile int char_min = CHAR_MIN;
int main(void)
{
printf("Size of Boolean type is %d byte(s)nn",
(int)sizeof(_Bool));
printf("Number of bits in a character: %dn",
CHAR_BIT);
printf("Size of character types is %d byten",
(int)sizeof(char));
printf("Signed char min: %d max: %dn",
SCHAR_MIN, SCHAR_MAX);
printf("Unsigned char min: 0 max: %un",
(unsigned int)UCHAR_MAX);
printf("Default char is ");
if (char_min < 0)
printf("signednn");
else if (char_min == 0)
printf("unsignednn");
else
printf("non-standardnn");
printf("Size of short int types is %d bytesn",
(int)sizeof(short));
printf("Signed short min: %d max: %dn",
SHRT_MIN, SHRT_MAX);
printf("Unsigned short min: 0 max: %unn",
(unsigned int)USHRT_MAX);
printf("Size of int types is %d bytesn",
(int)sizeof(int));
printf("Signed int min: %d max: %dn",
INT_MIN, INT_MAX);
printf("Unsigned int min: 0 max: %unn",
(unsigned int)UINT_MAX);
printf("Size of long int types is %d bytesn",
(int)sizeof(long));
printf("Signed long min: %ld max: %ldn",
LONG_MIN, LONG_MAX);
printf("Unsigned long min: 0 max: %lunn",
ULONG_MAX);
printf("Size of long long types is %d bytesn",
(int)sizeof(long long));
printf("Signed long long min: %lld max: %lldn",
LLONG_MIN, LLONG_MAX);
printf("Unsigned long long min: 0 max: %llun",
ULLONG_MAX);
return 0;
}

Jack Klein has written a chapter in C Unleashed among other things. He has not given me permission to use his copyrighted material above. Nor did he give me permission to add the comma required in line 9.

Find out more about C Unleashed at

http://home.att.net/~jackklein/C_Unleashed/code_list.html

In addition, he has a number of very helpful remarks for students who go on to become real programmers (as in to earn their living from it). –> http://home.att.net/~jackklein/c/inttypes.html

I called the first program intsize.c and it gave me these results on my Debian Dell Pentium P4 system:

myhome:~/Projects/c$ intsize
Character TypesNumber of bits in a character: 8

Size of character types is 1 byte

Signed char min: -128 max: 127

Unsigned char min: 0 max: 255

Default char is signed

Short Int Types

Size of short int types is 2 bytes

Signed short min: -32768 max: 32767

Unsigned short min: 0 max: 65535

Int Types

Size of int types is 4 bytes

Signed int min: -2147483648 max: 2147483647

Unsigned int min: 0 max: 4294967295

Long Int Types

Size of long int types is 4 bytes

Signed long min: -2147483648 max: 2147483647

Unsigned long min: 0 max: 4294967295

Your mileage may vary.

LLONG and ULONG statements are not part of the C90 standard which is the default for gcc. To use the features for the C99 standard, specify

gcc -std=c99 -o intsize intsize.c

Meanwhile, back at the ranch, we need to get into the float types.

I added a fourth computation (32.0/10.0) along with two more variables. This completes the possiblities and shows that in gcc, the float to integer conversion takes place quietly.
Hour 5 Handling Standard Input and Output
Here is where I begin to have trouble with C and most treatments of C. As long as I was using printf(..) and knew I was going to stdout, I had no problem. But I get to Hour 5 and I need to get input from the user. I see that the syntax entry is int getc(FILE *stream); . I guess I can take it on faith that getc( stdin ); is correct syntax and will work. If I jump ahead 16 hours, I will probably get confused. But if I do not, I will wonder about how is FILE *stream like stdin.

I made a few changes to the program.

/* 05L01.c: Reading input by calling getc() */#include <stdio.h>

int main ()

{

int ch;

/* tell the user that what input is required */

printf("Please type in one (1) character followed by return=>");

ch = getc(stdin);

printf("The character typed is %c with a numeric value of %dn",

ch, ch);
return 0;}

I put the int before main() to avoid the warning message. The text printed tells the user what is required and leaves the cursor pointing at the input area. Note that control does not return to the program until the Enter key is pressed

And my results were:

Please type in one (1) character and press Enter=>abcThe character typed is a with a numeric value of 97

See if you can make your version of listing 5.3 produce output like:

myhome:~/Projects/c$ 05L03The character that has a numeric value of 65 is:A

The character returned is A with a numeric value of 65 from function putc();

The clues are in the text. The function putc() returns the value of the character putc’ed.

The listing 5.8 might be better illustrated by an example like this:

/* 05X02.c: Using precision specifiers */#include int main()

{

int int_num;

double flt_num;

int_num = 1;

flt_num = 123.4567;

printf("Default integer format: >%d<n", int_num);

printf("With precision specifier: >%8.2d<n", int_num);

printf("With left align: >%-8.2d<n", int_num);

printf("Default float format: >%f<n", flt_num);

printf("With precision specifier: >%8.2f<n", flt_num);

printf("With left align: >%-8.2f<n", flt_num); return 0;

}

Which I stumbled on while doing exercise 2. Here you see the output:

myhome:~/Projects/c$ 05X02Default integer format: >1<

With precision specifier: > 01<

With left align: >01 <

Default float format: >123.456700<

With precision specifier: > 123.46<

With left align: >123.46 <

Note that default integer is only as long as required. With the specifier, the width and number of digits displayed is controlled. And it can be left aligned within the specified width.

The default float is also illustrated, specified precision and left aligned for comparison. Note the 10 character width of the default and the precision “padding” of the non-existant precision (the value is only specified to 4 decimal places). Note the rounding in the specified precision regardless of alignment.

This link had a more complete explanation of formats:

http://www-ccs.ucsd.edu/c/lib_prin.html#print%20conversion%20specification

Hour 6 Manipulating Data

Mess around with these programs to make sure that you understand operator precedence.
Hour 7 Working with Loops
Especially useful in this hour are the exercises 3, 4, and 5, rewriting that hour’s “for” loops as “while” loops and “while” loops as “for”.
Hour 8 Using Conditional Operators
Relatively un-eventful. Almost fun like real programming. Mess with the hex and number types to really understand the structure and range of the numbers a bit. Zhang tends to hard code the numbers in his examples. I modified the examples so that in a statement printf(“The decimal value 1000 is the hex value %h\\n”,x) in his example I used

printf(“The decimal value %d is the hex value %h\\”,x,x);. This will help to reenforce the idea of the same value printed in different formats is still the same value. It also lets you modify and play with the value of x more easily.
Hour 9 Working With Data Modifiers and Math Functions
Here again, the examples could use variables instead of constants to reenforce the differences in representation and allow playing with the variables.

In the Gnu world, the gcc compiler does not automatically include the math function library. Up to this point, we have only used the functions that are in libc which is searched by default.

I modified the options when invoking the compiler to include the math library libm by inserting -lm in the script created in hour 2. In general, libraries need to be added for additional functions and this is one way to do them.

A good explanation is here

http://www.network-theory.co.uk/docs/gccintro/gccintro_16.html

But in reading that I found that

gcc -Wall $1.c -lm -o $1

would be the correct order (http://www.network-theory.co.uk/docs/gccintro/gccintro_17.html) so I changed the order of the input and output files.

The exercises here and the results are especially instructive. Zhang note the “results on my machine” explicitly. And on my machine they were in fact different. This relates back to the the lesson example on the length of the int. On my machine, the results can be made to match by specifying “short int x;” and “short unsigned int y;”.

For reference the maximum and minimum values for integers of various sizes can be found in the header

limits.h

gcc also allows a long long type (signed and unsigned) if the

-std=c99

or

-std=gnu99

option is used.
Hour 10 Controlling Program Flow
In the 6th example, a while loop is used to demonstrate how break can end a loop. The while loop has no (expression) to be true and results in a compile error. Like many compile errors it has curious effects.

myhome:~/Projects/c$ xit 10L06
10L06.c: In function `main':

10L06.c:9: error: parse error before '{' token

10L06.c:12: error: break statement not within loop or switch

10L06.c: At top level:

10L06.c:14: error: parse error before string constant

10L06.c:14: warning: type defaults to `int' in declaration of `printf'

10L06.c:14: warning: conflicting types for built-in function `printf'

10L06.c:14: warning: data definition has no type or storage class

When a condition is added such as:

while (0==0) {

the errors are eliminated.

myhome:~/Projects/c$ xit 10L06
myhome:~/Projects/c$ 10L06

Enter a character:

(Enter x to exit)

(within the range of 1 to 3):

a

b

x

Broke the infinite while loop. Bye!

While I do not remember him discussing a general technique for resolving compile time errors, the general rules I use are:

1. Look for the cause of the first error and correct it. 2. If you cannot determine what caused it, make small changes to see if you can make it move.
3. If you see any other errors, correct those also. Then re-compile.

The generalized technique led to problems with the continue statement example: A small error in typing resulted in a lengthy search for a solution. Instead of typing sum += 1;, I typed sum +- 1;, in the if statement for hour 10 listing 7. Compiling this results in

10L07.c:12: warning: statement with no effect

Running the program gives this erroneous result:

The sum of 1, 2, 4, 6, and 7 is: 0

Once you see the typing error, it is fairly obvious what the source is. But I did not see it at first and so ran into one of the traps that C lays for the unwary. In an if, while, for, or do construct, curly braces { } are only required if the code block executed is more than one statement. They are always acceptable. In trying to debug this, I put in a printf which moved the continue out of the un-braced block. So it still did not work. I added the curly braces with the same result. And then noticed the error in the sum =+ i statement. The lessons here are to re-read your code to make sure that it says what you think it says. And to check for and add curly braces when you add a statement in a one-line code block in and if, while, for, or do statement. I am thinking about always using curly braces just to avoid that trap.

Now I am learning about getc vs getchar. Reviewing hour 5.2 listing so this program

/* 05L02.c: Reading input by calling getchar() */
#include <stdio.h>
int main ()
{

int ch1, ch2;

/* tell the user that what input is required */

printf("Please type in two characters together and then the enter key=>");

printf("(end with q)n");

/*    ch1 = getc(stdin);   *//* this may be unnecessary */

ch1 = ' ';

ch2 = 'x';

while (ch2 != 'q') {

ch2 = getchar();

printf("The next character typed is %c with a numeric value of %dn", ch2, ch2);

}

return 0;

}

reads characters from stdin. It prints them when \\r (return) is pressed. It ends when it encounters a ‘q’. I guess I do not understand the point of the hour 5 listing with getc and getchar except that getchar() defaults to stdin and getc must specify it.
Hour 11 Understanding Pointers
Pointers! While some people view them as a necessary evil, I think that they should be taken care of by the compiler entirely.

Richard Jones and Paul Kelly put it nicely here:

http://www-ala.doc.ic.ac.uk/~phjk/BoundsChecking.html

Where it summarizes their paper on bounds checking pointers in C.

They state:

“With pointers in C, a pointer can be used in a context divorced from the name of the storage region for which it is valid.”

I will work through this as it will be the foundation for arrays and strings and structures.

As an example of the statement above, here is my program for exercise 11.4:

/* 11X04.c: */
#include <stdio.h>
int main()
{
int x, y;
int *ptr_x, *ptr_y;
printf("After declaration:n");
printf("x: address=%p, content=%dn",&x, x);
printf("ptr_x: address=%p, content=%p, ==>value=undefinedn",
&ptr_x, ptr_x);
printf("y: address=%p, content=%dn",&y, y);
printf("ptr_y: address=%p, content=%p, ==>value=undefinedn",
&ptr_y, ptr_y);
ptr_x = &x;
ptr_y = &y;
printf("After pointer assignment:n");
printf("x: address=%p, content=%dn",&x, x);
printf("ptr_x: address=%p, content=%p, ==>value=%dn",
&ptr_x, ptr_x, *ptr_x);
printf("y: address=%p, content=%dn",&y, y);
printf("ptr_y: address=%p, content=%p, ==>value=%dn",
&ptr_y, ptr_y, *ptr_y);
*ptr_x = 5;
*ptr_y = 6;
printf("After value assignment:n");
printf("x: address=%p, content=%dn",&x, x);
printf("ptr_x: address=%p, content=%p, ==>value=%dn",
&ptr_x, ptr_x, *ptr_x);
printf("y: address=%p, content=%dn",&y, y);
printf("ptr_y: address=%p, content=%p, ==>value=%dn",
&ptr_y, ptr_y, *ptr_y);
*ptr_x *= *ptr_y;
printf("After calculation:n");
printf("x: address=%p, content=%dn",&x, x);
printf("ptr_x: address=%p, content=%p, ==>value=%dn",
&ptr_x, ptr_x, *ptr_x);
printf("y: address=%p, content=%dn",&y, y);
printf("ptr_y: address=%p, content=%p, ==>value=%dn",
&ptr_y, ptr_y, *ptr_y);
return 0;
}

The orignal code contained this fragment:

printf("After declaration:n");
printf("x: address=%p, content=%dn",&x, x);

printf("ptr_x: address=%p, content=%p, ==>value=%dn",

/*         note the print out for undefined content and value */

&ptr_x, ptr_x, *ptr_x);

printf("y: address=%p, content=%dn",&y, y);

printf("ptr_y: address=%p, content=%p, ==>value=%dn",

&ptr_y, ptr_y, *ptr_y);

/*         note the print out for undefined content and value */

Here are results of the first run:

After declaration:
x: address=0xbffffa54, content=134514272

ptr_x: address=0xbffffa4c, content=0xb7fabe80, ==>value=1252744

y: address=0xbffffa50, content=-1207958432

Segmentation fault

This is pretty much what I expected from trying to reference (print) a pointer pointing to an arbitrary location. I changed the value print to “undefined”.

The second run got this result:

After declaration:
x: address=0xbffffa54, content=134514240

ptr_x: address=0xbffffa4c, content=0xb7fabe80, ==>value=undefined

y: address=0xbffffa50, content=-1207958432

ptr_y: address=0xbffffa48, content=(nil), ==>value=undefined

After pointer assignment:

x: address=0xbffffa54, content=134514240

ptr_x: address=0xbffffa4c, content=0xbffffa54, ==>value=134514240

y: address=0xbffffa50, content=-1207958432

ptr_y: address=0xbffffa48, content=0xbffffa50, ==>value=-1207958432

After value assignment:

x: address=0xbffffa54, content=5

ptr_x: address=0xbffffa4c, content=0xbffffa54, ==>value=5

y: address=0xbffffa50, content=6

ptr_y: address=0xbffffa48, content=0xbffffa50, ==>value=6

After calculation:

x: address=0xbffffa54, content=30

ptr_x: address=0xbffffa4c, content=0xbffffa54, ==>value=30

y: address=0xbffffa50, content=6

ptr_y: address=0xbffffa48, content=0xbffffa50, ==>value=6

There are some who will say “Typical newbie error”. I submit that a lot of “oldbies” make the same mistake because they can. But I will reserve further judgement until I have finished Hours 12-16.

Arrays in C are easy to get wrong. They start at zero [0] and run for n elements. So when you define foo[n], a reference to foo[n] is just beyond the last one. The last one is at foo[n-1]. And normally there is no bounds checking. Bounds checking is available if the -fbounds-check(ing) option is used. This option is not working in the version of gcc that I have. A patch is available to add the function for some versions. You can find it as an extension to at gcc.gnu.org. The patch was developed by Herman ten Brugge whose home page is http://web.inter.nl.net/hcc/Haj.Ten.Brugge/ until the end of 2005. Sourceforge is the regular repository for the patch.

/* 12L01x.c: Initializing an array */
#include <stdio.h>
int main()
{

int    i;

int    list_int[10];

int    j;
printf("i: address=%p, content=%xn",&i, i);
printf("list_int[0 ]: address=%p, content=%dn",&list_int[0], list_int[0]);

printf("list_int[10]              content=%xn",list_int[11]);

printf("j: address=%p, content=%xn",&j, j);

for (i=0; i<10; i++) {

list_int[i] = i + 1;

printf("list_int[%d] is initialized to %dn",i,list_int[i]);

}

list_int[15] = 0xffff;

printf("i: address=%p, content=%xn",&i, i);

printf("list_int[0 ]: address=%p, content=%dn",&list_int[0], list_int[0]);

printf("list_int[15]              content=%xn",list_int[15]);

printf("j: address=%p, content=%xn",&j, j);

return 0;

}

I had to increase the subscript to 15 based on the addresses before I produced a storage overlay.

i: address=0xbffffa3c, content=b7fcee80
list_int[0 ]: address=0xbffffa00, content=-1073743308

list_int[10] content=80484cb

j: address=0xbffff9fc, content=bffffad4

list_int[0] is initialized to 1

.

.

.

list_int[9] is initialized to 10

i: address=0xbffffa3c, content=ffff <==== should be 0X000a

list_int[0 ]: address=0xbffffa00, content=1

list_int[15] content=ffff

j: address=0xbffff9fc, content=bffffad4

In listing 12.6, the initializer is shown as {1,2,3,4,5,10,20,30,40,50,100,200,300,400,500};.

12L06.c:6: warning: missing braces around initializer

The text explains that this is acceptable. It is only acceptable if the -Wnomissingbraces switch is active (default but not

-Wall

). A better construction is

{{1,2,3,4,5},{10,20,30,40,50},{100,200,300,400,500}};

as this insures that the structure of the 2-d array and the data match.

In listing 12.7 the initializer has a simlar warning.

I say treat yourself to tidy code. Use

-Wall

and correct the warnings. It will make your code more readable, easier to fix, and force you to think about what you are doing. It may save your job six months from now. It will certainly give you an opportunity to demonstrate that your company paid too much for whatever they got from those trainees used by the contracting firm: “With all this sloppy code, it is a wonder that it works at all”.

Truth first. A program which produces incorrect results is worse than no program.

Beauty second. A program which is clearly documented and clearly formatted will make it easier for you (and everyone else) to maintain and enhance in the future.

Efficiency third. While many strive to concoct the most efficient code from the get-go, if it does not produce correct results first, it is a waste of time. If it is producing correct results and has the clear comments and good structure, should the program prove inefficient, it makes it easier to improve.

While I am on the topic of options, the answer to the ULLONG and LLONG question above is the that the compiler options to change to the C99 standard is required. This can be accomplished by adding the -std=c99 or -std=gnu99 to the gcc command string.
Hour 13 Manipulating Strings
Strings and their null terminators are no problem. Copy and sizes are ok. gets is dangerous. If you are using

-Wall

, you will learn this when you get to the example. Google for gets function dangerous for some comments on gets.

This function can be used instead. You just need to add a second parameter to any program that would have used gets(buffer) so that now it will be getsafe(buffer, buffersize). The web site where I got this has a number of useful hints on good programming practices and I would encourage you to go there and review them as suggested above.

/

* getsafe.c function from jack klein */
/* http://home.att.net/~jackklein/ctips01.html#int_main */
#include <stdio.h>
#include <string.h>
char *getsafe(char *buffer, int count)
{
char *result = buffer, *np;
if ((buffer == NULL) || (count < 1))
result = NULL;
else if (count == 1)
*result = '�';
else if ((result = fgets(buffer, count, stdin)) != NULL)
if ( (np = strchr(buffer, 'n')) )
*np = '�';
return result;
}

Here is how it might be used:

/* 13L04safe.c: Using getsafe and puts */
/* this program does not use the unsafe gets function */
#include <stdio.h>
#include "getsafe.c"
int main()
{
char str[80];
int i, delt = 'a' - 'A';
printf("Enter a string less than 80 characters:n");
getsafe(str,sizeof(str));
i = 0;
while (str[i])
{
if ((str[i] >= 'a') && (str[i] <= 'z'))
str[i] -= delt; /* convert to ucase by subtracting diff a-A */
++i;
}
printf("The entered string is (in uppercase):n");
puts(str);
return 0;
}

Scanf is a real advance if you are used to accepting line input in assembler language. If you have used COBOL, BASIC, Visual Basic, or SAS it will seem fairly primative. scanf reads input in the requested form until it encounters a space, newline, tab, vertical tab, formfeed, or *mismatch-of-type*. When it encounters unexpected text, it quietly moves on to the next statement. Best to buff up your possible-input-error skills, anticipate, test, and request the input again if required. Zhang fails to discuss this aspect of scanf at all.

I still struggle with pointers. I found quiz 13 question 2 helpful in working though some of the confusion. It is not so much that I am confused. It is just that I that the word “dereference” is counter-intuitive to me. The other other problem is the declaration of the pointer *foo_ptr creates the POINTER. Subsequent references to *foo_ptr refer to the thing POINTED AT by *foo_ptr. foo_ptr is the pointer itself after the declaration.

Here is my response to quiz 13 question 2.

/* 13Q02.c: Quiz 13 question 2  */
#include <stdio.h>
int main()
{

char     *ptr_ch;

/* the location pointed to by *ptr_ch is a char i.e. numeric */
*ptr_ch = 's';
/* so this is correct.  The type of the location pointed to by  */

/* *ptr_ch is char (numeric) and so it the char literal 's'. */
ptr_ch = "A string";
/* the string is identified by the pointer to its first character */

/* so it is a pointer. ptr_ch is a pointer and can take the value */

/* of a pointer */
ptr_ch = 'x';
/* the pointer cannot take the value of a numeric (char) without a cast */
*ptr_ch = "This is quiz 2";
/* the numeric stored at the location pointed to by *ptr_ch cannot take */

/* the value of a pointer without a cast */

return 0;

}

Compiling yields:

13Q02.c: In function `main':
13Q02.c:18: warning: assignment makes pointer from integer without a cast

13Q02.c:21: warning: assignment makes integer from pointer without a cast

Note that these warning appear in the default settings as well as with

-Wall

. “But it’s just a warning” I here some programmer saying. Well if it was what you wanted, you should have explicitly cast it. Otherwise it is an error.

I apparently am not the only person who does not care for C as a language. You may wish to look at Dave Dyer’s page of (somewhat more than) Top 10 Ways to be Screwed by C. The scope example number 8 is similar to my debugging experience above.

http://www.andromeda.com/people/ddyer/topten.html

Hour 14 Understanding Scope and Storage Classes

Speaking of scope, nothing dramatic here. Pictures of a storage model would go a long way here.

The example 14L03 which is recycled and modified in exercise 14X04, might be more instructive if instead of add_two it was multiply_two returning the product of x and y. That way the number would vary based on the operands.

Also the opportunity to re-write a function, adding arguments. That will (presumably) be covered in the next hour but no forward pointer(strikeout)reference is provided.
Hour 15 Working With Functions
I am slowly coming to the conclusion that my original thought that I would never learn C is true:

1. I am too dumb.

2. I really understand C. I even understand pointers and their perverse arithmetic.

3. I hate C even more now.

4. This language was not named C because B was already used, but rather because those that kill the C program which has destroyed its brother will suffer a sevenfold vengence i.e. enough other processes will be damaged that a system restart (boot) will be required. Hence the C.

But I digress – Functions

This hour is actually pretty good. I have spent an enjoyable couple of hours looking at the header files for the time function and the . These actually come from different libraries although I am not sure I understand why. Clearly, they are part of separate packages since on my system comes from /usr/include/time.h and comes from /usr/lib/gcc. The answer is that one is part of the C library and the other is part of the gcc compiler.

You can find out about your system if you:

myhome:~/Projects/c$ gcc -Wall -E someprog.c | grep /

The grep / gets only the directory includes and omits the builtin includes.

I am using the gcc version 3.3.5-10.

Here is the result of doing a program with includes for stdio.h, stdarg.h, and time.hfrom my (upgraded and added to) Sarge Debian system. (Some repetitions are replaced by …)

# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4

# 1 "/usr/include/features.h" 1 3 4

# 308 "/usr/include/features.h" 3 4

# 1 "/usr/include/sys/cdefs.h" 1 3 4

# 309 "/usr/include/features.h" 2 3 4

# 331 "/usr/include/features.h" 3 4

# 1 "/usr/include/gnu/stubs.h" 1 3 4

# 332 "/usr/include/features.h" 2 3 4

# 29 "/usr/include/stdio.h" 2 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 213 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 3 4

# 35 "/usr/include/stdio.h" 2 3 4

# 1 "/usr/include/bits/types.h" 1 3 4

# 28 "/usr/include/bits/types.h" 3 4

# 1 "/usr/include/bits/wordsize.h" 1 3 4

# 29 "/usr/include/bits/types.h" 2 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 32 "/usr/include/bits/types.h" 2 3 4

# 129 "/usr/include/bits/types.h" 3 4

# 1 "/usr/include/bits/typesizes.h" 1 3 4

# 130 "/usr/include/bits/types.h" 2 3 4

# 37 "/usr/include/stdio.h" 2 3 4

# 62 "/usr/include/stdio.h" 3 4

# 72 "/usr/include/stdio.h" 3 4

# 1 "/usr/include/libio.h" 1 3 4

# 32 "/usr/include/libio.h" 3 4

# 1 "/usr/include/_G_config.h" 1 3 4

# 14 "/usr/include/_G_config.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 325 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 3 4

# 354 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 3 4

# 15 "/usr/include/_G_config.h" 2 3 4

# 24 "/usr/include/_G_config.h" 3 4

# 1 "/usr/include/wchar.h" 1 3 4

# 48 "/usr/include/wchar.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 49 "/usr/include/wchar.h" 2 3 4

# 1 "/usr/include/bits/wchar.h" 1 3 4

# 51 "/usr/include/wchar.h" 2 3 4

# 76 "/usr/include/wchar.h" 3 4

# 25 "/usr/include/_G_config.h" 2 3 4

# 44 "/usr/include/_G_config.h" 3 4

# 1 "/usr/include/gconv.h" 1 3 4

# 28 "/usr/include/gconv.h" 3 4

# 1 "/usr/include/wchar.h" 1 3 4

# 48 "/usr/include/wchar.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 49 "/usr/include/wchar.h" 2 3 4

# 29 "/usr/include/gconv.h" 2 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 32 "/usr/include/gconv.h" 2 3 4

# 45 "/usr/include/_G_config.h" 2 3 4

# 33 "/usr/include/libio.h" 2 3 4

# 53 "/usr/include/libio.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stdarg.h" 1 3 4

# 43 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stdarg.h" 3 4

# 54 "/usr/include/libio.h" 2 3 4

# 166 "/usr/include/libio.h" 3 4

# ...

# 73 "/usr/include/stdio.h" 2 3 4

# 86 "/usr/include/stdio.h" 3 4

# 138 "/usr/include/stdio.h" 3 4

# 1 "/usr/include/bits/stdio_lim.h" 1 3 4

# 139 "/usr/include/stdio.h" 2 3 4

# ...

# 718 "/usr/include/stdio.h" 3 4

# 1 "/usr/include/bits/sys_errlist.h" 1 3 4

# 27 "/usr/include/bits/sys_errlist.h" 3 4

# 748 "/usr/include/stdio.h" 2 3 4

# 767 "/usr/include/stdio.h" 3 4

# 807 "/usr/include/stdio.h" 3 4

# 834 "/usr/include/stdio.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stdarg.h" 1 3 4

# 105 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stdarg.h" 3 4

# 1 "/usr/include/time.h" 1 3 4

# 30 "/usr/include/time.h" 3 4

# 1 "/usr/lib/gcc-lib/i486-linux/3.3.5/include/stddef.h" 1 3 4

# 39 "/usr/include/time.h" 2 3 4

# 1 "/usr/include/bits/time.h" 1 3 4

# 40 "/usr/include/bits/time.h" 3 4

# 43 "/usr/include/time.h" 2 3 4

# ...

# 413 "/usr/include/time.h" 3 4

gcc version 3.4 introduced some incompatabilities in the libraries. Since this could affect the installation of packages down the road, the installation default is the older version. I see that I also have a gcc version 4.0. Maybe when I have more time, I will go back and explore the differences.

The advice about structured programming is very short.

An interesting question arose during exercise 15X04: Should a list 3 or more numbers have a comma after each of the numbers before the “and” or a comma after each of the numbers except the last and next to last? The answer to this changed some time after I was out of school. My wife thinks that it changed around 1985. Neither of us thinks it is a good idea. And my daughter, who was born in 1985 and learned it the new way prefers the old way as it seems clearer.
Hour 16 Applying Pointers
Applying pointers. This is pretty good practice for using pointers. The listing example 16L06 use a matrix (2d array) as an argument to a function. I did a little experiment because pointers are claimed to be faster. Here are the results:

Trial Array   Pointer Dummy
1     0.20927 0.19185 0.09980

2     0.20772 0.19076 0.10104

3     0.20900 0.20323 0.10000

4     0.20634 0.19153 0.10003

5     0.20314 0.19197 0.09989

6     0.20420 0.18953 0.10001

7     0.20123 0.19242 0.10077

8     0.20764 0.19212 0.10012

9     0.20274 0.19095 0.10794

10    0.20284 0.19033 0.10798

Sum   2.05412 1.92469 1.01759

Mean  0.20541 0.19247 0.10176

Less Dummy 1.03653 0.90710

Delta 0.12943

Cost (Improvement) 12.49% (14.27%)

StDev 0.00292 0.00389 0.00329
Trial  Array  Pointer Dummy
1       1.06    0.96    0.51
2       1.1     0.97    0.51
3       1.07    0.96    0.52
4       1.08    0.97    0.53
5       1.07    0.97    0.53
6       1.08    0.97    0.52
7       1.08    0.96    0.57
8       1.1     0.96    0.53
9       1.08    1.01    0.52
10      1.08    0.96
Sum     10.79194        9.70833 4.73260
Mean    1.07919 0.97083 0.52584
Less Dummy      6.05935 4.97573
Delta           1.08361
Cost (Improvement)      17.88%  (21.78%)
StDev   0.01229 0.01466 0.01798

The second set had 5 time the number of calls.

The data for each column indicates the time it took to perform the function based on the argument passed.

The column labeled Array is called with the array and Pointer is called with a pointer to the array. Pointer uses pointer arithemtic to calculate the values. Dummy is a dummy function which has a scalar and the diminsion arguments. It does the sum (+=) operation the same number of times, but does not do the subscript or pointer arithmetic. I think the values are supposed to be in seconds but I did not run a clock to check the timing.

The data used was a constant.

Test of 50×50 data

Each function tested 50000

The conclusion that I draw from this data is that the compiler generates better code for pointer arithmetic than the equivalent array. So it is faster to use pointers. But do compare these function routines:

int DataAdd1(int list[200][500], int max1, int max2)
{

int i, j;

int sum = 0;
for (i = 0; i<max1; i++)
for (j=0; j<max2; j++)

sum += list[i][j];

return sum;

}
int DataAdd2(int *list, int max1, int max2)
{

int i, j;

int sum = 0;
for (i = 0; i<max1; i++)
for (j=0; j<max2; j++)

sum += *(list + i*max2  + j);

return sum;

}

I find the first one clearer but maybe that is just me. I once did a project where the data was stored as (k by l by) m by n arrays in an array description that only supported single dimension arrays so the pointer calculation is not strange to me. The c language at least seems to accomodate the calculation through arrays that start at zero.

I also learned about putting timing points into the c code here:

http://www.ncsa.uiuc.edu/UserInfo/Resources/Hardware/IA32LinuxCluster/Doc/timing.html#gettimeofday

Error of the day:

myhome:~/Projects/c$ xit 16L07
16L07.c: In function `main':

16L07.c:18: error: called object is not a function

This was due to

StrPrint2(str(i));

where str is an array of char (i.e. a string) it should have read:

StrPrint2(str[i]);

But it is still apparent that either I do not really understand pointers or perhaps I do and just do not realize it. I am taking a side trip here:

http://pweb.netcom.com/~tjensen/ptr/pointers.htm

This is Ted Jensen’s “A Tutorial on Pointers and Arrays”.

Before I start, let me checkpoint. This is what I think that I know about pointers:

to declare a variable you name its type and name and optionally a value thus:

int some_int;
char some_char = 'A';

char some_string[] = "Initial string value";

to declare a pointer is the same except that the name is preceded by a “*”:

int *int_ptr;
char *char_ptr;
char *string_ptr;

to set the pointer you equate it to the location of the data, i.e. the pointer = &name:

int_ptr = &some_int;
char_ptr = &some_char;
string_ptr = &some_string;

But I also see this:

string_ptr = some_string;

Maybe the tutorial will tell me. This is my interpretation:

There is the introductory storage description, the rvalue which is the actual value of the variable and an lvalue which is the location of the data in memory. The compiler knows which it needs to use based on which side of the assignment statement or otherwise from context. If you go to a post office or mailbox storefront, the boxes are like the memory. The lvalue is the number on the box. The rvalue is the letter in the box. Each box can hold just one letter. If another letter is put in the box, the original content is lost.

When a new patron is assigned a PO box, he gets the contents of the box or a box with a letter which reads 0;

So a pointer is like the note the postmaster hands you that tells you which box number to use.

The compiler is like the postmaster. The postmaster has a list of all of the names of the box holders AND the numbers of the boxes. For the most part, the postmaster does not know what is in a box, he just looks at the NAME and puts it in the right numbered box. And actually, the notes (pointers) are stored in boxes just like any other data.

When a variable is declared it gets a zero value (or leftover value or memory on my system).

When a pointer is declared, it gets a NULL value where NULL is a macro which is guaranteed to be an invalid address.

So we get to this example:

/* Program 1.1 from PTRTUT10.TXT 6/10/97 */
#include <stdio.h>

int j, k;

int *ptr;

int main(void)

{

j = 1;

k = 2;

ptr = &k;

printf("n");

printf("j has the value %d and is stored at %pn", j, (void *)&j);

printf("k has the value %d and is stored at %pn", k, (void *)&k);

printf("ptr has the value %p and is stored at %pn", ptr, (void *)&ptr);

printf("The value of the integer pointed to by ptr is %dn", *ptr);

return 0;

}

This

(void *)

cast thing is new. Good news is that the programs in TYCI24H do not have it but work anyway with no warning even with

-Wall

. The bad news is that we will have to wait until later in the tutorial to find out why it is required. Patience.

So on th part 2 of the tutorial. Pointers need “type” so that the compiler knows how many bytes (1 for char, 2 for short integers, 4 for long integers, whatever).

Here is the crux:

“In C, the standard states that wherever we might use &var_name[0] we can replace that with var_name, thus in our code where we wrote:

ptr = &my_array[0];

we can write:

ptr = my_array;

to achieve the same result. ”

DA-A-H-H! the light goes on.

So in C,

ptr = my_array;
ptr = &my_array;

ptr = &my_array[0]

All result in ptr having the same value and type.

Ted goes on to explain the necessity (and correctness) of (void *) to get the pointers addresses to be type void (untyped) so their lengths and increments are not dependent on the type of the locations they point at. This allows pointers to different types of data to be compared.

But I can now proceed understanding that consistancy is not strictly required.

The examples and exercises in TYCI24H need to be done and played with for for complete understanding. Alone, you may not achieve enlightenment.
Hour 17 Allocating Memory
STOP! Put down your pencil. Fold your hands and place them on your desk. Do you really understand pointers? We are about to take up hour 17 Allocating Memory with

malloc(), calloc(), realloc(),

and

free()

. The first three return a pointer. The third and last need one to proceed. If you really understand pointers, you can go ahead. If not, go back and review your earlier work with pointers. When you are done, return here. Ready? Begin.

This hour is pretty clear. I thought example 4 (listing 17L04.c) could provide more insight into just what the realloc() does. Make these changes:

myhome:~/Projects/c$ diff 17L04.c 17L04a.c
11,12c11,12

<                                       "There's music in the gushing of a rill;",

<                                       "There's music in all things if men had ears;",

---

>                                       "There's music in the sighing of a reed;nThere's music in the gushing of a rill;",

>                                       "here's music in the sighing of a reed;nThere's music inthe gushing of a rill;nThere's music in all things if men had ears;",

15a16

>       int      *pint;

32a34,40

>               if ((pint=malloc(sizeof(int))) == NULL)

>                       {

>                               printf("malloc(int) failedn");

>                               termination = 4;

>                       }

>               else

>               printf("malloc(int) returned %pn",pint);

44a53

>                               printf("found: %sn",ptr);

46c55

<                               printf("%sn", ptr);

---

>                               printf("moved: %sn", ptr);

47a57

> /*            free(pint); */

</code>

The line numbers may not be the same as yours. I prefer to have the curly-brace stand along. The idea here is to make the strings longer each time, malloc space for an integer as a stopper. The pint=malloc() code goes just after the left curly-brace of the for-loop.

Run this program with the free commented. Note that the pint pointer gets lost and the memory cannot be freed. This is a classic memory leak. Remove the comments around the free and run it. Notice the difference in the storage allocated. And look at the “found” data and how it changes.

Zhang points out that a realloc(ptr,0); is the equivalent of a free but does not explicitly point out that it frees the old space if the space to be reallocated will not fit in the previously allocated space. He also does not point out that the content of the reallocated memory will be undefined if it moves.

Given the prevalence of leaking memory, I probably would have spent more ink on it in a text book. On the other hand, Zhang is scrupulous about checking the return from -alloc functions. Start those good habits early.

While I was doing this, I tried this

/* 17Lbig.c: Allocate lots of memory */
#include <stdio.h>
#include <stdlib.h>
/* main() function */
int main()
{
char *ptr;
int     count;
int  termination = 1;
count = 2;
do
{
ptr = malloc(count);
if (ptr==NULL)
{
printf("malloc() failed; count = %d.n",count);
if (count == 2)
termination = 1;
else
count /= 2;
}
else
{
printf("malloc(%d) returned %pn",count,ptr);
count *= 2;
free (ptr);
}
} while(count > 1);
return termination;
}

It was interesting to see what it did but it did not terminate as I expected. But I do not know enough to say why at this point.

The exercises were good exercises but somewhat mundane.

Onward!
Hour 18 – Using special types and functions
enum – other sources note that enum has similarities to #define but has scope like other variables. The examples do not highlight the sequential assignement of values as well as this example from:

http://www.phim.unibe.ch/comp_doc/c_manual/C/EXAMPLES/enum1.c

/****************************************************************************
*

*  Enumeration example. Program will return the month in a year.

*  I.E. It returns 9 for September.

*

****************************************************************************/
main()
{

/*

* Define a list of aliases

*/

enum months {Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec};

/*     A      A

|      |

|      |

|      -------  list of aliases.

--------------  Enumeration tag.    */
enum months month;        /* define 'month' variable of type 'months' */
printf("%dn", month=Sep);    /* Assign integer value via an alias
* This will return a 9                */

}

A regular sequential list needs to start at 1 instead of 0. Only the first value has to b specified. There is an example but it is not as useful or memorable.

typedef – the example here is comprehensive and have some useful examples: STRING defines a character array pointer.

I invite you to try some modifications. Change the int in the INTEGER line to short and long and char. Change the ITEM_NUM constant to 2 or 4 and check the results. Add +1 to the ‘a’ in the DELT definition. What happens to your upper case letters?

Recursive functions – includes the standard warning about efficiency.

Command line args – Good demo. Just for fun try this set of args:

myhome:~/Projects/c$ 18L05 these are some additional args
The value received by argc is 6.
There are 6 command-line arguments passed to main().
The first command-line argument is: 18L05
The rest of the command-line arguments are:
these
are
some
additional
args
myhome:~/Projects/c$ 18L05 (these are some additional args)
bash: syntax error near unexpected token `these'
myhome:~/Projects/c$ 18L05 "these are some additional args"
The value received by argc is 2.
There are 2 command-line arguments passed to main().
The first command-line argument is: 18L05
The rest of the command-line arguments are:
these are some additional args
myhome:~/Projects/c$

Do you get the same results?
Hour 19 – Understanding Structures
On the home stretch. This is the last 6 hours.

These program examples almost seem like useful programs. I am using the scansafe routine so I have an interesting wrinkle. When a text string such as employee name is entered that exceeds the length of the buffer the excess characters seem to satisfy (sort of) the subsequent scanf’s for numeric information. Since the information is not numeric, the values requested are not changed. This can be used to test for invalid or missing input.

Lesson example 4 is based on example 3 and illustrates my feelings about excessive use of pointers:

/* function definition from 19L04.c */

style=”font-family: Courier New,Courier,monospace;”>

void DataReceive(SC *ptr_s)
{
printf("The type of CPU inside your computer?n");
getsafe((*ptr_s).cpu_type,sizeof((*ptr_s).cpu_type));
printf("The speed(Mhz) of the CPU?n");
scanf("%d",&(*ptr_s).cpu_speed);
printf("The year the computer was made?");
scanf("%d",&(*ptr_s).year);
printf("How much you paid for the computer?n");
scanf("%f",&(*ptr_s).cost);
}

as compared to :

//* function definition from 19L03.c  */
SC DataReceive(SC s)
{
printf("The type of CPU inside your computer?n");
getsafe(s.cpu_type,sizeof(s.cpu_type));
printf("The speed(Mhz) of the CPU?n");
scanf("%d",&s.cpu_speed);
printf("The year the computer was made?");
scanf("%d",&s.year);
printf("How much you paid for the computer?n");
scanf("%f",&s.cost);
return s;
}

Here is the diff between the 2 programs:

myhome:~/Projects/c$ diff 19L03.c 19L04.c
1c1

< /* 19L03.c: Passing a structure to a function */

---

> /* 19L04.c: Pointing to a structure */

16c16

< SC DataReceive(SC s);

---

> void DataReceive(SC *ptr_s);

21,22c21,22

<

<       model = DataReceive(model);

---

>

>       DataReceive(&model);

27a28

>

31c32

< SC DataReceive(SC s)

---

> void DataReceive(SC *ptr_s)

34c35

<       getsafe(s.cpu_type,sizeof(s.cpu_type));

---

>       getsafe((*ptr_s).cpu_type,sizeof((*ptr_s).cpu_type));

36c37

<       scanf("%d",&s.cpu_speed);

---

>       scanf("%d",&(*ptr_s).cpu_speed);

38c39

<       scanf("%d",&s.year);

---

>       scanf("%d",&(*ptr_s).year);

40,41c41,42

<       scanf("%f",&s.cost);

*                  --

*                  This isn't pretty.

<       return s;

---

>       scanf("%f",&(*ptr_s).cost);

*                  ---------

*                  But this is just plain ugly. And error prone.

>

* Oh and did I mention that there is no need to report success?

Modifying the data in place with a pointer is more effecient. But it certainly is a triumph of efficiency over Truth and Beauty. If the structure is a couple of 100K bytes, yeah you do not want to be copying it excessively. But for normal size stuff, my vote is to avoid pointers where possible.

Listing 19.6 19L06.c

Another pet peeve of mine is using numeric items (integer, float, packed number) for ID numbers. There is absolutely no reason that an ID has to be numeric and even if it starts out that way, if you never use it for arithmetic, define the field as ALPHANUMERIC (character[]) and use edits to keep it pure. That way when you grow, you can make the numeric ID Alpha numeric without ANY file changes and only change the edit routines where the data enters the system.

Need to note that for a struct, the “type” is the struct keyword plus the tag. If this is used as an argument, both words must be present. This is why the typedef was introduced here but it really did not explain why.

It is also not real clear from the text how the . (dot) operator and the -> (points to) operator are related. Once you realize that the dot is the direct pointer to the member of the structure and the points to is an indirect reference off the pointer it all becomes clearer. As an additional exercise I rewrote 19X01 with pointers. Here is the diff:

< /* 19X01.c: Display structure contents */
---

> /* 19X01p.c: Display structure contents with pointers */

17c17

< int InfoDisplay(struct automobile zcar);

---

> int InfoDisplay(struct automobile *pcar);

23c23

<       rc = InfoDisplay(sedan);

---

>       rc = InfoDisplay(&sedan);

28c28

< int InfoDisplay(struct automobile zcar)

---

> int InfoDisplay(struct automobile *pcar)

30,33c30,33

<       printf("Name:   %sn",zcar.model);

<       printf("Year:   %dn",zcar.year);

<       printf("HP:     %dn",zcar.engine_power);

<       printf("Weight:%6.2fn",zcar.weight);

---

>       printf("Name:   %sn",pcar->model);

>       printf("Year:   %dn",pcar->year);

>       printf("HP:     %dn",pcar->engine_power);

>       printf("Weight:%6.2fn",pcar->weight);

The first difference is obviously in the title and comment.

The next difference is the prototype function declaration. The zcar reference is a direct reference to the variable sedan (zee car). The pcar is a pointer that points to the the sedan.

The next difference is the actual call. In the first case, I use the name of the variable directly. In the second case, I use the address-of operator to make the reference into a pointer.

This is scary. This pointer business almost make sense.

Exercise 19X02 teachs a good lesson about scope. I had to move the intial structure definition so that it was available to both main() and InfoDisplay().

Exercise 19X03: “Change 19L04 from using the . (dot) notation to the -> (arrow) notation” epitomizes why some authorities consider C a Black Art. It certainly explains why Perl and M4 exist.

< /* 19L04.c: Pointing to a structure */
---

> /* 19X03.c: Pointing to a structure with the arrow operator */

20a21,22

>       SC *ptr;

>       ptr = &model;

22c24

<       DataReceive(&model);

---

>       DataReceive(ptr);

24,27c26,29

<       printf("Year:           %dn",model.year);

<       printf("Cost:           $%6.2fn",model.cost);

<       printf("CPU Type:       %sn",model.cpu_type);

<       printf("CPU Speed:      %d MHzn",model.cpu_speed);

---

>       printf("Year:           %dn",ptr->year);

>       printf("Cost:           $%6.2fn",ptr->cost);

>       printf("CPU Type:       %sn",ptr->cpu_type);

>       printf("CPU Speed:      %d MHzn",ptr->cpu_speed);

35c37

<       getsafe((*ptr_s).cpu_type,sizeof((*ptr_s).cpu_type));

---

>       getsafe(ptr_s->cpu_type,sizeof(ptr_s->cpu_type));

37c39

<       scanf("%d",&(*ptr_s).cpu_speed);

---

>       scanf("%d",&(ptr_s->cpu_speed));

39c41

<       scanf("%d",&(*ptr_s).year);

---

>       scanf("%d",&(ptr_s->year));

41c43

<       scanf("%f",&(*ptr_s).cost);

---

>       scanf("%f",&(ptr_s->cost));

Did I mention that there is no end of compile time messages you can get if you leave off a semi-colon?

After exercise 4, I will probably never knowingly trust a program known to be written in C.
Hour 20 – Unions
Starting right off, the first example does not blaze any new territory. In its effect, it varies not a whit from previous examples which store data. To discover how it overlays I made a few modifications:

< /* 20L01.c: Referencing a uniion */
---

> /* 20L01a.c: Referencing a uniion and showing */

11a12

>                       int i;

15a17,21

>       /* print dish.name before */

>       printf("->%pn  %pn",&dish.name[0], &dish.price);

>       for (i=0; dish.name[i]; i++)

>               printf("%p->%x %cn",&dish.name[i],dish.name[i],dish.name[i]);

>       printf("n");

17a24,25

>       printf("Dish Price: %5.2fn",dish.price);

>       /* print dish.name after */

18a27,31

>       printf("Dish Name: %sn",dish.name);

>       printf("->%pn  %pn",&dish.name[0], &dish.price);

>       for (i=0; dish.name[i]; i++)

>               printf("%p->%x %cn",&dish.name[i],dish.name[i],dish.name[i]);

>       printf("n");

myhome:~/Projects/c$ 20L01a

The content assigned to the union separately:

Dish Name: Sweet and Sour Chicken

->0xbffffa30

  0xbffffa30

0xbffffa30->53 S

0xbffffa31->77 w

0xbffffa32->65 e

0xbffffa33->65 e

0xbffffa34->74 t

0xbffffa35->20

0xbffffa36->61 a

0xbffffa37->6e n

0xbffffa38->64 d

0xbffffa39->20

... simlar lines omitted

0xbffffa45->6e n

Dish Price:  9.95

Dish Name: fffffæ#@d Sour Chicken

->0xbffffa30

  0xbffffa30

0xbffffa30->66 f

0xbffffa31->66 f

0xbffffa32->66 f

0xbffffa33->66 f

0xbffffa34->66 f

0xbffffa35->ffffffe6 æ

0xbffffa36->23 #

0xbffffa37->40 @

0xbffffa38->64 d

0xbffffa39->20

... simlar lines omitted

0xbffffa45->6e n

Note: it only took me 15 minutes to remember that char is signed and the extra bytes were coming from negative chars!

And only when I formated the hex print this way. As a string, it blends right in

Duh.

Perhaps I should have been more patient. Listing 20L02.c shows storage being overlayed. Adding a couple of lines of print to show how good it used to be drives the point of shared memory being overlayed home.

myhome:~/Projects/c$ diff 20L02.c 20L02a.c
1c1

< /* 20L02.c: Memory Sharing in unions */

---

> /* 20L02a.c: Memory Sharing in unions clarified */

13a14

> printf("Start Year:   %dn",info.start_year);

15a17

> printf("Dpt. Code:    %dn",info.dpt_code);

Listing 3 and 4 cover measuring the size of a union or struct.

Listing 5 is a long program but it begins to look like a program which does useful work. If the printf/gets statements were dialog boxes, this could be a genuine application.

Repeat 5 for 6 as bit fields. I do not encourage people to do this. It is important to know how to map bits and or, and and xor them as appropriate. But for business applications, my recommendations is to use meaninful character flags. That way when you a looking at a record or a dump of a row, the meanings are immediately obvious and aid with debugging.

myhome:~/Projects/c$ diff 20X03.c 20X04.c
8c8

<       char    usa;

---

>       char    usa:1;

39c39

<                       ptr->usa = 'Y';

---

>                       ptr->usa = 1;

45c45

<                       ptr->usa = 'N';

---

>                       ptr->usa = 0;

55c55

<       if (ptr->usa == 'Y')

---

>       if (ptr->usa)

Here are the differences between the programs 20X03.c and 20X04.c. The first uses a character flag for the value, the 04 version uses a bit flag. Remember bits are on or off, yes or no. A character may be yes or no, but if necessary, maybe, possibly, and almost-certain can be added to the possible values with little effort in re-coding as real-life changes the original requirements.
Hour 20 – Reading and Writing with Files
For listing 21L01.c I am not exactly sure where the “haiku.txt” file was supposed to come from. I created it with:

touch haiku.txt

after the first run indicated that it was not there. Then I used the text editor to fill it in from the output listing of 21L02.c so it would be there for the program (and non-empty).

Listing 21L02.c and 21L03.c are instructive. It will be especally instructive if you diff them and see the the few thing that need to be changed between getc/putc and fgets/fputs. Especially noteworthy is that when reading a stream a character at a time, you can watch for the EOF value. When you read a line at a time, expect a NULL pointer.

This hour contains a box which notes how dangerous the gets() for input from the keyboard can be as I noted in hour 13 above. Also provided is an example of how to use fgets from stdin with the appropriate checks.

Now here is the question of the hour. Why in listing 21.4 is MAX_LEN set equal to 80? Maybe because the FORTRAN example that this program was derived from used that length. Or maybe because of 80 column punch cards. Or maybe just a coincidence. It probably is not related to the number of character column positions on the screen of a VT-100 terminal (This is so because of punch cards).

Exercise 21X04 is interesting although Zhangs potential solution just writes one block for the whole string. A more interesting solution writes a number of blocks.

You might use a block write routine similar to this:

void BlockWrite(char *fin, FILE *fout)
{

int num = 0;
char buff[MAX_LEN+1];
int counter = 0;

while ((fin[counter]))
{
buff[num] = fin[counter];
num += 1;
if (num >= MAX_LEN)
{
fwrite(buff, sizeof(char), num, fout);
buff[num] = '�';
printf("[%s]n",buff);
num = 0;
counter += 1;
}
else
counter += 1;
}
/* write last partial buffer if any */
if (num>0)
{
fwrite(buff, sizeof(char), num, fout);
buff[num] = '�';
printf("<%s>n",buff);
}
printf("CharReadWrite %d charactersn",counter);
}

I see I could have rolled that counter increment out of the if statement. But I did not want to spend any more time on this one.
Hour 22 – Using Special File Functions
I had only used scanf from the terminal. Using in against data that you cannot readily see and may be sketchy because you created it with a program of as yet untested capabilities is a challenge. The SAS system has some input oddities but they are apple-pie-and-motherhood compared to scanf. What bit me was a space. Space is a terminator for scanf even if you are reading a string. In modifying a program for one of the exercises I left the / from a comment. This resulted in a half-a-dozen messages. Removing the / let the program compile without any messages. 22X04.c:28: error: parse error before ‘/’ token

22X04.c:36: error: parse error before string constant
22X04.c:36: warning: type defaults to `int' in declaration of `printf'

22X04.c:36: warning: conflicting types for built-in function `printf'

22X04.c:36: warning: data definition has no type or storage class

22X04.c:40: error: parse error before string constant

22X04.c:40: warning: type defaults to `int’ in declaration of `printf’
Hour 23 – The C Preprocessor
Some interesting variations on listing 23L02.c can be had using the #undef directive Add these after the other #define s

> #undef UPPER_CASE
> #define LOWER_CASE 0

More info can be found here:

http://pbpl.physics.ucla.edu/Computing/C_Tutorial/Section_7_(Preprocessor)/

Hour 24 – Where Do We Go From Here?
This last hour is an excellent wrap-up. A more complex program with a data header, function module, and main driver is created. This program creates a simple linked list file in storage.

This requires a modification to the compile command as now there are 2 C modules to compile.

g

cc -Wall -o 24L03 -lm 24L03.c 24L01.c

The header file 24L02.h will be included because it is in the local directory and is specified by

#include "name"

I would have provided 2 exercises in this chapter to ensure complete understanding.

1. Update the source files in 24L01.c 24L02.h, and 24L03.c to have a back pointer as well as a forward pointer. Make sure that the the back pointers is correctly updated for create and delete functions.

2. Add functions to the driver and function module which will write the created file out to disk and read it back in recreating the pointers correctly.

Begin by updating the header file. In general, it is a good idea to deal with the data first, then the procedures.

The change to add a back pointer is relatively minor: just add a pointer for a struct of lnk_list_struct. This will allocate storage for an additional pointer. And when this pointer is incremented, it will increase by the length of a lnk_list_struct.

myhome:~/Projects/c$ diff 24L02.h 24L02a.h
14a15

>       struct lnk_list_struct *back_ptr;

Since we changed the name of this file when we saved it, the driver needs to be updated to include the correct header. This is the only change to the driver! Ain’t modular programming great!

The function file needs a few more changes:

I also had some problems getting the pointers right so here is the diff between the original and the version with back pointers.
,4c3,4

< #include "24L02.h"
<

---

> #include "24L02a.h"

> #define dbug 0

5a6

> static NODE *tail_ptr = NULL;

36a38

>       ptr->back_ptr = NULL; /* set the back pointer to NULL for now */

65a68,81

>       if (tail_ptr == NULL)

>       {

>               tail_ptr = new_ptr;

>               new_ptr->back_ptr = NULL;

>       }

>       else

>       {

>               /* find the last name in the list */

>               new_ptr->back_ptr = tail_ptr;

>               tail_ptr = new_ptr;

>

>

>       }

>

84c100

<               printf("enter the stuent ID: ");

---

>               printf("enter the student ID: ");

91a108,111

>                       if (ptr_saved == NULL)

>                               tail_ptr = head_ptr;

>                       else

>                               ptr_saved->back_ptr = NULL;

108a129,132

>                                       if (ptr_saved == NULL)

>                                               tail_ptr = ptr;

>                                       else

>                                               ptr_saved->back_ptr = ptr;

134a159,161

> #ifdef dbug

>               printf("Head %p tail %pn",head_ptr, tail_ptr);

> #endif

141a169,172

> #ifdef dbug

>               printf("this %p next %p back %pn",

>                       ptr, ptr->next_ptr, ptr->back_ptr);

> #endif

146a178,181

> #ifdef dbug

>               printf("this %p next %p back %pn",

>                       ptr, ptr->next_ptr, ptr->back_ptr);

> #endif

151c186,187

<  */

---

>  ** free all remaining nodes when requested

>  **/

For extra credit, you can make the changes necessary to print the Next and Previous record.

I would also probably add checks for duplicate or missing data.

Remove the define for DBUG to neaten up the print out.

Step 2.

These are the changes that I made in order to save and load a file
c1

< /* 24L01.c: A module file */
---

> /* 24L01b.c: Routines */

3c3

< #include "24L02a.h"

---

> #include "24L02b.h"

6a7

> enum {SUCCESS, FAIL};

22a24,29

>               case 'l':

>                       list_node_load();

>                       break;

>               case 's':

>                       list_node_save();

>                       break;

39a47,49

> #ifdef dbug

>    printf("NODE created at %p ",ptr);

> #endif

199a210,304

>  ** list_node_save()

>  **/

> void list_node_load()

> {

>       NODE *ptr, *new_ptr;

>       FILE *fileptr;

>       int     reval;  /* supplied but not returned */

>       char filename[] = "listnode.txt";

>       /* dialog to name file would go here */

>       if ((fileptr = fopen(filename,"r")) == NULL)

>               {

>                       printf("Cannot read %s",filename);

>                       reval = FAIL;

>               }

>       else

>               {

>                       if (head_ptr != NULL)

>                               {

>                                       list_node_free();

>                                       head_ptr = NULL;

>                                       tail_ptr = NULL;

>                               }

>                       printf("Reading %s ",filename);

>                       for (new_ptr = list_node_create();

>                                 ((new_ptr != NULL) &&

>                                        (fscanf(fileptr,"%s %ld",

>                                                       new_ptr->name, &(new_ptr->id)

>                                                         ) != EOF));

>                                 new_ptr=list_node_create())

>                               {

>                                       printf("nnew_ptr %p ",new_ptr);

>                                       if (head_ptr == NULL)

>                                       {

>                                               head_ptr = new_ptr;

>                                               tail_ptr = new_ptr;

>                                               new_ptr->next_ptr = NULL;

>                                               new_ptr->back_ptr = NULL;

>                                       }

>                                       else

>                                       {

>                                               /* find the last name in the list */

>                                               for (ptr=head_ptr;

>                                                         ptr->next_ptr != NULL;

>                                                         ptr=ptr->next_ptr)

>                                                       ; /* doing nothing here */

>                                               /* link to the last node */

>                                               ptr->next_ptr = new_ptr;

>                                               new_ptr->back_ptr = tail_ptr;

>                                               tail_ptr = new_ptr;

>

>                                       }

> #ifdef dbug

>                                       printf("%s:%ldn",

>                                               new_ptr->name,

>                                               new_ptr->id);

>                                       printf("this %p next %p back %pn",

>                                                       new_ptr, new_ptr->next_ptr, new_ptr->back_ptr);

> #endif

>                               }

>                       free(new_ptr);

>                       fclose(fileptr);

>                       printf(" completedn");

>               }

>       return;

> }

> /**

>  ** list_node_save()

>  **/

> void list_node_save()

> {

>       NODE *ptr;

>       FILE *fileptr;

>       int     reval;  /* supplied but not returned */

>       char filename[] = "listnode.txt";

>       /* dialog to name file would go here */

>       if ((fileptr = fopen(filename,"w")) == NULL)

>               {

>                       printf("Cannot open %s",filename);

>                       reval = FAIL;

>               }

>       else

>               {

>                       printf("Saving to %s ",filename);

>                       for (ptr = head_ptr;

>                                       ptr != NULL;

>                                       ptr=ptr->next_ptr)

>                               {

>                                       fprintf(fileptr,"%s %ldn",ptr->name, ptr->id);

>                               }

>                       fclose(fileptr);

>                       printf(" completedn");

>               }

>       return;

> }

> /**

myhome:~/Projects/c$ diff 24L02a.h 24L02b.h

24a25,26

> void list_node_load(void);

> void list_node_save(void);

myhome:~/Projects/c$ diff 24L03a.c 24L03b.c

2c2

< #include "24L02a.h"

---

> #include "24L02b.h"

8a9

>       printf("       l  to load,      s  to saven");

After spending a lot of time trying to get the load loop right, I realized that the tail_ptr could replace the find-it loop. This could make a significant difference in adding to a long list.

1c1
< /* 24L01b.c: Routines */

---

> /* 24L01c.c: Routines */

4c4

< #define dbug 0

---

>

67,79d66

<       }

<       else

<       {

<               /* find the last name in the list */

<               for (ptr=head_ptr;

<                         ptr->next_ptr != NULL;

<                         ptr=ptr->next_ptr)

<                       ; /* doing nothing here */

<               /* link to the last node */

<               ptr->next_ptr = new_ptr;

<       }

<       if (tail_ptr == NULL)

<       {

80a68

>               new_ptr->next_ptr = NULL;

86c74,76

<               new_ptr->back_ptr = tail_ptr;

---

>               ptr = tail_ptr;

>               /* link to the last node */

>               ptr->next_ptr = new_ptr;

88,89c78,79

<

<

---

>               new_ptr->back_ptr = ptr;

>               new_ptr->next_ptr = NULL;

251,254c241

<                                               for (ptr=head_ptr;

<                                                         ptr->next_ptr != NULL;

<                                                         ptr=ptr->next_ptr)

<                                                       ; /* doing nothing here */

---

>                                               ptr = tail_ptr;

259d245

<

Zhang does a review after the programming example. The review is a good thing to do. It also acts as a reference summary to jog your memory if you get forgetful about a function you have not used for a while.

Zhang casually mentions debugging as part of his review. The IDE that he is using in Windows probably had some debugging (step, display value, etc) support.

I have used printf’s to display critical values at various points and I put them in so that the could be turned off easily. Taking them out all together is still laborious and could result in program changes which break your program. Watch especially adding statements adjacent to if’s, do’s and for’s that do not use { … } braces.

GNU/Linux has a debugger known as

gdb

. I will write about debugging separately.

A topic that Zhang neglects altogether is

make

. The

make

utility to me is inseparable from Unix. If you are creating programs for Unix or Linux environment, you need to at least have a passing familiarity with make. Knowing

automake

and

autoconfig

will help you as developer and as a system administrator.

If you have questions about anything here, you can e-mail me at cbc0777 on earthlink dot net.