Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language courses for corporations. He is the author of C Language for Programmers and All On C, and is a member on the ANSI C committee. He also does custom C programming for communications, graphics, and image databases. His address is 4201 University Dr., Suite 102; Durham; NC 27707.
You may fax questions for Ken to (919) 493-4390. When you hear the answering message, press the * button on your telephone. Ken also receives email at kpugh@dukeac.ac.duke.edu (Internet) or dukeac!kpugh (UUCP).
Q
I am a brand-new subscriber, and also almost a beginner in the "C" language. I have wrestled with it off and on since the CP/M days ("BDS C") and now with Borland's Turbo C (Versions 1 and 2), and have had only moderate success in getting it to run for my purposes.
My "game" is number-crunching, for engineering and scientific applications. I have Turbo Pascal (now using Version 5.5) working very well; I would like to get "C" under reasonable control too!
I have about 15 books on "C" and not one has the answer to my rather simple problem. Briefly, I want to achieve in "C" what I can in Pascal as in the following statements;
x := 5; ( "x" was declared as integer) y := 6; ( "y" was declared as integer) z := x + y; ( "z" was declared as integer) WRITELN(LST,'The answer is ',z:10); ( The value of "z" is printed on the line printer) This is what I need.One of your knowledgeable staff can undoubtedly set me straight on how to do that in Turbo C Version 2.0.My machine is a KAYPRO PC with two 360k floppy drives, a 20 MB Seagate built-in hard disk, running at 4 or 8 MHz. My printer is the old Epson FX80 which is still running like new. I use either MS/DOS 3.2 or PC/DOS 3.2 (I have both currently running the latter).
Can you give me an example in "C" of how to do the above simple operation? It will be much appreciated.
Bill Whitcraft
Natick, MAA
You just need a 16th book C Language for Programmers by yours truly, available in bookstores or from QED Information Sciences, P.O. Box 181, Wellesley, MA 02181. This book was written specifically to help with the transition from Pascal, FORTRAN, Basic, Cobol, and PL/I to C. It was based on the K&R standard, but has been revised to incorporate the ANSI standard. Your answer is on page 107 and is included here.
To output to any device with C, you open the appropriate file and use the appropriate file output function. The name of the printer file varies from system to system, but the standard name under MS/DOS is LST. The code in Listing 1 accomplishes the same end as your Pascal version.
The fopen() function returns a pointer to a data type named FILE. It is described in <stdio.h>. The fprintf function works like the printf() function, except that its output goes to the device associated with the pointer that is the first argument. The fclose() function closes the device. If you forget to call fclose(), the device will be closed automatically if your program exits normally.
The %10d is a format specifier that states that you are passing fprintf() an integer and you want it printed out as a decimal number, 10 characters long. By default, the number is printed right justified, with spaces for padding. For a left-justified result you would use %-10d.
I've found in my classes that Pascal programmers tend to make a common mistake with the formatted print functions (printf() and fprintf()). They use the Pascal WRITE syntax, which intermixes the strings to be printed out with the data items. From the example in the book, you might write in Pascal:
WRITELN('I=', I:3, 'Str=' STR:7, 'F= ', F:5:1, 'C=', C:1);The compiler knows the data types of each variable and does the appropriate conversion.In C, this would be written as:
printf("I=%3d Str %7s F=%5.1f C=%c\n", i, str, f, c);where i is an integer, str is a string, f is a float and c is a char.Q
I am in the process of reading your article in the February, 1990 issues.
I find most "explanations" of C confusing and incomplete. I am thinking that a number of readers might also be confused.
(So you don't think I'm totally green I have been programming, engineering, and teaching others to do same since the days of the Intel 4004.)
Upon some reflection the part that is the most confusing is what the compiler is trying to do. (to help???!!!)
Of course many authors either don't know what the compiler is trying to do or assume it doesn't matter. Since I know that's not the case with you I thought I would share some of my confusions with you.
What would be of help to me is explanations which analyze the assumptions made by the compiler at each step of the way and how the source code modifies them.
I have taken the liberty of marking up some pages from your recent article in hopes that you might tell me where I am wrong, point me to lucid descriptions which might help, help other confused readers, or maybe all of the above.
Thanks in advance for your help.
John M. Harrison
Concord, NHA
In answering questions, I tread a fine line between too much detail and not enough. Too much and it may be boring or Robert will complain that there isn't room. Too little and I might as well not answer the question. One way I can tell which way I'm leaning is from your feedback. If others have a question or comment on my answers, please do not hesitate to contact me.
I will give more explanation on my answer and reply to your comments on my listing. The question revolved around how to print out the values in a structure without having to code each field separately.
Let's review the problem first. The user had an array of pointers to structures that looked like:
struct { char firstname[MAX_FIRSTNAME+1]; char lastname[MAX_LASTNAME+]; char homephone[MAX_HOMEPHONE+1]; char workphone[MAX_WORKPHONE+1]; char areacode[MAX_AREACODE+1]; char street [MAX_STREET+1]; char city[MAX_CITY+1]; char state[MAX_STATE+1]; char comments[MAX_COMMENTS+1]; } *record[MAX_RECORDS];He wanted to print each element of the array in a for loop, such as follows, but was unsure of what to use, as shown here:
show_record () { int i: for(i = 0; i < NUM_OF_FIELDS; i++) { printf("%s\n",record[current_record]-> ??? ); } }Next let me review some of may coding conventions. I always use a #define in lieu of numeric constants. The simple substitution it allows makes for a more readable and maintainable program. I also try to include a comment on the #define line that explains the value's usage. Because of the consistent use of capital letters for the name of the #define, the actual #defines themselves are sometimes not listed in the code. For example, the reader who posed this question did not give the values for MAX_FIRSTNAME, and so forth, since they were immaterial to the problem. I try to use a long variable name which describes the purpose of the variable and therefore omit comments that only rehash its use. Ellipses are employed to show code that simply repeats in form what previously is shown. For example, I coded the reader's structure as:
struct s_record { char firstname[MAX_FIRSTNAME + 1]; char lastname[MAX_LASTNAME+1]; ... }; struct s_record *record[MAX_RECORDS];The remaining members in the structure template duplicate the types of the members listed. If the other members had been doubles or pointers, then they would be explicitly declared. The term field is often used to represent one of the members of the structure, when the structure is used as a data record.I broke out the declaration of the array of pointers from the template. This set of declarations operates the same as the reader's. However the template is more usable with other files, since it does not contain a variable declaration.
In the answer, I described a static variable that has a constant address that could be used with an array of pointers to the addresses of the members (see Listing 2) .
Each member in print_record is an array, so the name of the member represents an address. record_field_address[] contains the address of each field (member) in print_record. Using that array, then the records could be printed using the code in Listing 3.
Note that the variable record[] is an array of pointers to structures of type s_record. record[current_record] is a pointer to structures of type s_record. *record[current_record] represents the actual structure pointed to. So print_record = *record[current_record] copies the entire structure, that is the sizeof (struct s_record) number of bytes.
As I mentioned in the answer, a new offsetof() macro created by the ANSI standard can help. (If you don't have offsetof, see the answer to the next question). Its syntax is:
#include <stddef.h> offsetof( type, member-name)The type is a structure type and the member-name is a member in the structure. Instead of keeping the address of individual members in an array, only the offsets from the start of a structure are stored. For example:
#define NUMBER_FIELDS 9 size_t record_offsets[NUMBER_FIELDS] = { offsetof(struct s_record, firstname), offsetof(struct s_record, lastname), ... /* Remainder of the fields */ };size_t is a typedef supplied in <stddef.h>. It is usually an int or unsigned int, but could be a long or unsigned long. If the values you will store do not approach the maximum for an integer, then size_t's data type is unimportant.Using the offsetof array, show_record could look something like Listing 4.
record[current_record] is a pointer to structures of type s_record. The (char *) cast converts the pointer to structure to a pointer to char. This cast does not change the actual value that is assigned. It simply changes the type of the pointer. This conversion to a char pointer is necessary for the next calculation. If you simply printed
record[current_record] + record_offsets[i]you would be getting the address of something which is
record_offsets[i] * sizeof(struct s_record)after the beginning of record.The example code in my original column:
pc = (char *) &record[current_record];was wrong. record[current_record] is a pointer, so the original expression would yield the address of the current_record element in the array. Usually I embed a hint about a varable's type within its name. For example, I would call this array p_record[] or something similar (p for pointer.) The name record was simply copied from the reader's example.If the calling sequence of show_record was changed so that it expects a record (or an address of a record), it would be a more useful function. (See Listing 5)
Once again, the east transforms the pointer to a structure of s_record type to a pointer to char. The & operator on variable record disappeared from the code listing between creation and appearance. In the original column it, appeared as pc = (char *) record, which would have given a compiler error. This function receives one record from the calling function and prints it. I've changed the name of the parameter to a_record to make it clearer that this is not the same as the original array. With the original name of record, the compiler would ignore the global declaration for record[] and simply use the parameter throughout the function.
If you passed an address of a record, then the function would look like Listing 6. I've changed the array's name again to make sure it does not get confused with the original array.
Q
In February 1990 issue, you have given a short cut method of accessing individual elements of structure. I am using AT&T 3B2 machine running UNIX. Can you suggest a similar method which can be adopted under UNIX, because we don't have offsetof function?
Jaspal Singh
Riyadh
Saudi ArabiaA
As described in the response above, you could use a static variable, which will have constant addresses and set up an array of pointers to those address. That is shown in the previous answer. If you needed the offsets, you could simply use the differences in the addresses for the elements as in Listing 7.
The casts are necessary to transform the address of the structure into a pointer to char. Otherwise the compiler would not allow the subtraction, since we would be subtracting pointers to different types. record_field_offsets contains the offset in bytes to each member.
The purpose of the ANSI offsetof macro is to eliminate having to declare a variable simply so that these offsets can be calculated. (KP)
Q
While reading MS-DOS files in UNIX, I get Control M character as a last character. Sometimes files read are not correct as if some portion of the next file is present in the current read file, can you tell me how to correct this problem?
Jaspal Singh
Riyadh
Saudi ArabiaA
When a MS-DOS text file is written, every \n character is replaced with a carriage-return/linefeed pair. When the file is read under MS-DOS, the pair is replaced back with a single \n character. The values for this pair are control-M and control-J.
UNIX does not perform this replacement. There is a single character written for every \n character. Usually this is a control-J. When you read the file under UNIX, the additional control-M is not translated and you see this in the input.
MS-DOS uses a control-Z to signify the end of a text file. Any additional characters beyond this EOF character are not part of the logical file and should be ignored. They are there to fill up a disk sector.
UNIX uses the actual size of the file to designate the end of the file, although a control-D character can be used. When UNIX reads the file, it simply inputs the control-Z as a regular character and keeps reading through the rest of the file.
If you dump the file in binary mode under MS-DOS, you will eliminate the translation of the \n character. You should also write a control-D as the last character of the file.
If you do not have access to the program that orginally created the file, you could write a translation program that reads the MS-DOS created file and performes these translations (see Listing 8) . For brevity, Listing 8 ignores error returns.
Reader's Replies:
Stringizing Constants:
In your column in the March 1990 C Users Journal you asked for a solution to getting manifest constants into string form.I haven't found a way to get a manifest constant into a static string using ANSI "string-izing", but there are other solutions.
1. Use sprintf as part of program initialization to build the string:
#define MAX 10 char errmsg[80]; void init() { sprintf(errmsg, "The maximum is %d", MAX); }This extra code should not be important.2. If there are lots of codes and associated strings, the above approach is wasteful and hard to maintain. A solution we have used is to build an ASCII file containing code names and their strings. A separate program, called out in a MAKE file, uses that file to build two include files, one with an enum to make the values, and the second containing an array of the strings in the same order as the enum constants. The error function is called with, say, the error code (one of the enum values) and a second value which might be a number or a string pointer for more specific data. Only the special text file is maintained; the header files are automatically generated whenever the text file is changed. Any files including the headers are automatically recompiled whenever the header files are changed (see Listing 9) .
Finally, in your example of using manifest constants for printf field widths, ANSI C provides a simple solution: use an asterisk (*) in place of the field width or precision (or both), and provide the value as a type int parameter to printf. Your example becomes:
printf("\n Record is %*s %*s", WFIRST_NAME, record.first_name, WLAST_NAME, record.last_name);Another example, for printing E-format floating-point:
#define E_WIDTH 20 #define E_PREC 8 printf("%*.*E", E_WIDTH, E_PREC, val);Steve Clamage
TauMetric Corporation
San Diego, CAThanks for your reply. Your use of the * for the field width works for the instance I provided. For the actual problem I was facing, I really wanted to put the format specifier strings into a table. (KP)
More on Stringizing
I sent you mail yesterday regarding your March 1990 column on string-izing manifest constants. I suddenly realized that there is an example of how to string-ize a manifest constant in the ANSI standard itself, in the examples following section 3.8.3.5. In the 7 Dec 1988 draft, it is on page 93. You just need one extra level of indirection. Here is a small example:
#define q(x) # x/* just string-ize the argument */ #define quote(x) q(x) /* expand the argument, thenstring-ize */
#define MAX 10 char *p = "MAX = " quote(MAX) ; /* results in "MAX = 10" */Steve Clamage
TauMetric CorpThat'll do the trick. The answer is obvious now that you mentioned it. As I reread the standard again, the replacement will take place as you suggest. Reading the standard is practically like reading the law and sometimes as confusing. I'm looking for a compiler that meets the ANSI standard completely so I can test out all my assumptions, rather than try to figure them out from the text.
The steps for this are:
quote(MAX) Call to macro
quote(10) Expansion for argument x as it will appear in the replacement string. This occurs if the argument in the replacement string is not preceded by # or ## or followed by ##.
q(10) Value of the macro, as argument is substituted.
#10 macro named q is found and argument x is substituted.
"10" Quote operator applied.
Note that the same argument may or may not be expanded in the replacement string, depending on the presence of a # or ## operator. This makes it easy to write a debug macro that works for both variables and macros. For example:
#define debug_int(name) \ printf("\n Value of " #name " is %d", name);makes the following code:
#define MAX 10 int var; debug_int (var); debug_int (MAX);turn into:
int var; printf("\n Value of var is %d", var); printf("\n Value of MAX is %d", 10);Thanks for your help (KP).
Reader's Requests:
Linking FORTRAN TO C
I've written you once before concerning a Turbo C question, and I appreciated your answer (before I got your reply, I discovered an easier way to draw XOR lines using the Turbo setwritemode() function).Well, I've got a couple more Turbo C questions, both pertaining to compatiblility with MicroSoft products.
Some of my clients have written large amounts of code in MS FORTRAN, and we wish to tie their code to C to utilize the graphics capabilities that we have written. The only problem is that we cannot get MS FORTRAN and Turbo C to cooperate.
We called Borland, and they said that "it can't be done", and that even trying to link Turbo C and MS C together works only about 10% of the time.
Given a preference, I lean toward Turbo C, and to that end, I've developed a quite extensive graphics library which calls the Turbo's graphics functions. Naturally, I'd prefer not to have to re-write it again (it started out as a Lattice library calling the IBM (GSS) VDI drivers).
Have you or any of the other readers of the C Users Journal tried and/or had any success linking Turbo C modules with MS FORTRAN?
In a similar vein, some of our customers plan to use Windows 3.x and or OS/2 when they are available to run large 32-bit programs. Have you heard any rumors, etc. as to if or when Borland will offer this support?
Steve Nelson
Mansfield, TexasNot I, but perhaps some of our readers have. (KP)
UNIX Interprocess Communications/Graphics
I am doing commercial programming in C using AT&T 3b2 machine, using UNIX 3.51.My problem is that when my program is loaded from more than one terminal, it corrupts the files, therefore I am interested in knowing more about "inter process communication", can you suggest a good book on the subject.
I am also interested in developing graphics application on my system. I have a VDI interface toolkit, but I want to learn more, so if you can suggest something here too, it will be highly appreciated.
Jaspal Singh
Riyadh
Saudi Arabia
X Window Books
For those of you who have been hearing the X Window buzzword, but want more information, I have just been reading a series of books put out by O'Reilly & Associates, 632 Petaluma Avenue, Sebastopol, CA 95472. They publish a series of books on both X Windows and UNIX. They include: X Protocol Reference Manual, Xlib Programming Manual, Xlib Reference Manual, X Protocol Reference Manual, X Window System User's Guide, Xview Programming Manual, and Managing UUCP and Usenet. (KP)
The Great Name / Obscure Code Contest
As announced in previous columns, this contest has begun.Send examples of the worst names or abbreviations that you have seen in other people's programs (or even your own). Include both the name and a description of what it is supposed to represent. The best (or worst) examples will be published here, with credit for your submission. The name of the programmer who actually wrote the code in which the name is used will not be mentioned without his/her express permission.
Here are a couple more entries:
int spl_flag; long o_count;They both come from a text parsing program.