通过优化,使用C,gcc,C99和宏改进微控制器的简约OOP

And*_*ose 9 c oop gcc pointers

我经常用C编程微控制器,因为C++编译器通常不可用,或者由于各种错误而无法制作极小的代码.但是,为了便于维护,OOP"语法糖"通常非常方便,因为它可以更加清晰地封装硬件程序.所以我想知道是否有办法在C中进行OOP语法,尽可能使OOP开销(在不需要时)以可移植的方式进行优化.例如:这将针对不同微控制器针对gcc进行优化,或者如果gcc不适用于该微控制器,则可能使用gcc的预处理器和通用ANSI-C编译器.

我发现只有线程,像这样,在C中进行OOP时,优雅的方式来模拟'this'指针?通常通过将指针嵌入到结构中来执行OOP,但这并不总是我想要的,因为当我对虚拟方法不感兴趣时​​,它会浪费内存,或类似的东西.我总是可以在需要这些功能的链接中遵循编码风格,但我想开发不需要它们的技术; 例如,我只是希望能够使用OOP范例进行编程,使用简单易懂的代码(不一定是C++,尽管我喜欢C++),并且当一些OOP范例不使用时仍然能够实现最小的C程序内存使用.

因此,我使用gcc和C99进行实验,因为一般来说gcc 3.2或更高版本适用于大多数平台; 并意识到我可以使用C99中的sizeof()和typeof()编译器函数自动索引类(来自未使用/未初始化的联合成员的"技巧")(所以类必须是带有子结构的联合),命令访问由宏创建的编译时常量查找表,它可以绑定数据和方法,并保证所有类型检查.等等

例如:GCC允许优化const结构和数组,当它们的成员只作为常量表达式访问时,所以我想我可以用它来构建一个基于宏的编译时绑定系统,其中处理OOP开销GCC实际上优化了最终的二进制文件.

使用这个系统,我现在可以进行可变宏方法调用,例如:M(a,init,"with","any","parameters",7)查找变量a的类型,调用方法init,使用变量number参数...

请参阅下面的代码示例,并尝试它们 - 它比解释简单:使用g​​cc -E查看宏扩展,并注意仅ANSI编译器,typeof()运算符必须替换为(void*)类型转换; 类型检查仅适用于GCC.

代码可以剪切并粘贴到文本编辑器中,文件名在第一行,它可以在普通的PC系统上编译和运行.

虽然我确实成功地摆脱了每个结构中的单个指针以"指回"一个类的方法列表,这可以节省有限内存微控制器中的内存,但我还是无法弄清楚如何让编译器进行优化out unused指针,因为我必须使用(void*)指针将类保存在数组中,这些指针需要一个内存地址(结构地址)和一个链接器实例; 并且不要优化.

所以:我想知道是否有人知道通过制作某种初始化的方法结构来改进我的解决方案的方法,该结构将在编译后优化(没有链接器地址),例如:当它的成员仅作为常量表达式访问时码.本质上,我需要能够在数组中查找元素,其中每个数组元素的初始化部分是不同的classXXX_mt,而不是所有类型为(void*)的classXXX_mt的地址列表.

如果有人能想到一个简单的解决方案,还有其他两个我需要帮助的改进; cpp(c-pre-processor)不允许通过标记连接从前一个宏中定义新的宏(据我所知),所以我必须制作固定长度的宏列表(在我的例子中最多10个) )持有类定义; 这意味着我在一个程序中最多只能有10个班级; 但理想情况下,我想要一种方法来使我的代码更通用,以便cpp可以动态创建可变长度列表.例如:问题与c预处理器无法自动"计数"有关.

其次,当我尝试为更新版本的GCC使用匿名结构时,我可能会删除访问ISO-C中的成员数据所需的额外"m",例如:foo.m.mydata,删除"m"从类联合定义中命名,并使用gcc -std = c11进行编译,然后它只是给了我错误声称结构定义没有...所以,即使在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
}
Run Code Online (Sandbox Code Playgroud)

接下来是class1和class2的类定义/头文件,显示宏预处理器如何用于创建绑定到方法和_ct类型的数据类; 通常这可能会被分解为两个头文件和两个库; 但我只是简单地将所有代码放在一起来滥用标题.

//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
Run Code Online (Sandbox Code Playgroud)

最后,来自voidbind.h这是系统的核心,让CPP编写一个编译时常量列表,指向方法结构的void*指针......只要传入的所有内容都是编译时间常数.(但列表中的结构将不会完全优化.:(即使常数.)

为了实现这个想法,我不得不想办法让cpp计算voidbind头文件#included的次数,以便自动生成类指针列表,并且因为宏预处理器不能做加法,或者定义基于同一宏名称的先前定义而改变的宏; 我不得不使用内联函数来"保存"从一个传递到下一个传递的类方法struct(_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 //#}
Run Code Online (Sandbox Code Playgroud)

Ale*_*lex 5

一般来说,你要求的是C++.您发布的示例最有可能是使用C++编译器更高效或同等效率.

通常在嵌入式目标上,你有过时的版本,gcc它们会为c ++生成错误的代码,或者不支持所有的cory c ++细节.

您可以尝试运行${your_arch_prefix}-g++ --nostdlib --nostdinc,这将在解析器中启用c ++语法,而不会浪费任何空间.如果要禁用其他内容,可以添加-fno-rtti -fno-exceptions删除运行时类型检查和异常支持(请参阅此问题).

由于C++解析器是C前端的一部分,即使您的微控制器供应商没有正式支持C++,这可能仍然有用(有时您也可以尝试自己编译供应商特定的版本并添加c ++到配置脚本中的语言).

这通常被认为优于尝试发明自己的OOP,如宏DSL(领域特定语言).

如果您不想走这条路并且不想使用手工制作的vtable(如链接中)那就是这么说的.最简单的事情是编码约定.如果你不想要多态,下面的代码就足够了.您可以在.c文件中定义结构和函数,并将声明放在标题中.下面的函数可以直接调用,因此它不在vtable中,第一个成员是thisc ++中的指针.struct impl是对象持有的实际数据,而不是vtable或类似的数据.

struct impl;
struct impl *make_impl();
// don't use this as it is a reserved keyword in c++
void do_bar(struct impl *myThis, int bar);
Run Code Online (Sandbox Code Playgroud)

如果你想要多态,看一下内核的作用.他们明确地将vtable嵌入到对象中并使用宏来提取它们并初始化它们.

例如,查看char设备的定义.

并看看人们如何在代码标题中实现这一点.查看container_of宏并了解media_entity_to_video_device转换的工作原理.(如果您的上下文太少,请查看本书:Linux设备驱动程序(LDD3)).

我知道您的代码有效,您应该自豪地了解自己在做什么.但是,如果您向其他人展示您的代码,他们希望您编写C或C++.如果您在C中并且缺少OOP,我会尝试以某种方式编写代码,以便其他人可以轻松掌握您正在做的事情.使用宏来提取函数指针或获取多态成员通常很好,隐藏函数调用并在宏中生成结构通常是不可读的,人们必须在运行时调试代码,gcc -E以查看从预处理器扩展的创建,以了解它们实际调用的内容.

编辑

我已经非常快速地从clang ++生成C代码.根据这个问题,这个命令应该是:

$ clang++ -std=c++11 -S -emit-llvm -o out main.cc # Worked
$ llc -march=c out 
llc: error: invalid target 'c'.

 $ clang++ --version
 clang version 3.7.0 (trunk 232670)
Target: x86_64-unknown-linux-gnu
Thread model: posix
Run Code Online (Sandbox Code Playgroud)

似乎clang C后端已被删除(另请参阅这些来源重新启动C-backend代码).话虽这么说你也可以看看为你的目标平台生成一个后端,但我认为这肯定是过度设计的.