Ancient Compilers - Advanced Programming Concepts - Practical C Programming, 3rd Edition (2011)

Practical C Programming, 3rd Edition (2011)

Part III. Advanced Programming Concepts

Chapter 19. Ancient Compilers

Almost in every kingdom the most ancient families have been at first princes’ bastards....

—Robert Burton

C has evolved over the years. In the beginning, it was something thrown together by a couple of hackers (Brian Kernigham and Dennis Ritchie) so that they could use a computer in the basement. Later the C compiler was refined and released as the “Portable C Compiler.” The major advantage of this compiler was that you could port it from machine to machine. All you had to do was write a device configuration. True, writing one was extremely difficult, but the task was a lot easier than writing a compiler from scratch.

The Portable C Compiler was widely distributed and soon became the most widely used C compiler around. Because there were no official standards around at the time, whatever the Portable C Compiler could compile became the “official” standard.

This chapter describes that “standard.” The Portable C Compiler didn’t have many of the features that were later defined in the ANSI standard. Many of these new features were added to make C programs safer and more reliable. Programming in ANSI C is difficult enough. Programing in the old Portable C is like walking a tightrope with no net—blindfolded.

K&R-Style Functions

K&R-style C compilers use an older style of declaring functions. For example, the ANSI C function declaration:

int process(int size, float data[], char *how)

in K&R C would be:

int process(size, data, how)

int size;

float data[ ];

char *how;

{

/* Rest of the function */

Strictly speaking, we don’t need the declaration "int size” because all parameter types default to int automatically. However, we put it there because declaring everything and not letting things default is a good idea.

Function Prototypes

Functions prototypes are not required in K&R-style C and can be omitted. For example, suppose you use the function draw without declaring a prototype:

draw(1, 8, 2, 20);

C will automatically define this function as a function that returns an int and has an unknown number of parameters of an unknown type. Obviously, the type checking of parameters cannot be done. So it is entirely possible to write a program like Example 19-1.

Example 19-1. area/area.c

#include <stdio.h>

float area(width, height)

int width;

float height;

{

return (width * height);

}

int main()

{

float size = area(3.0, 2);

printf("Area is %f\n", size);

return (0);

}

Question 19-1: What will the program in Example 19-1 output when it is run? Why? (Click here for the answer Section 19.6)

K&R-style C does allow for function prototypes, but only the return type can be declared. The parameter list must be (). For example:

extern float atof();

Again, the () indicates that this function takes an unknown number of parameters of an unknown type.

Question 19-2: What does Example 19-2 print and why? (Click here for the answer Section 19.6)

Example 19-2. ret/ret.c

#include <stdio.h>

int main()

{

/* Get the square of a number */

int i = square(5);

printf("i is %d\n", i);

return (0);

}

float square(s)

int s;

{

return (s * s);

}

Question 19-3: What does Example 19-3 print and why? (Click here for the answer Section 19.6)

Example 19-3. sum/sum.c

#include <stdio.h>

int sum(i1, i2, i3)

{

int i1;

int i2;

int i3;

return (i1 + i2 + i3);

}

int main()

{

printf("Sum is %d\n", sum(1, 2, 3));

return (0);

}

Question 19-4: Example 19-4 prints John'=(3 instead of John Doe. Why? (Your results may vary.) (Click here for the answer Section 19.6)

Example 19-4. scat/scat.c

#include <stdio.h>

#include <string.h>

char first[100]; /* First name of person */

char last[100]; /* Last name of person */

/* First and last name combined */

char full[100];

int main() {

strcpy(first, "John");

strcpy(last, "Doe");

strcpy(full, first);

strcat(full, ' ');

strcat(full, last);

printf("The name is %s\n", full);

return (0);

}

Prototypes are an extremely valuable diagnostic tool for the C compiler. Without them, all sorts of errors can happen without the programmer knowing it. For this reason, prototypes were borrowed from C++ and put in C.

Library Changes

Like the language, the library has evolved as well. The “standard” C library used to be whatever came with the UNIX operating system. Then, the UNIX operating system split into two families: BSD-UNIX and System V UNIX. The standard library split as well.

When ANSI standardized C, it standardized the library as well. However, you will still find code out there with the old library calls. The main differences are:

§ The old K&R C had no stdlib.h or unistd.h headers.

§ A number of the older functions have been renamed or replaced. Table 19-1 lists the functions that have been updated.

Table 19-1. K&R Versus ANSI Functions

K&R function

ANSI equivalent

Notes

bcopy

memcpy

Copies an array or structure.

bzero

memset

Sets memory to zero.

bcmp

memcmp

Compares two sections of memory.

index

strchr

Finds character in a string.

rindex

strrchr

Finds character starting at end of a string.

char *sprintf

int sprintf

The K&R function returns pointer to string. The ANSI standard one returns number of items converted.

Missing Features

As we’ve said before, the C language has been evolving for some time. Some of the earlier compilers may not have the latest features. Some of these features include:

§ void type

§ const qualifier

§ volatile qualifier (See Chapter 21)

§ The stdlib.h header file or unistd.h header file

§ enum types

Free/Malloc Changes

In ANSI C, the malloc function is defined as:

void *malloc(unsigned long int size);

Because void * indicates “universal pointer,” the return value of malloc matches any pointer type:

struct person *person_ptr; /* Define a pointer to a person */

/* This is legal in ANSI C */

person_ptr = malloc(sizeof(struct person))

Because some K&R C compilers don’t have a void type, malloc is defined as:

char *malloc(unsigned long int size)

In order to get the compiler to stop complaining about the different pointer types, the output of malloc must be cast to make it the proper type:

struct person *person_ptr; /* Define a pointer to a person */

/* This will generate a warning or error in K&R C */

person_ptr = malloc(sizeof(struct person));

/* This will fix that problem */

person_ptr = (struct person *)malloc(sizeof(struct person));

The same problem occurs with free. While ANSI C defines free as:

int free(void *);

K&R defines it as:

int free(char *);

So you need to cast the parameter to a character pointer to avoid warnings.

lint

The old C compilers lacked much of the error checking we take for granted now. This deficiency made programming very difficult. To solve this problem, a program called lint[23] was written. This program checks for common errors such as calling functions with the wrong parameters, inconsistent function declarations, attempts to use a variable before it is initialized, and so on.

To run lint on your program, execute the command:

% lint -hpx prog.c

Option -h turns on some heuristic checking, option -p checks for possible portability problems, and option -x checks for variables declared extern but never used. Note: On System V UNIX systems, the function of the -h option is reversed, so you should omit it on these systems.


[23] For more information, see the Nutshell handbook Checking C Programs with lint, by Jan F. Darwin.

Answers

Answer 19-1: The problem is that our area function takes as its arguments an integer and a floating-point number:

float area(width, height)

int width;

float height;

But we call it with a floating-point number and an integer:

float size = area(3.0, 2);

We have our types reversed: function(float, int)—call(int, float). But C has no way of identifying this reversal because we are using K&R-style C. The result is that when the program passes the parameters from main to area, they get mangled and our output is garbled.

Question 19-5: Example 19-5 contains our “fixed” program. We now use two floating-point parameters, “3.0” and “2.0”, but we still get the wrong answer? Why?

Example 19-5. param2/param2.c

#include <stdio.h>

float area(width, height)

float width;

float height;

{

return (width * height);

}

int main()

{

float size = area(3.0 * 2.0);

printf("Area is %f\n", size);

return (0);

}

Answer 19-2: The result is garbled. A typical output might look like:

i is 1103626240

which is a little large for 52. The problem is that we have no prototype, even a K&R-style prototype, for square. The result is that C assumes the default definition: a function returning an integer that takes any number of parameters.

But the function returns a floating-point number. Because we return a float at a point at which C thinks we are receiving an int, we get garbage. The problem can be fixed by putting a K&R-style prototype at the beginning of the program:

float square();

An even better solution is to turn this program into ANSI C by adding a real prototype and fixing up the function header.

Answer 19-3: This program prints out a random number based on the sum of three uninitialized variables.

The problem is with the function header:

int sum(i1, i2, i3)

{

int i1;

int i2;

int i3;

Parameter type declaration occurs just before the first curly brace ({) of the function. In this case, we have no braces, so the types i1, i2, and i3 default to integer.

But then we have the declaration of i1, i2, and i3 at the beginning of the function. These declarations define local variables that have nothing to do with the parameters i1, i2, and i3. But, because these variables have the same names as the parameters, they cause the parameters to become hidden variables. These three uninitialized variables are summed and this random result is returned to the caller.

ANSI has outlawed this type of silliness, but many compilers still accept this code.

Answer 19-4: The function strcat takes two strings as its arguments. In the statement strcat(full, ' '), the first argument, full, is a string; the second, ' ', is a character. Using a character instead of a string is illegal. Old-style C compilers do not type-check parameters, so this error gets by the compiler. The character ' ' should be replaced by the string " ".

Answer 19-5: The problem is that we wrote:

float size = area(3.0 * 2.0);

when we should have written:

float size = area(3.0, 2.0);

The first version passes the expression “3.0 * 2.0” or “6.0” as the first parameter. No second parameter exists. C doesn’t check the number of parameters so it made up a random value for the second parameter.