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 would be very glad if you could help me with a preprocessor problem.
I am trying to keep all external variables in just one .H file. The preprocessor should then produce the appropriate declarations in the main file and the external references in all other modules. This works just fine so far (Listing 1) .
The problem arises as soon as I would like to initialize an external variable in the .H file. A program line in the .H file like
EXTERN i = 5 ;leads to a link error saying "symbol defined more than once" (MSC 5.1). It would be nice collecting all global variables in just one
int i = 5 ;in the main file and
extern int i ;in all other modules.One solution could be :
#ifdef MAIN int i = 5 ; #else extern int i ; #endifbut it isn't very elegant.Andreas Lang
A
There is no write-once solution. In this regard, variable definitions and declarations are similar to functions and prototypes; you need to repeat the prototype, even though the function already is listed.
I don't try to initialize in a header file, but I do include a copy of the header file in the source file where the variable is declared. For example, the header file would be:
"extern.h" extern int i; /* External reference */and the main source file would have:
#include "extern. h" int i = 5; /* External definition */You can have both an extern reference and a definition in the same source file and the compiler should check for agreement between them.Q
Using C library functions, how can I show on the screen the ASCII character for each of these seven control codes or hexadecimal values?
BEL 07 CR 0d BS 08 SUB 1a HT 09 ESC 1b LF 0aI want to display the seven characters the way printed charts picture them. But with printf(), for example, I beep, backspace, tab, etc.Any suggestions would be welcome.
Dale Wharton
Montreal QCA
The answer to your question depends on what you mean by "C library functions". ANSI standard library functions will not allow you to display these characters. Therefore, you must use compiler-dependent functions. Many compilers provide a version of the putch() function that will write directly to the screen (using the BIOS) without going through DOS. The function you need may be named conout() or something similar.
If your compiler does not have such a function, but does have a function that allows you to call interrupts (it might be called sysint or int86 or intcall), you can build your own conout(). When calling the interrupt function, invoke INT 0X10 and set the AH register to 10 (output a character), AL to the character to output, CX to 1 (number of characters to output) and BH to 0 (display page normally 0). Failing that, you could write the character directly into screen memory, using a properly initialized far pointer.
Readers' Replies:
Bytes Into ints
In the July 1990 of The C Users Journal, Larry Myers of Raleigh, North Carolina wrote you asking about stuffing character values into declared integer variables. You suggested he try the memcpy() function or bit shifting to achieve his ends.Using the memcpy() function brings a considerable amount of overhead into his program to do something so basic. And while bit shifting does the job, the resulting code is not self explanatory, nor is it portable across platforms that may use more than 16 bits as the size of an integer. Finally, neither method provides a sensible way of retrieving either of the newly stuffed character values from the integer storage location.
I might have suggested the use of a union instead. It uses no extra overhead as does a function, it is immediately understandable to anyone having to maintain the code later, it is independent of either character or integer size, and it provides easy access to the character values after storage.
I have provided a short test program (Listing 2) and the associated output to illustrate. The union sets up memory storage to accommodate both an integer type variable and a character type variable, declared as an array of size two. In the IBM compatible world, this translates into a single two-byte memory allocation, which is defined as type uSTUFF. The two variable types overlap each other perfectly, and the union allows us to access either component individually, as well as readably.
One final thing Larry might remember is that the Intel 80X86 CPUs work with bytes of data in what is called "big endian" format (also referred to as a "left-to-right" architecture). Computers conforming to the "byte-addressable" model (and IBM PCs and compatibles do so) assume that the address of a variable type is the same as the address of the "first" byte of the variable, which is the byte lowest in memory (closest to memory location zero).
In "big endian" format, the first byte of a variable is the highest order byte, which means that when we look at the variable in two-byte chunks, we see the lower order byte on the left and the higher order byte on the right. This is only important if Larry wants to treat the newly created stuffed union as an integer for calculation purposes. If so, then he must watch the order in which each character is stuffed into the union.
Rod Hutson
Edmonton, Alberta, CanadaThanks for your reply. I don't condone trying to play games with chars and ints. As you mention, it can be inherently nonportable. Although memcpy does bring in extra overhead, if you're only going to do it a few times, it won't hurt. For example, to match your listing, I would use memcpy (&iInteger, "AB", 2) to copy the two bytes. If you are going to be doing this game a lot throughout your code, then using the union as you propose is a good idea. (KP)
Printers
I am an avid reader of The C Users Journal, and have been for a good three years now. As such I have always enjoyed your question and answer column. However, having just read the June 1990 issue I must make a comment on your column, as it seems to me that one of your answers is in error.First regarding the proper C statements to cause formatted output to go to the line printer: your answer gives the program in Listing 3 and states that on MS-DOS that the line printer device is called LST.
Although my experience with MS-DOS is actually with IBM PC- DOS (I have used several versions of it over the years on several different machines currently using PC-DOS 3.30 on a DTK PC/XT clone) rather than a generic MS-DOS I have not run into one which calls the printer device by that name. All that I have seen call it PRN, although you can also use LPT1, or LPT2 or LPT3 if you wish to be specific.
Anyway, I was puzzled by this so I tried your program with both Turbo C 2.0 and Microsoft QuickC 2.0 and found that both of them write their output to a file named LST, as I expected. If one opens the device PRN, however, the output does go to the parallel printer port, as desired:
fopen ("PRN", "w");A further "improvement" might be to dispense with the fopen and fclose calls entirely. This is easily done on MS-DOS systems (at least when using Turbo C or MS C) by writing to the standard printer device, stdprn:
fprintf (stdprn, "The answer is %10d", z);Both Turbo C and MS C provide an open FILE device by this name which points to the PRN device. The resulting program then becomes Listing 4.Please do not take this comment as being unduly critical of your column it is always very educational and well worth reading! (Besides this is probably about the hundredth such letter on this subject that you will receive!)
Frederick C. Smith
Stoneham, MAMea culpa. Your's is not the only letter I've received. The LST name just came up in my memory slot and sounded so right. It happens that's the name on some compilers on some other systems. (Reader's are invited to guess which ones.) The MS-DOS device reserved names (all of which can be used in an fopen) are:
CON console AUX first serial port COM1 " " " COM2 second serial port LPT1 first parallel printer PRN first parallel printer LPT2 second parallel printer LPT3 third parallel printer NUL dummy deviceAlthough stdprn is "semi-standard", it's not ANSI standard, and therefore I tend to shy away from it. In addition to being ANSI incompatible, using stdprn makes it harder to change the output device. Listing 5 shows how the function could be written using a variable passed to fprintf, rather than stdprn. This listing permits redirection to either the printer, a disk file (or other device listed above), or to the NUL file (a dummy device all output written to a NUL file is thrown away).Listing 6 shows a portion of Listing 5 rewritten to use the stdprn value that "might" be in <stdio.h>.
Along this same line, for some of our readers, let me review a little about file pointers. The ANSI standard states that there are three predefined file pointer values:
stdin standard input /* Default: keyboard */ stdout standard output /* Default: screen */ stderr standard error /* Default: screen */These three flies (or devices) are already opened when your C program begins. When you call printf, you are writing to stdout.
printf("Hello world\n");is the same as:
fprintf(stdout, "Hello world");When you call scanf, you are reading from stdin.
int i; scanf("%d",&i );is the same as:
int i; fscanf(stdin, "%d", &i);To write to the standard error output, you need to call fprintf, as:
fprintf(stderr, "This is an error");The stderr output is set to the screen and under MS-DOS cannot be redirected. It should only be used for those messages which are absolutely critical to be output. (KP)I refer to the Q and A column in the July, 1990 C Users Journal. Part two of the first question asks for a method of addressing a printer other than using stdprn. Your answer was, under MS-DOS, to use the MODE command to designate a printer as either LPT1 or LPT2.
However, what do you do if you have two printers attached to one computer and you want to select them individually in your program? The answer was given to me by one of the technical people at Borland. You can select a printer, in your program, as follows:
FILE *fp1; FILE *fp2; fp1 = fopen("LPT1", "wb"); fprintf(fp1,"Hello printer ONE!"); fclose(fp1);If two printers are connected you can send output to the second printer with:
fp2 = fopen("LPT2", "wb"); fprintf(fp2,"Hello, printer TWO!"); fclose(fp2);As you can see each printer can be addressed as you would any file. In this case the file names are LPT1 and LPT2.Jack Steiner
Cedar Grove, New JerseyThanks for your reply. This is another possible reason for not using stdprn. The printers could be opened in either binary wb mode or text w mode, depending on how the printers are set up. The binary mode would not translate \n characters to CR/LF combinations, while the text mode would. (KP).
In reference to your reply to Bill Whitcraft (The C Users Journal, Volume 8, No.6, page 73).
Bill used WRITELN in Pascal, which automatically ends the line with a carriage return. C's printf( ) does not do this, so Bill should be advised to include the \n escape character.
John Deurbrouck
Mountlake Terrace, WashingtonThanks. Make that code:
fprintf (file_printer, "The answer is %10d\n", z);(KP)
Obfuscated Code
I was wondering where I could send a submission for the Obfuscated Code contest sponsored by The C User's Journal.The code in Listing 7 is a validation test for a code object in the voice processing metalanguage VOCAL (Voice Oriented Computer Application Language™).
In English, the code in Listing 7 performs the following operation:
Well, it made sense at the time and, for some reason we can't fathom, actually works.
- If the block under test {blk[BN]} claims to be referenced by an invalid block or ...
- If the referencing block {blk[blk[BN]->pt.parent]} has been deleted or...
- The referencing block is not a transfer class and ...
- The block under test is not in the global mode and...
- The referencing block is not in the global mode and...
- The referencing block is not in the same mode as the block under test.
- Mark the block under test for deletion.
Mark Assousa
Charlotte, NCThanks for the contribution. This is a "pretty-printed" version of your listing. I had to be sure the parentheses matched up.(KP)
[Editor's Note: We don't really "sponsor" the contest, though Don Libes does report the results each year. The contest is run by Landon Noll and Larry Bassel. To enter, send email to ...!amdahl!obfuscate or surface mail to Landon Curt Noll, Amdahl Corp., 1250 E. Arques Ave. M/S 316, P.O. Box 3470, Sunnyvale, CA, 94088-3470. Please note that entries must be in a certain format. See the Jury 1989 issue of CUJ for details.]
FORTRAN And C
In response to Steve Nelson's letter in the June issue, about linking FORTRAN to C, there are several ways around this problem. (1) translate, (2) use externals, (3) buy compatible C and FORTRAN compilers and (4) have the client buy NKR Research's FORTRAN.My personal favorite is to use a FORTRAN to C translator. You can have the client do the translation or you can do it yourself. If you have the client do the translation you can lose control of the quality, but then you don't have to sign a lot of secrecy papers.
The big draw back of using externals is that you will have to write a lot of new code. And you will have to do a lot of coordinating between you and your client. But you should have to do it only once.
There are two companies that have compatible FORTRAN and C compiler that I know of Micro Way and Watcom. They are designed for the 32-bit processor.
NKR Research claims that you can call from other languages without interfacing subroutines. I also got a similar response from Borland's Tech staff.
Eric Teeter
Brooklyn, Wisconsin
MS-DOS to UNIX Files
The C Users Journal is great one of my favorites, and I always enjoy your column! I have one comment however. Your reply to Jaspal Singh's question (Q?/A? June 1990) about extraneous carriage returns (Control-Ms) in MS-DOS files when they are moved onto a UNIX system was accurate and informative, but I think that there is a better solution than the little program given. Most UNIX shells provide a translate function (tr) that should take care of this quickly and efficiently. The command:
tr -d '\015' < msdos.file > unix.filewill delete all Control-Ms from the MS-DOS file. And the sequence:
tr -d '\015' < msdos.file | tr '\032' '\004' > unix.fileshould combine that with translation of the Control-Z (MS- DOS EOF) to Control-D (I have not actually tried this!). My apologies for the "bang-dash-dot-backslash" syntax so typical of UNIX. Some of your readers can probably point out a way of translating both characters in a single translate command that makes use of some arcane "feature" of the shell.Ahhhh....isn't UNIX wonderful!! Perhaps there should be an "Obfuscated UNIX Command Line" Contest??
Roger Hanscom
Livermore, CAIn reference to your reply to Jaspal Singh, if the program is converted on the MS-DOS machine, here is a simpler solution! (Listing 8)
I hope that this is useful to you.
John Deurbrouck
Mountlake Terrace, WashingtonDeurbrouck's program opens the input file in text mode and writes the output file in binary mode. On text mode input, CR/LF combinations are converted to single \n. On binary mode output, there is no conversion of the \n. His program does not remove the EOF (control-Z) character found in MSDOS text files. Depending on how you move the file, the EOF may not be a problem. (KP)
Dynamic Linking
Your column in the July 1990 C Users Journal had a request for dynamic linking of C code on the IBM PC. The TopSpeed C compiler provides this in the current version; I wish Borland and Microsoft would follow suit. There is also an English compiler suite (Fortran and other languages) that I saw reviewed recently that has something similar; I'm at home now, and don't recall the exact company name offhand. The compiler review might have been in a recent issue of Computer Language.