For this assignment I chose to show ambiguities in the specification and/or implementation of the 'C' and Pascal languages. While some of these programs exploit well-known limitations, the ideas discussed in [1], [2], and [3] were extremely helpful.
Pascal
For Pascal, I used the following three compilers under three different environments:
Five sample programs were used to demonstrate either implementation problems in the compiler or a language ambiguity which caused different computational behavior amongst the above systems. The results are shown in the following table.
| Name | Description | Turbo Pascal | pc | fpc |
| bigint | Tests size of integers | Loop never stops (16-bit) | Loop terminates properly (32-bit) | Loop never stops (16-bit) |
| harmful | Uses goto to jump into for | Control variable is undefined | Compile-time error | Control variable is undefined |
| logic | Tests shortcut evaluation | Shortcut | Full evaluation | Shortcut |
| int_real | Real calculations on integers | Compiler-time error | Compile-time error | Undefined result (no warning) |
| term_func | Jumps out of a function | Compile-time error | Compile-time warning (variable never assigned) | Compile-time warning (variable never assigned, seg fault) |
bigint - This first program exploits a well-known problem in program portability, namely, the size of data types. Various machine architecture/operating system combinations can dictate different sizes for integers or pointers. Older DOS systems, for example, used 16-bit quantities for integers and pointer types because of the 80286 chips they ran on. On a 32-bit system, the following code fragment will run as expected.
However, as the comment indicates, under a 16-bit machine the maximum positive value is
32,767 (215 ). The 16th bit is the sign bit so if we increment 32,767 by one the sign bit is now 1 and we have a negative value. What is surprising is that fpc still used 16 bits for an integer when running under a 32-bit OS. No doubt this is due to its DOS roots.
harmful - By using a goto statement, this program is able to jump into the middle of a for loop, thereby avoiding initialization of the control variable. Both Turbo Pascal and fpc allow this behavior. As a result, the control variable takes on the value of its last assignment (or an undefined value if uninitialized). The pc compiler, on the other hand, notifies the programmer that goto is not to be used with a structured statement in this way.
logic - This code tests for shortcut evaluation of boolean expressions. For example, if the first part of a compound AND turns out to be false, the statement as a whole will also be false. 'C' uses this shortcut evaluation in its language specification. It seems that Pascal implementors, however, have been left to their own devices. The following code fragment provides an example of this indeterminate behavior.
Both Turbo Pascal and fpc skip the second part of this AND statement and save themselves from a divide-by-zero error. pc performs a full evaluation and suffers a runtime exception.
int_real - This example performs real division with integer arguments. As expected, both Turbo Pascal and pc generated error messages at compile time. I was a bit surprised to find out that fpc did not. More importantly, the calculation was carried out with an incorrect (and undefined) result. In a strongly-typed language such as Pascal, this is most certainly a bug in the compiler rather than a justifiable implementation decision.
term_func - Our discussion in class about the legitimacy of terminating a function prematurely led to the following example:
This nasty little contrivance led to three different results. Turbo Pascal generated a compiler error message about using a label out of the current scope. pc gave a warning about an unreachable statement (the calculation of square) but generated an executable which never made an assignment to square. The behavior of fpc was like that of pc except the executable generated a segmentation fault error. Perhaps this was because of the abrupt termination of square() and an unpopped stack.
C
By contrast my study of 'C' led to fewer implementation inconsistencies. There are several reasons for this. First, I only examined two compilers instead of three. It would have been nice to have used an old DOS-based compiler like Turbo C, but I didn't have any available. I think I made up for this by finding (of all things) a Macintosh compiler. Secondly, since 'C' is not a strongly typed language like Pascal, it doesn't have to worry about special cases that may arise in cross-type calculations (like int_real). Third, 'C' has become a more popular language than Pascal and, as a result, may have some better compiler technology supporting it. Here are the compilers I used:
The same programs used for the Pascal evaluation were ported to 'C'. The results of those programs along with two additional tests are shown in the table below.
| Name | Description | gcc | Symantec |
| bigint | Tests size of integers | Loop terminates properly (32-bit) | Loop never stops (16-bit) |
| harmful | Uses goto to jump into for | Control variable is undefined | Control variable is undefined |
| logic | Tests shortcut evaluation | Shortcut | Shortcut |
| int_real | Real calculations on integers | Truncates to 0 | Truncates to 0 |
| term_func | Jumps out of a function | Compile-time error | Compile-time error |
| order | Checks parameter evaluation for functions | right to left | right to left |
| cast | Casts char* to int* | Pointer unaltered | Loss of information |
As shown in the chart, all of the tests but two show consistency between the implementations.
bigint - Although the Macintosh used a fairly modern processor (Motorola 68040) with 32-bit registers, the MacOS maintains backward compatibility by using 16-bit quantities for its integer type.
cast - This test exploits the difference in the sizes of integers (16-bit) and pointers (32-bit) as the following fragment shows:
For a 32-bit system, assigning a pointer to an integer causes no problems. However, on the Macintosh (and some DOS-based compilers as well) storing p (32-bits) into xp (16-bits) will cause a loss of information. As a result, the original pointer value is lost. This discrepancy can be alleviated, of course, by using a generic pointer (void *) instead.
order - Although this test produced a consistent result, it should be noted that some compilers allow the standard right-to-left evaluation of parameters to be replaced with the opposite scheme. A compiler that deviates from the norm can cause problems, as code.c shows:
If right-to-left evaluation is used, the global variable 'i' becomes 10. With left-to-right evaluation (sometime refered to as the Pascal calling convention), the result of calling foo sets 'i' to 1.
References
[1] J.W. Backus, et al, "Revised Report on the Algorithmic Language Algol 60", The Computer Journal, 19(4), 1976.
[2] Brian W. Kernighan, "Why Pascal is Not My Favorite Programming Language",Computer Science Technical Report no. 100. April 1981.
[3] Andrew Koenig, "C Traps and Pitfalls"