我只发现了像这样的线程,Elegant way to emulate 'this' pointer when doing OOP in C? 它通常通过在结构体中嵌入指针来执行OOP,但这并不总是我想要的,因为当我不关心虚拟方法或其他类似功能时,它会浪费内存。我总是可以按照链接中需要这些功能的编码风格进行操作,但我想开发一些技术,以便在不需要这些功能时使用OOP范例进行编程,使用简单易懂的代码(不一定是C ++),并仍能实现最小的C程序内存使用情况。
因此,我开始尝试使用gcc和C99进行实验,因为通常大多数平台都可以使用gcc 3.2或更高版本。我意识到我可以使用C99的sizeof()和typeof()编译器函数自动索引类(一种“技巧”),从未使用/未初始化的联合成员中访问由宏创建的编译时常量查找表,该表可以绑定数据和方法,并保证所有类型检查等等。
例如:GCC允许在其成员仅被访问为常量表达式时优化其常量结构和数组,因此我认为我可能可以使用基于宏的编译时绑定系统来构建一个OOP开销在GCC中处理并实际优化出最终二进制文件的系统。
使用此系统,我现在可以执行可变参数宏方法调用,例如:M(a, init, "with", "any", "parameters", 7),它查找变量a的类型,调用init方法,并使用可变数量的参数……
请参见下面的代码示例,并尝试它们-这比解释更简单:使用gcc -E查看宏扩展,并注意对于仅限ANSI的编译器,typeof()运算符必须替换为(void*)类型转换;类型检查仅适用于GCC。
代码可剪切粘贴到文本编辑器中,第一行为文件名,并且在普通PC系统上可以编译和运行。
虽然我成功地消除了每个结构体中指向类方法列表的单个指针,这在有限内存微控制器中节省了内存,但我无法弄清如何让编译器优化掉未使用的方法指针,因为我必须使用(void*)指针将类保存在数组中,而这些需要一个内存地址(结构体的地址)和一个链接实例,不能优化掉。
所以:我想知道是否有人知道通过创建某种“初始化”的方法结构体来改进我的解决方案,该结构体在编译后进行优化(没有链接器地址),例如:当其成员仅作为代码中的常量表达式访问时。 实质上,我需要能够查找数组中的元素,其中每个数组元素的初始化部分都是不同的classXXX_mt,而不是所有类型转换为(void *)的classXXX_mt地址列表。
如果有人能想到简单的解决方案,我还希望获得另外两个改进的帮助; cpp(c预处理器)不允许通过标记连接从先前的宏中定义新宏(据我所知),因此我必须制作固定长度的宏列表(在我的示例中最多为10)来保存类定义; 这意味着我在程序中只能有最多10个类; 但是理想情况下,我希望能够使我的代码更加通用,以便cpp可以动态创建可变长度的列表。例如:问题与c预处理器无法自动“计数”有关。
其次,在尝试使用新版本GCC的匿名结构时(因此我可以通过从类联合定义中删除'm'名称并使用gcc -std=c11编译来摆脱访问ISO-C中成员数据所需的额外'm'),它只是给我错误,声称该结构未定义...因此,即使在GCC 4.8中,联合内的匿名结构也无法正常工作,尽管它应该可以; 我如何使匿名结构工作?
以下是我测试和实施包含文件voidbind.h的示例,该文件构建了一个类列表,并将方法静态链接到该类类型的变量。
最终,该系统允许我像这个示例一样编程;我使用gcc 4.0到4.9编译时没有任何问题:
//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
}
下面是class1和class2的类定义/头文件,展示了如何使用宏预处理器创建与方法和_ct类型绑定的数据类;通常情况下,这可能会被分成两个头文件和两个库;但出于简单起见,我只是在头文件中滥用了所有代码。请注意保留HTML标签。
//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
最后要介绍的是voidbind.h。 这是系统的核心,它使得 CPP 能够在编译时生成一个常量列表,其中包含指向方法结构体的 void* 指针……只要传入的所有内容都是编译时常量,void* 列表就会始终进行优化。(但即使是常量,列表中的结构体也不会完全优化掉。:()。
为了使这个想法起作用,我必须想出一种办法,让 CPP 计算 voidbind 头文件被 #include 的次数,以便自动创建一个类指针列表,而由于宏预处理器无法执行加法,或者定义基于同一宏名称的先前定义而更改的宏,所以我不得不使用内联函数将类方法结构体(_mt)的指针从一轮到下一轮“保存”下来。这就是迫使我基本上使用 void* 指针的原因,尽管可能还有其他解决方法。
// 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 //#}