NIEDZIELSKI.COM
 

opaque types in C89

Introduction
  I’ve been fascinated with exploring different object oriented programming techniques in C89 since I started C++ coding a few years back. I only recently concluded my exploration of opaque types. I think they’re interesting but impractical.

Definition of Opaque Type
  A user data type, normally a struct, that presents no public member variable interface.

Example Case
  The following examples were compiled with ‘gcc -std=c89 -Wall’:

/* opaque.h */
#ifndef __OPAQUE_H__
#define __OPAQUE_H__

struct opaque_type;
typedef struct opaque_type opaque_type;
extern const unsigned sizeof_opaque;

void opaque_init(opaque_type * o, unsigned data);
unsigned opaque_data(const opaque_type * o);

#endif

  There’s no implementation in the above header, only declarations. Clients are unaware of implementation, their only interface is provided by the function prototypes.

/* opaque.c */
#include “opaque.h”

struct opaque_type
{
  unsigned data;
};

const unsigned sizeof_opaque = sizeof(opaque_type);

void opaque_init(opaque_type * o, unsigned data)
{
  o->data = data;
}

unsigned opaque_data(const opaque_type * o)
{
  return o->data;
}

  The above source contains the implementation for the opaque type. This implementation has file scope and is unknown at compile time to all other files in the build.

/* main.c */
#include <stdio.h>
#include “opaque.h”

/* not permitted: variable sized type outside function.
unsigned char mem[sizeof_opaque];
*/

opaque_type * o;

int main()
{
  /* not permitted: size unknown.
  opaque_type o;
  */
  unsigned char mem[sizeof_opaque];

  o = (opaque_type *)mem;

  opaque_init(o, 10);
  printf(”%u\n”, opaque_data(o));

  return 0;
}

  The above source shows how a client may interface with the opaque type.
  Now that we see how most implementations may be written, let’s look at a simpler case. Here’s what it would like if we stuffed it all in main.c:

/* main.c */
#include <stdio.h>

/* forward declarations. */
struct opaque_type;
typedef struct opaque_type opaque_type;
extern const unsigned sizeof_opaque;

void opaque_init(opaque_type * o, unsigned data);
unsigned opaque_data(const opaque_type * o);

/* client code. */

/* not permitted: variable sized type outside function.
unsigned char mem[sizeof_opaque];
*/

opaque_type * o;

int main()
{
  /* not permitted: size unknown.
  opaque_type o;
  */
  unsigned char mem[sizeof_opaque];

  o = (opaque_type *)mem;

  opaque_init(o, 10);
  printf(”%u\n”, opaque_data(o));

  return 0;
}

/* opaque implementation. */

struct opaque_type
{
  unsigned data;
};

const unsigned sizeof_opaque = sizeof(opaque_type);

void opaque_init(opaque_type * o, unsigned data)
{
  o->data = data;
}

unsigned opaque_data(const opaque_type * o)
{
  return o->data;
}

Analysis
  The example case demonstrates that it is impractical to allocate opaque types at compile time. Since the opaque_type implementation is unknown in main, the compiler cannot instantiate an object of this type. Hacking around this problem by making an opaque_type * and assigning statically allocated memory works, but you can only make the allocation in function scope. In systems permitting dynamic allocation, this may not be an issue. Even an opaque_ctor() function could be defined.
  This case also shows there’s no partial public / private interfaces possible. It’s all public or all opaque. Consequently, this means that every member variable to be exposed must have an associated getter and setter function. This may cause poor code optimization in our multiple file example since the implementation will be unknown at compile time and the compiler will be unable to eliminate the overhead of a function call. The exception here is if the compiler generates intermediate code in the object files to allow for optimizations in the linking step.

Conclusion
  I like simplicity, I like static allocation, and I’m not a huge fan of universal setters and getters no matter the context. It should come as no surprise that I don’t like opaque types.

Keystrokes Randomly Repeat

  I found a weird bug in Vista when using a Basic Mouse and Apple Keyboard: frequently, my keystrokes repeat. Sometimes a keystroke is repeated about a dozen times, other times the stroke is repeated until another key is pressed.

Environment:
Dell Inspiron E1705
Intel 82801G (ICH7 Family) USB Universal Host Controller
Intel 82801G (ICH7 Family) USB2 Enhanced Host Controller
Apple Wired Keyboard with Numeric Keypad
Microsoft Basic Optical Mouse
Microsoft Windows Vista Business

Reproduction:

  1. Attach the keyboard to a USB port.
  2. Attach the mouse to a USB port.
  3. Open Control Panel-> Personalization-> Window Color and Appearance-> Open Classic Appearance Properties for More Color Options (Appearance Settings).
  4. Select Windows Standard color scheme and apply.
  5. Type for a couple minutes. I find ctrl-z / ctrl-y (undo / redo) iterations reproduces the problem within a 100 strokes or so.

Notes:

  • Attaching the keyboard or mouse to EHCI or UHCI ports doesn’t seem to affect issue.
  • This issue occurred with another keyboard of the same model.

Workarounds:

  • Change the Appearance Settings color scheme to Windows Aero.
  • Detach the mouse.

GNU Make + Python

  I thought it would be interesting to use the Python interpreter for a makefile instead of BASH. This is a one line change via the SHELL variable.
  The combination of Python’s indentation syntax and Make’s command invocation style did not mix well. The problem is identical to that encountered when specifying a Python program on invocation via the -c option. In Make it’s $(SHELL) -c COMMAND.
  In conclusion, it’s certainly possible to use Python and Make together in this way, but I don’t think it’s practical.

  Appended is one of the makefile’s I experimented with. Swap the spaces for tabs if you’re going to try this.

#!/usr/bin/make

SHELL  =  /usr/bin/python

some_make_var := \
  $(shell \
    print ‘ABCD’; \
  )

foo:
  print ‘$(some_make_var)’;
  for c in range(0, 5): \
    print c;
  for d in range(5,6): \
    print d;