c - 通过优化使用C,gcc,C99和Macros改善微 Controller 的简约OOP

标签 c oop pointers gcc

我经常不得不用C语言对微控制器进行编程,因为C ++编译器通常不可用,或者由于各种错误而无法编写非常小的代码。但是,在使硬件程序更清晰地封装以便于维护时,OOP通常很方便。因此,我想找出是否有一种方法可以在C语言中执行OOP语法,在该方法中,可以尽可能多地使用OOP开销(不需要时)来以可移植的方式进行优化。例如:如果针对不同微控制器的gcc进行了优化,或者如果gcc对于该微控制器不可用,则可以使用gcc的预处理器和通用ANSI-C编译器进行优化。

我发现只有这样的线程,Elegant way to emulate 'this' pointer when doing OOP in C?通常通过将指针嵌入结构中来进行OOP,但这并不总是我想要的,因为当我对虚拟方法或类似方法不感兴趣时​​,它会浪费内存。我总是可以在需要这些功能的链接中遵循编码风格,但是我想开发不需要这些功能的技术。例如我只希望能够使用简单易懂的代码(虽然我喜欢C ++,不一定是C ++)使用OOP范例进行编程,并且在不使用某些OOP范例时仍能够实现最少的C程序内存使用。

因此,我求助于gcc和C99,因为一般而言,大多数平台都可以使用gcc 3.2或更高版本。并意识到我可以使用C99中的sizeof()和typeof()编译器函数从一个未使用/未初始化的联合成员(因此,类必须是具有子结构的联合)中自动索引类(一种“技巧”)。为了访问由宏创建的编译时常数查找表,该表可以绑定数据和方法,并保证所有类型检查。等等等等等等

例如:当仅以常量表达式形式访问其成员时,GCC允许对const结构和数组进行优化,因此我认为我可能能够使用它来构建基于宏的编译时绑定系统,从而在其中处理OOP开销GCC并实际上从最终二进制文件中进行了优化。

有了这个系统,我现在可以执行可变参数宏方法调用,例如:M(a,init,“ with”,“ any”,“ parameters ,, 7),它使用变量的数量查找变量a的类型,调用方法init。参数...

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

该代码可剪切并粘贴到文本编辑器中,第一行带有文件名,并且可以在常规PC系统上编译并运行。

尽管我确实成功地摆脱了每个结构中的各个指针,以“返回”类的方法列表,从而在有限的内存微控制器中节省了内存,但我仍然无法弄清楚如何让编译器进行优化淘汰未使用的方法指针,因为我必须为类使用(void *)指针才能将它们保存在数组中,并且这些指针需要内存地址(结构的地址)和链接器实例;并且不要优化。

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

如果有人能想到一个简单的解决方案,我还希望提供另外两项改进; cpp(c预处理器)不允许通过令牌级联从前一个宏中定义新宏(据我所知),因此我必须制作固定长度的宏列表(本例中最多为10个) )保存类的定义;这意味着一个程序中最多只能有10个课程;但理想情况下,我希望有一种使我的代码更通用的方法,以便cpp可以动态创建可变长度列表。例如:问题与c预处理器无法自动“计数”有关。

其次,当我尝试对较新版本的GCC使用匿名结构时,我可能会删除“ m”来摆脱访问ISO-C中的成员数据所需的额外“ m”,例如:foo.m.mydata从类并集定义中命名,并使用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
}


接下来是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


最后,是voidbind.h
这是系统的核心,使CPP生成指向方法结构的void *指针的编译时常数列表...只要传入的所有内容均为编译时常数,void *列表将始终进行优化。 (但是列表中的结构不会完全优化出来。::即使常量。)

为了使这个想法可行,我不得不想出一种方法来使cpp计数voidbind头文件被包含#include的次数,以便自动生成一个类指针列表,并且由于宏预处理器无法进行加法运算,或定义基于相同宏名称的先前定义而更改的宏;我必须使用内联函数从一次传递“保存”指向类方法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 //#}

最佳答案

通常,您要的是C ++。您发布的示例很可能会使用C ++编译器更高效或更有效。

通常,在嵌入式目标上,您已经使用了过时的gcc版本,这些版本会为c ++生成错误代码,或者不支持所有繁琐的c ++详细信息。

您可以尝试运行${your_arch_prefix}-g++ --nostdlib --nostdinc,它将在解析器中启用c ++语法,而不会浪费所有空间。如果要禁用其他功能,则可以添加-fno-rtti -fno-exceptions以及删除运行时类型检查和异常支持(请参见this question)。

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

通常认为,这比尝试发明自己的OOP(例如宏DSL(特定于域的语言))要好。

这就是说,如果您不想走这条路并且不想使用手工vtable(如your link中所述)。最简单的事情是拥有编码约定。如果您不想多态,下面的代码就足够了。您可以在.c文件中定义结构和函数,并将声明放在标头中。下面的函数可以直接调用,因此不在vtable中,并且第一个成员是c ++中的this指针。 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);


如果您想要多态性,请查看内核的功能。他们将vtable明确嵌入对象中,并使用宏提取它们并将其初始化。

例如查看char device的定义。

并查看人们如何在codeheaders中实例化这一点。查看container_of宏,了解media_entity_to_video_device强制转换的工作方式。 (如果这对您来说太少了,请看这本书:Linux Device Drivers (LDD3))。

我知道您的代码有效,您应该为了解自己的工作而感到自豪。但是,如果您将代码展示给其他人,他们希望您编写C或C ++。如果您使用C语言并且缺少OOP,我将尝试以某种方式编写代码,以便其他人可以轻松地掌握您的工作。使用宏提取函数指针或获取多态成员通常是好的,在宏中隐藏函数调用并生成结构通常是不可读的,人们必须在运行gcc -E时调试代码,才能看到您的创建物从预处理器扩展以了解它们的含义。实际上在打电话。

编辑

从clang ++生成C代码方面,我做得很快。
根据this so questionthis one的命令应为:

$ 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


似乎C语言C后端已被删除(另请参见these sources恢复C后端代码)。话虽如此,您也可以看看为目标平台生成后端,但是我认为那肯定是过度设计的。

关于c - 通过优化使用C,gcc,C99和Macros改善微 Controller 的简约OOP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29909665/

相关文章:

c++ - 为什么成员函数地址与自由函数相距如此远?

c - 从文件中读取信息

c - 应用 memset 后结构变量未设置为 0

c# - List<string> 的对象如何添加提供的字符串

java - 在 Java 中存储静态映射

c++ - 在 Linux 上尝试/捕获段错误

c - C中的通用实现

c - sprintf 格式化 : fixed number of characters, 小数位数可变

oop - 为什么是接口(interface)而不是普通类

iphone - Objective-C指针/内存管理问题