This paper was presented at the G.U.I.D.E.&SHARE Europe Joint Conference (10-13 October 1994, Vienna, Austria).

Text marked up in cooperation with:

Please use at least WebExplorer Version 1.03 or NetScape Version 1.1 as WWW browser.

The author is member of the scientific staff for application programming at the Computing Center of the University of Münster, Germany.

This presentation is biased -- neither accidentally nor intentionally but necessarily. The reason is that programmers have their favorite programming language. Mine is PL/I but I studied C because there is a widespread operating system called Unix which I had to write programs for. Additionally I had many discussions with colleagues in my computing center comparing the two languages. Of course, I will always refer to OS/2 PL/I and ANSI-C because they are the modern versions!

First I want to present 5 theses: Too many things in C...

How does a program look?

Let's start with the outward appearance of a C and a PL/I program:

C PL/I /* compilation unit: file */ #include static void subroutine (...); float function (...); float something; static float anything; extern float nothing; int main (void) {... subroutine (...); ... } void subroutine (...) {... printf ("%f", function(...)); ... } float function (...) { ... return ...; } /* compilation unit: package */ Program: package exports (Main); declare Something float external, Anything float static; Main: procedure options (main); ... call Subroutine (...); ... end; Subroutine: procedure (...); ... put (Function(...)); ... end; Function: procedure (...) returns(float); ... return (...); end; end;

As you can see, at least comments are the same in both languages. The first difference is the treatment of letters. C is case-sensitive, PL/I doesn't care. For example, don't write the word main in C in capital letters; you will get an error message by the linker! The main procedure in C always has to have the name main ! PL/I handles the specification of a main procedure in the usual way: it introduces keywords: options (main) . Here we come to the next difference: Keywords in C are reserved words, in PL/I it's clear from the syntax what is a keyword and what is not. In PL/I you can truthfully say "What you don't know can't hurt you!"

Both in C and in PL/I the unit of compilation is a file. But in PL/I there must be at least one complete procedure contained in the file; in the general case a file will contain a package as shown here. The advantage compared to C is that the compiler is able to recognize a corrupted file, perhaps caused by a file transfer.

The scope of functions and variables defined outside functions in C is global by default! The PL/I rule -- names are local to the package or procedure -- isn't as dangerous: You have to mention procedures in the export-option of the package statement and to declare variables external if you want them to be used by other routines. The method to reduce the scope in C is to define it as static . This treatment is a candidate for thesis 1! Static storage is storage that lives during the lifetime of a program. And that is true whether you specify static or not. static in this place means "local to the file"! Even the scope of a function can be reduced to the file by declaring it " static ".

If you wonder why there is a #include -line in the C program: input and output is done by library functions, and you have to include the function declarations in order to be able to do I/O.

The two lines following in the C example have no counterpart in PL/I. They declare the functions coming afterwards in the file, for the purpose of correct parameter passing. This is neither necessary nor possible in PL/I because the compiler "knows" all procedures in the package, only procedures in other files have to be declared in an analogous manner. C differentiates between declaration and definition: the first is an association of attributes and the second an association of storage. In PL/I there are only declarations, a PL/I programmer doesn't even understand the problem! The extern -declaration doesn't reserve storage. Why it's called extern is mysterious: The definition may come in another file or in the same file later on!

Declarations and definitions are order-dependent in C and block-dependent in PL/I. This means that in C you cannot use something you have not at least declared previously in the file. In PL/I declare -statements may be in any order, relevant is only the block they are contained in. This makes life easier for PL/I programmers!

Obviously there are two kinds of subprograms in PL/I and only one in C. The subprogram subroutine is called by a call statement in PL/I and only by referencing the name in C. C programmers praise this fact as a clean solution. But referencing thesis 4 I would reply that a subprogram should only serve one of two purposes: either computing a value or providing an effect! This can be checked by a compiler and is, of course, checked by the OS/2-PL/I compiler: there must be either a returns-attribute and a return-statement with value or a return-statement without a value (or return by an end-statement). The methods of parameter passing will be explained later.

Signed Characters?

At first glance declarations (or definitions) look similar in C and PL/I:

C PL/I const volatile float f = 1; dcl F float init(1) nonasgn abnormal; dcl F float nonasgn abnormal init(1);

But there could be no greater error! First you have to obey a certain order of keywords in C whereas PL/I only demands that the first keyword specifies the statement type. The attributes following the name of the variable may be in any order. Of course the initial value 1 has to follow immediately the keyword init ! The keyword const in C means in fact nonassignable as it it called in PL/I! Recall Thesis 1! The following statements declare fixed-point datatypes:

C PL/I char I; signed char J; unsigned char K; short int L; int M; long int N; /* no counterpart */ /* no counterpart */ dcl I char; /* ? */ dcl J fixed binary (7); dcl K fixed bin (8) unsigned; dcl L fixed bin (15); dcl M fixed bin; dcl N fixed bin (31); dcl O fixed bin (31,16); dcl S fixed dec (7,2);

If you specify only char , it depends on which C compiler you're using whether it's signed or unsigned. What adventurous consequences this has will be discussed in the operations part of this presentation. If you have never heard of signed characters you needn't worry! It's up to thesis 1: char means in fact small integer, as you see when you regard their PL/I counterparts! You feel the power of PL/I when you look at the various types on the right: Binary fractionals (you can find a long-winded C-style definition of them in OS/2 Presentation Manager manuals) and decimal numbers even with fractional parts. In PL/I you can declare in a problem-oriented fashion what precision you want, as it is for floating-point numbers:

C PL/I float A; double B; long double C; /* no counterpart */ dcl A float; dcl A float decimal (6); dcl A float binary (21); dcl B float dec (16); dcl B float bin (53); dcl C float dec (33); dcl C float bin(109); dcl D float dec (12);

Non-Computational Datatypes?

Additionally there are non-computational datatypes in PL/I: area, entry, file, format, handle, label, offset, pointer, and task ! Because of the little space in this presentation I will only comment on two PL/I counterparts to the C datatype pointer. First a typical usage in both languages:

C PL/I char x, *p = &x; *p = 'Z'; dcl X char, P ptr init(addr(X)), C char based (P); C = 'Z';

The C definition means that x and *p each have one byte of storage. * in this context is the indirection operator: *p is the storage where p points to. In other words: p is a pointer to char . The syntax is slightly chaotic; on the one hand *p is of type char , on the other hand p is initialized to the address of x (by using the address operator & ). It's unclear whether to group char *p or p = &x ! The assignment means in fact that x gets the value 'Z' .

In the PL/I example the pointer p is "untyped". This is a point where using PL/I can get adventurous, but the advantage is that pointers in PL/I often only appear in declare statements as it is in this case. You have to define a character object C which is located where the pointer P points to. In the assignment no pointer is used, only the name of a based object.

The other example shows the usage of variable subprograms in the two languages:

C PL/I float (*f) (float); f = func; printf ("%f", f(7.3)); dcl F entry (float) variable returns (float); F = Func; put (F(7.3));

The C philosophy is that f is a pointer to a function with parameter float which returns a float value. In PL/I there is no pointer needed, only the program control data type entry . The keyword variable is necessary because names of procedures are entry constants. You shouldn't confuse the C definition with the following: float *f (float); which means that f is a function which returns a pointer to float ! By the way, what do you think is the result of quotient in the following example:

C float *pdenominator; ... quotient = nominator/*pdenominator /* ? */;

You think that nominator is divided by the value pdenominator points to? No, that's wrong (surprise, surprise)! The characters "/*" after nominator start a comment! And thinking of thesis 5: does your compiler complain?

What Is an Array?

In order to introduce character strings we have first to present arrays. Although there is a rudimentary concept of arrays in C, no "good" C programmer uses the rudimentary array notation directly (with the exception of character arrays). Instead they use pointers which are closely related to arrays in C. For example you want to copy array A to array B :

C PL/I int i; float a [10], b [10]; for (i = 1; i < 10; i += 1) a[i] = b[i]; dcl I fixed bin; dcl (A, B init ((5)0,(5)1)) dim (0:9) float; do I = lbound(A) to hbound(A); A(I) = B(I); end;

The C philosophy is that array bounds start with 0 and extend to N-1, if N is the declared number of elements. In PL/I you can specify that you mean the pumpkin harvest of the years 1989 to 1993!

PL/I dcl Pumpkin_harvest (1989:1993) fixed bin (31);

Of course this supports thesis 3 because no human starts counting from 0! And what do "real" programmers in C and PL/I do to improve the examples above?

C PL/I float *pa, *pb; float a [10], b [10]; for (pa=a, pb=b; p < a+10; *pa++, *pb++); A = B;

Instead of arrays only pointers are used, with pointer arithmetic used exclusively! What advantage are C programmers hoping for? The PL/I compiler can do a better job of optimization than any C programmer! Here thesis 1 and 4 apply! An array is treated as a pointer and no subscript value can be checked by the compiler. Using pointers is not a natural way of programming arrays. They obscure inherently clear things! The most extreme example is the C definition that a[i] is the same as *(a+i) . And this is the same as *(i+a) . As you can easily conclude, they are all the same as i[a] !

Let A and B declared as above, then the following is possible in PL/I:

PL/I dcl C float dim (0:9); C = A + 2*B - 4;

Arrays are full fledged objects and not pointers in PL/I!

What Is a String?

Now to the translations of char to PL/I counterparts:

C PL/I char c = ' '; char s [10] = {'a','b','c'}; char t [4] = "abc"; t = s; /* illegal */ t = "abc"; /* illegal */ strcpy (t, s); dcl C char init ('Ä'); dcl S char (3) init ('abc'); dcl T char (3) var; T = S; T = 'abc'; T = T || 'xy';

The C paradigms know about character arrays like s in the example above and strings which extend up to a null byte like t . Here t effectively uses 4 bytes of storage, the length is maximal 3, and the subscript of the last character is 2! As arrays are in fact constant pointers you cannot assign s to t because you cannot assign a value to a constant!. The string constant "abc" may be assigned to t in the definition but not by a assignment statement! Indeed, the library function strcpy brings some limited help, but it will blindly move many bytes of s to t until it finds the null byte. There is no check possible whether there is enough space in t ! In PL/I there is even a concatenation operator ( || ), a task which has to be done in C again by using a library function!

In PL/I, if you want to declare varying length strings ending with a null byte like in C, you can use the varyingz attribute. If you want to use varying strings of arbitrary contents (even ones containing null bytes in the midst of the string) PL/I gives you the opportunity to do so by specifying the varying attribute. There is no distinction between characters and strings. Both are scalars in PL/I. Of course, you can declare arrays of strings!

What Are Bits?

In C there are three concepts regarding bits. The first one treats int as a sequence of bits. In PL/I you have to use the pseudovariable and builtin function unspec if you want to treat anything as bit if it is not:

C PL/I short int k; k = k & 0x00FF; dcl K fixed bin (15); unspec(K) = unspec(K) & '00FF'x;

This is, of course, not really equivalent because unspec means bit representation whereas a hexadecimal constant in C represents an int ! Of course, use of unspec may inhibit portability! In PL/I there is an extra datatype bit which can be used in an analogous manner to character strings, complete with builtin functions like substr , which can be modelled in C only in an arcane manner:

C PL/I short int x; k = (x>>(m+1-n))& ˜(˜0<<n); dcl X bit (16); K = substr(X,M,N);

The second concept in C uses structures; this, of course, is possible in PL/I, too:

C PL/I struct {int a: 4; signed int b: 1; int c: 3;} byte; byte.c = 2; dcl 1 Byte unaligned, 2 A bit (4), 2 B bit, 2 C bit (3); C = '010'b;

Really strange is the treatment of bits as integers: The variable b can have only one of two possible values: 0 and -1!

The third point is that C uses int values for storing logical values: 0 means false and all other values mean true. In PL/I a bit string of length one is used to hold logical values. Thus there is only one set of logical operators in PL/I whereas C has two:

C PL/I if (x > 0 && j == 2) ... b = k < 0 & j == 1; if K > 0 & J = 2 then ... B = K > 0 & J = 2;

As you can see, C needs three concepts where PL/I has only one!

Static, Automatic, and more ...

As static and automatic are very similar in C and PL/I, there is one storage class you can find only in C: register . It expresses your wish to hold a variable in a register. There are two storage classes which can be found only in PL/I: controlled and based . Let's first consider an example of controlled :

PL/I declare (K, L) fixed bin, A dim (K) char (L) ctl; K = 10; L = 10; allocate A; ... K = 15; L = 29; allocate A; ... put (A); free A; put (A); free A;

The controlled (abbrev. ctl ) means that the variable initially has no storage allocated to it. Only after executing an allocate statement can you make a legal reference to it. After executing a corresponding free statement, the controlled variable is no longer available. A powerful use of this type of storage class is to make multiple allocations of a variable before freeing it. PL/I treats this as building a stack. Older generations of the variable are hidden in the meantime, and only the most recent allocation of the variable is available to the program. Even more remarkable is that array bounds and string sizes may be different in different generations.

Say It in the Declaration!

The PL/I attribute based can best be explained when defining chained (or "linked") structures. Look at the following example:

C PL/I struct atype {struct btype *a1; float a2;}; struct btype {long int b1; float b2 [10];}; struct atype *a; a = (struct atype *) malloc (sizeof(struct atype)); a->a1 = (struct btype *) malloc (sizeof(struct btype)); printf ("%f", a->a1>b2[3]); dcl 1 A based (P); 2 A1 ptr, 2 A2 float; dcl 1 B based (A1), 2 B1 fixed bin (31), 2 B2 float dim (10); allocate A; allocate B; put (B2(3));

In PL/I you find complexity only in the declaration: A is where P points to, B is where A.A1 points to. After this you can forget the pointers and use only the object names A and B . In C, however, you have to use the pointers everywhere. Pointers are indeed typed in C, but even for the purpose of allocation you have to cast them to byte pointers!

How Many Must a Programming Language Have?

If you compare the following tables, what do you think? Is it really necessary to have 45 operators instead of 20? Is it really necessary to have 15 priority groups instead of 7?

C priority direction operator 15 < () [] . -> 14 > ++ -- ˜ - + ! & * (typename) sizeof() 13 > * / % 12 > + - 11 > << >> 10 > < > <= >= 9 > == != 8 > & 7 > ^ 6 > | 5 > &.&. 4 > || 3 < ?: 2 < = += -= *= /= <<= %= &= ^= |= >>= 1 > ,

PL/I priority direction Operator 7 < + - ^ ** 6 > * / 5 > + - 4 > = ^= < > <= >= ^< ^> 3 > || 2 > & 1 > | ^

Even == and >= are in different precedence groups! ** and || of PL/I cannot to be found in C, and C's shift operators (the << and >> operators) are handled by builtin functions in PL/I.

Something to Do with Mathematics?

Many operations in C are done after the so-called usual conversions. So, what is done in the following C example:

C signed char c = 0xFF; if (c == 0xFF) ...