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 was a member on the ANSI C committee. He also does custom C programming for communications, graphics, image databases, and hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707 You may fax questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.ac.duke.edu (Internet).
Q
Our system has typedefs for structure templates all over the place. What are the relative advantages and disadvantages of typedefs?
George Murphy
New York, NYA
typedefs have several uses. First you can use them to identify the logical use of a variable:
SPEED s; DISTANCE d; TIME t;Second you can use typedefs to create an object-oriented package in standard C, as I've done in Listing 1. Unfortunately, even with OOP packages, I've seen user code something like
my_func() { AN_IMPORTANT_OBJECT my_object; my_object = their_function(); my_object->one_member = 4; }The function my_func() has peeked inside the typedef to find the type that it represents. This action violates the spirit of the typedef. If you have set up packages that requires the user to look inside, you might as well skip the typedef. You are not hiding anything.If the user needs further information on the typedef, you should provide functions, such as:
set_one_member_of_AN_IMPORTANT_OBJECT (AN_IMPORTANT_OBJECT , int); get_one_member_of_AN_IMPORTANT_OBJECT (AN_IMPORTANT_OBJECT);For the purpose of speed, you might consider implementing the functions as macros.One rule of thumb for judging how abstract you've made an object is the degree to which you can modify how the object works. For example, you should be able to rename the actual members of a typedef structure without necessitating a change in user code. If you can reorder the members, then you have achieved even a greater degree of abstraction.
To my mind, employing the typedef is more confusing than declaring data structures straightforwardly. For example, I would prefer to code the sample as
struct s_an_important_object { int one_member; char another_member; .... }; /* Function prototypes */ struct s_an_important_object *their_function(void); void another_function(struct s_an_important_object *); User file: #include "important.h" my_func() { struct s_an_important_object my_object; my_object = their_function(); my_object-one_member = 4; ... }This method works in the case where the user (not the package maker) must look inside the structure. In this case the template is simply a handy way of keeping related information together, not an abstract object.Q
I am trying to write a C function that will receive three integers, and return the largest and the smallest. Such as:
int value1, value2, value3; /* pass these three integers */ int largest, smallest; /* put results here */I can't quite figure a way to get this to work right. I'd appreciate any help with this problem.Steve Kosloske
MilwaukeeA
The most common question asked every juggler performing with three items is, "Can you do four?" You should pose the same question for this problem. Is three the maximum or minimum for which you need a solution, or do you want a more general solution?
If you must solve only the specific case, then the function requires one of two techniques for returning multiple values. You can use parameters that are references (a la scanf) or you can create a structure that holds the two return values.
In the first case, the calling code would look like:
int value1, value2, value3; int minimum, maximum; ... find_min_max(value1, value2, value3, &minimum, &maximum);Listing 2 shows the function itself.With the structure method, you would probably want to place the template in a header file, such as "compare. h":
struct s_compare { int minimum; int maximum; }; struct s_compare find_min_max();The calling code would look like:
#include "compare.h" int value1, value2, value3; struct s_compare compare; ... compare = find_min_max(value1, value2, value3);and the function itself would be as in Listing 3. Of course you could simplify the code by calling the two functions as shown in Listing 4, with similar code for the find_minimum.Now consider generality. Assuming that the variables are not in an array that you could pass, you could use a variable parameter function as in Listing 5. The function returns a single value. You could use either of the previous techniques (pointers or structures) to return both the minimum and the maximum.
Q
I've written a small bit of code and have come to the conclusion that it probably could be done better. Could you rewrite my code in any way to show me how I can improve upon it? The code follows (in Listing 6) .
I can take any amount of constructive criticism, so long as that is all it is, I am asking this question because I want to improve my C coding!
R. Smithers
Guildford, EnglandA
I will start with an overall viewpoint of the code and then give some line-by-line comments. Some comments represent personal preferences developed from many years of coding. Finally, I'll give my version of your program for comparison.
First, the names you used reflect the old style of C, which limited names to eight characters. Using full names would clarify the program's organization.
You should include comments in the header files to explain each function's purpose, its required parameters, and its effect on global variables.
Functions should attempt to return all values through the parameter list. My response to the previous question gives some tips on how to return multiple values. A variable needed by only one function should be local to that function.
With those guidelines in mind, let me hit the lines in detail.
#include <ctype.h> #include <string.h>You should include only the header files your program needs, including <stdio.h>. The file <string,h> is not needed.
int ind=1,tmp=1; int mat=1,spc;These variables should not be global. Note that if your program calls getln twice, the value of ind for the second call is the same as after the first call.
main() { char text [1024];The size of the buffer should be #defined. You can then use the label wherever you need it.
getln (text); putln (text); printf ("Sentence is %d\n",ind); printf ("And has %d spaces in it\n",spc); }It should be clearer how functions called getln and putln changed the values of ind and spc.
getln (text) char *text;A comment would be in order here that suggests getln fills in text with an input string and alters the value of ind.
{ printf ("Enter Text : "); while ((text [ind-1] =getchar ())!='\n') { ind++; } }You do not test whether the index on the array exceeds 1024 (the size of text). Although you may never loop past the end of the array, you have not guaranteed that you won't. You also could have used the standard fgets() function instead.The value of ind should start as 0 so that subtracting 1 from the index would not be necessary. You could modify your implicit definition of the length of a sentence to state that a sentence can have length zero.
putln (text) char *text;Once again, a few comments on the purpose of putln would have been in order.
{ printf ("Text is : "); while(text [tmp-1] !='\n') {If tmp had started at 0, the indexing here and in the following lines would be a bit simpler.
if (mat==1)The purpose of mat might be clearer if it were named print_as_uppercase.
{ printf("%c",toupper(text[tmp-1])); mat=0; }else{ printf("%c",tolower(text[tmp-1])); }A comment stating that the first letter of every word must be uppercase would be handy here.
if (isspace(text[tmp-1])) { mat=1; spc++;} if (text[tmp-1]=='.' || text[tmp-1]==',') mat=1; tmp++; } printf("\n"); }My version of your program appears in Listing 7. The basic outline follows your original program. If I were starting from scratch, I would have written a couple of general-purpose functions:
void uppercase_words (text_in, size_text_in, text_out) /* Uppercase first letter of each word in sentence */ /* Words to uppercase */ char *text_in; int size_text_in;/* Size of input */ /* Output characters */ char *text_out; int count_space (text_in, size_text_in) /* Counts number of spaces in string */ char *text_in; /* Characters in */ int size_text_in; /* Size of input */QHere's something odd that happened to me today while using Microsoft C, version 6.0. I had the following:
char *string; unsigned int *wordarray; unsigned int count; if (wordarray[(unsigned int) string [count]] ++) { ... do some stuff ... }The actual variable names were different and the values of the variables are irrelevant. The problem was in the code generated.In a nutshell, the compiler performed signed arithmetic on the pointer wordarray, even though I was explicitly casting the index to an unsigned value. It was incrementing, for instance, wordarray[-1] instead of wordarray [255].
Is this correct? Seems to be the order of operations would dictate that the compiler obtain string[count], cast it to an unsigned int, add it to wordarray, and increment the unsigned int at that address.
I solved the problem by making string into an array of unsigned char, which is really how I wanted it anyway, but I'm wondering why the compiler didn't generate correct code. Maybe the Microsoft Knowledge Base has something about it.
Will Israel
North Brunswick, NJA
Your solution is one correct possibility. To arrive at my solution I'll explain how the expression
wordarray[(unsigned int) string[count]]++is evaluated.string[count] is a char. A char with the value of 255 indicates that the sign bit is set. The expression is widened to an int (ANSI standard 3.2.1.1) with sign extension. The value of the int is -1. To convert it to an unsigned int, the value of the largest int (65,536) is added, producing 65,535 (per ANSI 3.2.1.2)
When used as the index to the array, the expression is multiplied by two, giving 13,170. This value does not fit into a 16-bit register, so the high bit is dropped, leaving the result 65,534. Adding this index to the base address (a 16-bit register) is the equivalent of adding -2 to the base address. This is the same as using index of -1 for the array.
Listing 8 shows the generated assembly code with some comments.
You also could have added another cast to avoid the widening of the char as:
if (wordarray[(unsigned int) (unsigned char) string[count]]++)which could be shortened to
if (wordarray[(unsigned char) string [count]] ++)since the unsigned char will be converted to an int of the proper value by default.
Readers' Replies
DLLs
In response to David Qualls' question in the September issue, one way to support DLLs is to use the Topspeed line of compilers. These compilers offer full DLL support for MS-DOS, Windows, and OS/2.Lawrence C. Widing
Naperville, IL