我目前正在尝试编写一些Lil文字游戏机,以在C中获得乐趣。
为此,我需要能够在...很好... C中打印类似窗口的结构。
我想使用通用的渲染方法(称之为frame_render(...)
)来渲染所有不同的“ ui元素”
现在的问题是:如何解决这个问题?
鉴于这种情况:
// Theoretical base
struct frame { int x; int y; int width; int height; }
struct button { int x; int y; int width; int height; ... }
struct whatever { int x; int y; int width; int height; ... }
如何确保我的x
,y
,width
和height
始终位于正确的位置内存中?
刚开始时以“仅”顺序排列它们就足够了吗?
还,如何设计方法标题以接受它?
答案 0 :(得分:5)
如果所有结构都以相同类型的成员开始,并且顺序相同,则对应的成员将具有相同的偏移量。大多数编译器都可以配置为允许使用任何结构类型的指针来检查其他任何类型的Common Initial Sequence的成员,但是存在一些问题:
在某些不常见的平台上,如果对象后跟填充字节,则将对象和填充字节写在一起的指令(可能在后者中存储无意义的值)可能比仅写对象的指令更快。如果某个成员在某些结构中后跟填充字节,但在另一结构中后跟有意义的数据,则使用在其后跟填充字节的类型对该成员进行写操作可能会写入无意义的值来覆盖“填充字节”,从而破坏这些值其他结构类型中的以下成员。我不知道当前使用的任何体系结构,这对于除位域之外的任何结构成员都是一个问题,并且我不知道当前的实现,即使对于那些结构,这也将是一个问题,但是在某些平台上可能会出现这种情况,尤其是对于位域。
给出类似的内容:
int readField1OfS1(struct s1 *p) { return p->field1; }
struct s2 *myStruct2Ptr;
if (readField1ofS1((struct s1*)myStruct2Ptr)
...
某些编译器(例如gcc和clang)无法可靠地考虑到,从函数返回的值可能取决于当时struct s2
类型的对象的Common Initial Sequence部分所保存的值除非已禁用优化(例如使用-fno-strict-aliasing
选项),否则将不进行呼叫。我认为函数调用表达式中从struct s2*
到struct s1*
的强制转换应该使质量编译器认识到,任何函数都可能对类型为struct s1
的对象执行某些操作尽管在struct s2
上完成了该操作,但由于该标准没有明确要求,因此gcc和clang的作者拒绝做出任何努力来可靠地识别此类构造,即使在上述简单情况下也是如此。
使用“公共初始序列”规则的代码几乎可以在任何经过适当配置的编译器上可靠地工作,但是某些 gcc和clang必须使用-fno-strict-aliasing
选项进行特殊配置。自1974年以来,利用Common Initial Sequence保证的能力一直是该语言的完善部分,而当编写该标准时,熟悉该语言的任何人都将了解到,该语言被设计为允许上述构造,这些编译器应该没有困难的认识。由于该标准的作者未能明确要求以有用的方式兑现CIS保证,因此,clang和gcc的作者决定,他们宁愿声称依赖数十年的CIS保证的程序“被破坏”,而不是拥有40多年的先例。
答案 1 :(得分:3)
刚开始时以“仅”顺序排列它们就足够了吗?
是的,如果您像上面那样谨慎的话。
还,如何设计方法标题以接受它?
有不同的方法可以做到这一点。
下面是一个示例,它使用c++
“基本”类的[ugly]等效项:
enum type {
FRAME,
BUTTON,
WHATEVER
};
struct geo {
int x;
int y;
int width;
int height;
enum type type;
};
struct frame {
struct geo geo;
};
struct button {
struct geo geo;
int updown;
};
struct whatever {
struct geo geo;
int what;
int ever;
};
void
frame_render(struct geo *geo)
{
struct frame *frm;
struct button *but;
struct whatever *what;
switch (geo->type) {
case FRAME:
frm = (struct frame *) geo;
frame_render_frame(frm);
break;
case BUTTON:
but = (struct button *) geo;
printf("x=%d y=%d updown=%d\n",geo->x,geo->y,but->updown);
frame_render_button(but);
break;
case WHATEVER:
what = (struct whatever *) geo;
printf("x=%d y=%d what=%d ever=%d\n",
what->geo.x,what->geo.y,what->what,what->ever);
frame_render_whatever(what);
break;
}
}
这是使用虚函数表的一种方法:
enum type {
FRAME,
BUTTON,
WHATEVER
};
struct geo;
// virtual function table
struct virtfnc {
void (*calc)(struct geo *);
void (*render)(struct geo *);
};
struct geo {
int x;
int y;
int width;
int height;
enum type type;
struct virtfnc *fnc;
};
struct frame {
struct geo geo;
};
struct button {
struct geo geo;
int updown;
};
struct whatever {
struct geo geo;
int what;
int ever;
};
void
frame_render(struct geo *geo)
{
struct frame *frm = (struct frame *) geo;
// whatever
}
void
button_render(struct geo *geo)
{
struct button *but = (struct button *) geo;
// whatever
}
void
whatever_render(struct geo *geo)
{
struct whatever *what = (struct whatever *) geo;
// whatever
}
void
any_render(struct geo *geo)
{
geo->fnc->render(geo);
}
这是使用union
的第三种方式。它比较简单,但是要求基本结构必须与最大的子类一样大:
enum type {
FRAME,
BUTTON,
WHATEVER
};
struct frame {
...
};
struct button {
int updown;
};
struct whatever {
int what;
int ever;
};
struct geo {
int x;
int y;
int width;
int height;
enum type type;
union {
struct frame frame;
struct button button;
struct whatever what;
} data;
};
void
any_render(struct geo *geo)
{
switch (geo->type) {
case FRAME:
render_frame(&geo->data.frame);
break;
case BUTTON:
render_button(&geo->data.button);
break;
case WHATEVER:
render_whatever(&geo->data.what);
break;
}
}
更新:
这种方法投放安全吗?例如。将所有内容放入类型为
frame*
的某个数组中,然后仅访问frame->geo
?还是对以后调用free(..)
造成任何问题?
如果分配使用派生的类型(例如free
,frame
)进行分配,而不是,则button
没问题基本类型geo
:malloc(sizeof(struct button))
。
要具有一个[形状]的简单数组,需要使用union
方法(即,所有派生的结构必须具有相同的大小)。但是,如果我们有一些子类型使用了比其他空间更多的 lot 空间,这将是浪费的:
struct polyline {
int num_points;
int x[100];
int y[100];
};
仍然可以使用方法#1或#2 [其中子类型结构的大小不同]和间接指针数组来完成 :
void
all_render(struct geo **glist,int ngeo)
{
for (; ngeo > 0; --ngeo, ++glist)
any_render(*glist);
}
我会考虑使用一个[双重]链接列表,而不是使用不同形状的数组。这允许子类型结构具有不同的大小。我们将向struct geo *next
添加一个struct geo
元素。然后,我们可以这样做:
void
all_render(struct geo *geo)
{
for (; geo != NULL; geo = geo->next)
any_render(geo);
}
列表方法可能更可取,尤其是当我们动态添加/删除形状(或基于Z深度对它们重新排序)时。
或者,某些形状可能包含其他形状。因此,我们可以将struct geo *children
添加到struct geo
中。然后,很容易(例如)绘制一个包含框,然后遍历children
列表,然后绘制该框中的所有形状。如果我们选择children
,也可以添加struct parent *parent
,以便每个形状都知道包含它的形状。
答案 2 :(得分:2)
“经典”方法是使一个struct
包含所有可能对象的union
和一个enum
来标识已确切传递了哪个对象:
struct renderable {
enum {FRAME, BUTTON, WHATVERE, ...} type;
union {
struct frame frame;
struct button button;
struct whatever whatever;
....
} data;
};
将此结构传递给渲染器后,在switch
字段上使用type
提取坐标,等等:
void renderer (renderable *obj, ...) {
...
switch(obj->type) {
case FRAME: x = obj->data.frame.x; ...; break;
case BUTTON: x = obj->data.button.x; ...; break;
...
}
...
}
据报道,这种怪异行为促使Stroustrup发明了C ++:)
已编辑
另一种“经典”解决方案是拥有一个单独的struct
,该{@ 1}可以具有任何对象的尺寸和位置:
struct geometry {
int x, y, width, height;
}
您可以将此结构存储在任何特定于对象的struct
的开头,并使用强制转换来访问它:
struct frame {
struct geometry geo;
// more stuff
};
struct frame frame = {....};
rendered((void*)&frame, ...);
在渲染器中:
void renderer (void *obj, ...) {
...
struct geometry *geo = (struct geometry *)obj;
geo->x ...
}
后一种方法可能有些不安全。为了使其100%安全,请将几何图形与特定于对象的信息分开,然后将它们作为两个单独的结构传递给渲染器。