Often I have to program microcontrollers in C, because C++ compilers are often not available, or can not make extremely small code because of various bugs. But often, OOP "syntactic sugar", is very convenient when it comes to making programs for hardware more clearly encapsulated for easy maintenance; so I wanted to find out if there was a way to do OOP syntax in C where as much as possible the OOP overhead (when not needed) could be made to optimize out in a way that is portable. eg: That will optimize with gcc targeted for different microcontrollers, or perhaps by using gcc's preprocessor and a generic ANSI-C compiler if gcc is not available for that microcontroller.
I found only threads, like this, Elegant way to emulate 'this' pointer when doing OOP in C? which generally do OOP by embedding pointers into structs but that's not always what I want because it wastes memory when I'm not interested in virtual methods, or anything like that. I can always follow the coding style in the link where those features are needed, but I want to develop techniques for when they are not needed; e.g. I just want to be able to program using OOP paradigms, with simple easy to understand code (Not necessarily C++, though I like C++), and still be able to achieve a minimal C program memory usage when some OOP paradigms are not in use.
So, I resorted to experimentation with gcc, and C99, because in general gcc 3.2 or above is available for most platforms; and realized that I could use the sizeof() and typeof() compiler functions from C99 to index classes automatically ( a 'trick' of sorts ) from an unused/uninitialized union member (So classes must be unions with sub-structs), in order to access a compile time constant lookup table created by macros, which could bind data and methods, and guarantee all type checking. etc. etc. etc.
eg: GCC allows the optimizing out of const structures, and arrays, when their members are only accessed as constant expressions, so I thought I might be able to use that to build a macro based compile time binding system where the OOP overhead is handled in GCC and actually optimizes out of the final binary.
With this system, I can now do variadic macro method calls, like: M( a , init, "with", "any", "parameters", 7 ) which looks up variable a's type, call method init, using variable number of parameters...
See code examples below, and try them out -- it's simpler than the explanation: Use gcc -E to see the macro expansions, and note for ANSI only compilers, the typeof() operator will have to be replaced by a (void*)typecast; type checking only works with GCC.
The code is cut and paste-able into a text editor, with filename on first line, and it does compile and run on normal PC systems.
Although I did succeed in getting rid of individual pointers in every struct to "point back to" a class's list of methods, which saves memory in a limited memory microcontroller, I wasn't quite able to figure out how to get the compiler to optimize out unused method pointers because I had to use (void*) pointers for the classes to hold them in an array, and those require a memory address (address of a struct) and a linker instance; and don't optimize out.
So: I was wondering if anyone knew of a way to improve my solution by making some kind of an initialized method struct which would optimize out (have no linker address) after compilation, eg: when it's members are only accessed as constant expressions in the code. In essence I'm needing to be able to look up an element in array where the initialized portion of each array element is a different classXXX_mt, rather than a list of addresses to classXXX_mt all typecast to (void*).
There's two other improvements I'd like help with if anyone can think of a simple solution; The cpp (c-pre-processor) doesn't allow defining of new macros from within a previous macro by token concatenation (As far as I know), so I have to make fixed length macro lists (A maximum of 10 in my example) to hold class definitions; which means I can only have a maximum of 10 classes in a program; but ideally, I would like a way to make my code more generic, so that the cpp could create variable length lists on the fly. eg: The problem is related the inability of the c pre-processor to "count" automatically.
And secondly, when I try to use anonymous structs for newer versions of GCC, so I might get rid of an extra 'm' required to access member data in ISO-C eg: foo.m.mydata, by deleting the 'm' name from the class union definition, and compile with gcc -std=c11 , it then simply gave me errors claiming the struct defined nothing... so, anonymous structs inside unions don't work even in GCC 4.8 although it supposed to; how can I get anonymous structs to work?
Below is the example of how I tested and implemented an include file, voidbind.h, which builds a list of classes and statically links the methods to the variables of that class type.
Ultimately, the system allows me to program like this example; which I compiled with gcc 4.0 to 4.9 with no problems:
//classtest.c
#ifndef MACROCHECK // Don't macro expand stdio.h, it's ugly...
#include <stdio.h> // to see macros, do gcc -D MACROCHECK -E classtest.c
#endif
#include "class1.h" // include example class, library.
#define _VOID_FINALIZE
#include "voidbind.h" // Make class list finalized, no more classes allowed
void main( void ) {
class1_ct a; // types ending in _ct are the macro created class types
class2_ct b;
M( a , init ); // Call method of variable, a, and the function init.
printf("a=%s %s\n",a.m.name, M( b, tryme, "echo is this" ) );
// I'd love to be rid of .m. in the previous line using anonymous struct
}
Next is the Class definition / header file, for both class1 and class2, showing how the macro pre-processor is used to create classes of data bound to methods and the _ct type; normally this would probably be broken up into two header files, and two libraries; but I'm just abusing the header by putting all the code together for simplicity.
//class1.h
#ifndef _class1_h
#define _class1_h
// Define the data type structure for class1
typedef struct {
char* name;
int one;
} class1_t;
// Define the method type structure for class1
union class1_ctt ; // class type tag, incomplete tag type for class1_ct
typedef struct { // method prototypes
void (*init)( union class1_ctt* ); // passed a pointer to class1_ct
} class1_mt;
// bind class1_mt and class1_t together into class1_ct
#define _VOID_NEW_CLASS class1
#include "voidbind.h"
// Begin class2 definition
typedef struct { // define data type for class2
int x;
} class2_t;
union class2_ctt ; // class type tag, forward definition
typedef struct { // method prototypes for class2
char* (*tryme)( union class2_ctt*, char* echo );
} class2_mt;
// bind class2_t and class2_mt together into class2_ct
#define _VOID_NEW_CLASS class2
#include "voidbind.h"
// --------------------------------------------- Start library code
// This would normally be a separate file, and linked in
// but as were doing a test, this is in the header instead...
//#include <class1.h>
void class1_init( class1_ct* self ) {
self->m.name = "test";
self->m.one=5;
}
// Define class1's method type (_mt) instance of linker data (_ld):
// voidbind.h when it creates classes, expects an instance of the
// method type (_mt) named with _mt_ld appended to link the prototyped
// methods to C functions. This is the actual "binding" information
// and is the data that I can't get to "optimize out", eg: when there
// is more than one method, and some of them are not used by the program
class1_mt class1_mt_ld = {
.init=class1_init
};
// ----------- CLASS2 libcode ----
char* class2_tryme( class2_ct* self, char* echo ) {
return echo;
}
// class2's method type (_mt) instance of linker data (_ld).
class2_mt class2_mt_ld = { // linker information for method addresses
.tryme=class2_tryme
};
// --------------------------------------------- End of library code
#endif
Finally, comes voidbind.h This is the heart of the system, Getting the CPP to make a compile time constant list of void* pointers to method structs ... the void* list will always optimize out, as long as everything passed in are compile time constants. (But the structs in the list will not completely optimize out. :( even if constants. )
For this to idea to work, I had to figure out a way to make cpp count how many times the voidbind header file was #included, in order to automatically make a list of class pointers, and since the macro preprocessor can not do addition, or define macros which change based on a previous definition of the same macro name; I had to use inline functions to "save" the pointer to the class method struct (_mt) from one pass to the next. That's what forces me to basically use void* pointers, though it might be solvable in another way.
// voidbind.h
// A way to build compile time void pointer arrays
// These arrays are lists of constants that are only important at compile
// time and which "go away" once the compilation is finished (eg:static bind).
// Example code written by: Andrew F. Robinson of Scappoose
#ifdef _VOID_WAS_FINALIZED //#{
#error voidbind_h was included twice after a _VOID_FINALIZE was defined
#endif //#}
// _VOID_FINALIZE, define only after all class headers have been included.
// It will simplify the macro expansion output, and minimize the memory impact
// of an optimization failure or disabling of the optimization in a bad compiler
// in hopes of making the program still work.
#ifdef _VOID_FINALIZE //#{
#define _VOID_WAS_FINALIZED
#undef _VOID_BIND
static inline void* _VOID_BIND( int x ) {
return _VOID_BIND_OBJ[ x ];
}
#else
// Make sure this file has data predefined for binding before being
// included, or else error out so the user knows it's missing a define.
#if ! defined( _VOID_NEW_OBJ ) && ! defined( _VOID_NEW_CLASS ) //#{
#error missing a define of _VOID_NEW_OBJ or _VOID_NEW_CLASS
#endif //#}
// Initialize a macro (once) to count the number of times this file
// has been included; eg: since one object is to be added to the void
// list each time this file is #included. ( _VOID_OBJn )
#ifndef _VOID_OBJn //#{
#define _VOID_OBJn _ERROR_VOID_OBJn_NOT_INITIALIZED_
// Initialize, once, macros to do name concatenations
#define __VOID_CAT( x, y ) x ## y
#define _VOID_CAT( x, y ) __VOID_CAT( x , y )
// Initialize, once, the empty void* list of pointers for classes, objs.
#define _VOID_BIND_OBJ (void* []){\
_VOID_OBJ0() , _VOID_OBJ1() , _VOID_OBJ2() , _VOID_OBJ3() , _VOID_OBJ4()\
, _VOID_OBJ5() , _VOID_OBJ6() , _VOID_OBJ7() , _VOID_OBJ8() , _VOID_OBJ9()\
}
// Define a function macro to return the list, so it can be easily
// replaced by a _FINALIZED inline() function, later
#define _VOID_BIND(x) _VOID_BIND_OBJ[ x ]
// All void pointers are initially null macros. So the void list is 0.
#define _VOID_OBJ0() 0
#define _VOID_OBJ1() 0
#define _VOID_OBJ2() 0
#define _VOID_OBJ3() 0
#define _VOID_OBJ4() 0
#define _VOID_OBJ5() 0
#define _VOID_OBJ6() 0
#define _VOID_OBJ7() 0
#define _VOID_OBJ8() 0
#define _VOID_OBJ9() 0
#endif //#}
// Figure out how many times this macro has been called, by
// checking for how many _VOID_OBJn() function macros have been
// replaced by inline functions
#undef _VOID_OBJn
#if defined( _VOID_OBJ0 ) // #{
#undef _VOID_OBJ0
#define _VOID_OBJn 0
#elif defined( _VOID_OBJ1 )
#undef _VOID_OBJ1
#define _VOID_OBJn 1
#elif defined( _VOID_OBJ2 )
#undef _VOID_OBJ2
#define _VOID_OBJn 2
#elif defined( _VOID_OBJ3 )
#undef _VOID_OBJ3
#define _VOID_OBJn 3
#elif defined( _VOID_OBJ4 )
#undef _VOID_OBJ4
#define _VOID_OBJn 4
#elif defined( _VOID_OBJ5 )
#undef _VOID_OBJ5
#define _VOID_OBJn 5
#elif defined( _VOID_OBJ6 )
#undef _VOID_OBJ6
#define _VOID_OBJn 6
#elif defined( _VOID_OBJ7 )
#undef _VOID_OBJ7
#define _VOID_OBJn 7
#elif defined( _VOID_OBJ8 )
#undef _VOID_OBJ8
#define _VOID_OBJn 8
#elif defined( _VOID_OBJ9 )
#undef _VOID_OBJ9
#define _VOID_OBJn 9
#else
#error Attempted to define more than ten objects
#endif //#}
// -------------------------------------------------------
// If the user defines _VOID_NEW_CLASS
// Create a union of the two class structs, xxx_t and xxx_mt
// and call it xxx_ct. It must also be compatible with xxx_ctt, the tag
// which allows forward definitions in the class headers.
#ifdef _VOID_NEW_CLASS //#{
#ifndef M //#{
#define M( var , method , ... )\
(( (typeof(var._VOIDBIND_T))_VOID_BIND( sizeof(*(var._VOIDBIND)) ) )->\
method( & var , ## __VA_ARGS__ ))
#endif //#}
extern _VOID_CAT( _VOID_NEW_CLASS , _mt ) _VOID_CAT( _VOID_NEW_CLASS , _mt_ld );
typedef union _VOID_CAT( _VOID_NEW_CLASS, _ctt ) {
char (*_VOIDBIND)[ _VOID_OBJn ];
_VOID_CAT( _VOID_NEW_CLASS , _mt ) *_VOIDBIND_T;
_VOID_CAT( _VOID_NEW_CLASS , _t ) m ;
} _VOID_CAT( _VOID_NEW_CLASS , _ct );
static inline void* (_VOID_CAT( _VOID_OBJ , _VOID_OBJn )) ( void ) {
return & _VOID_CAT( _VOID_NEW_CLASS, _mt_ld );
}
#undef _VOID_NEW_CLASS
#else // ---------- Otherwise, just bind whatever object was passed in
static inline _VOID_CAT( _VOID_OBJ , _VOID_OBJn ) (void) {
return (void*) & _VOID_NEW_OBJ ;
}
#undef _VOID_NEW_OBJ
#endif //#}
// End of Macros to define a list of pointers to class method structures
// and to bind data types to method types.
#endif //#}