You are viewing paulmck

Previous Entry | Next Entry

elephant, penguin
I first encountered the C language about three decades ago, and I must confess that my first impressions were not particularly positive. Part of my early dissatisfaction was due to the fact that I was using 8-bit microprocessors and that early C compilers had a bad habit of promoting char to int in the generated object code, regardless of whether this made any particular sense. For example, if x and y were of type char, and were to be added with the sum placed in z, then the assembly output would execute something resembling the following steps:

  1. Load x into an eight-bit register, which on the 8080 or z80 would normally be the a register.
  2. Move the a register to the lower half of a sixteen-bit register, for example, the l portion of the hl register.
  3. Test the sign bit of the a register.
  4. Using one of a number of obscure instruction sequences, fill the upper half of the sixteen-bit register (in this example, h) with eight copies of this sign bit.
  5. Repeat the above steps to get a sign-extended copy of y into (say) the de register.
  6. Move l to a.
  7. Add e to a (which sets the carry bit in the condition-code register appropriately).
  8. Move a to (say) c.
  9. Repeat the preceding three steps to add (with carry) h to d, placing the result in b.
  10. Move b to a, never mind that this is where we just got b from.
  11. Store a into z.

This sort of assembly code sequence was simply not what you wanted to see when attempting to jam 50,000 lines of source code into a 64KB address space, even when given a full 256K of physical memory and an overlay loader hacked up to do bank switching. Other long-dead and unlamented languages were able to generate a pair of loads, an add, and a store, which did much to limit C's uptake on 8-bit platforms. Of course, these other languages had their own charming limitations, such as lacking any variable-initialization syntax (use assembly instead!), insanely inefficient for loops, strange restrictions on their equivalent of union, and much else besides.

After all, the compiler had to fit in 48K, since the operating system (such as it was) consumed the remaining 16K. (My application dispensed with the operating system, and hence could use the entire 64K.)

The past three decades have seen C grow up to be almost unrecognizable, which, believe me, is a very good thing. This growth allows us to enjoy a huge number of language features, including:

  1. identifiers longer than eight characters (and longer than seven for extern identifiers).
  2. type-checked function arguments and return values.
  3. the deprecation of the gets() function.
  4. 32-bit and 64-bit integers.
  5. inline functions.
  6. standard printf() formats (yes, these really did vary from compiler to compiler).

But one feature that I hadn't noticed until recently is the empty structure declaration, which Peter Zijlstra recently introduced me to in the form of the non-lockdep definition of struct lock_class_key. A recently detected hang in RCU under extreme conditions caused me to want an array of struct lock_class_key, which of course turns into an array of empty structures in non-lockdep builds.

To my surprise, this actually works, at least in gcc:

#include <stdio.h>

struct foo {};

struct foo a[10];

int main(int argc, char *argv[])
{
        printf("%p %p\n", &a[0], &a[1]);
        return 0;
}

This generates an object module whose bss and data really are of zero size:

   text    data     bss     dec     hex filename
     66       0       0      66      42 arrayemptystruct.o

And different elements of the array really do have identical addresses:

./arrayemptystruct 
0x8049588 0x8049588

The C language certainly has come a long way in the past three decades. And yes, after all these years, I am still easily amused.

Comments

paulmck
Oct. 17th, 2009 01:54 pm (UTC)
Re: C int promotion
Gah... That should have been "zero-filled load" (movzbl) rather than "sign-extended load". And the variables being added are in fact signed, so the compiler really is optimizing away the promotion.