【问题标题】:Solving the circular dependency conundrum "elegantly"“优雅”地解决循环依赖难题
【发布时间】:2013-11-04 10:08:25
【问题描述】:

所以我正在开发一种编程语言,它可以编译为字节码以供 VM 执行,也可以编译为 C 作为中间语言,用于编译为本机二进制文件。我选择 C ​​是因为它足够低级且可移植,通过重用现有编译器节省了大量工作量,而不必为每个不同的平台及其奇怪之处编写编译器来汇编。

但现有的编译器也有其缺点,其中之一就是循环依赖问题。我想以一种优雅的方式(与 C/C++ 不同)解决循环依赖关系,无需笨拙的前向声明,无需使用指针和额外的间接和浪费内存,无需将声明与定义等分开......换句话说,像某些编程语言一样,让开发人员远离这个问题。

在我看来,目前的 C/C++ 编译器的主要问题是他们不能“展望未来”,即使它不是真正的未来,因为程序员的意图已经在代码中表达了,我的编译器没有有这个问题,除了解析进度的某个特定点之外,它并不是不知道任何事情,它知道具有循环依赖关系的对象的大小,并且可以计算适当的偏移量等。

我已经实现了“伪造”继承,它只是对继承结构的成员进行“内联扩展”,所以我想我也可以使用相同的方法来实际伪造聚合。在最基本和最简单的例子中:

typedef struct {
    int a;
} A;

typedef struct {
    A a;
    int b;
} B;

变成:

typedef struct {
    int A_a;
    int b;
} B;

编译器会做一些“翻译”:

B b;
b.a.a = 7;

变成:

b.A_a = 7;

以同样的方式,所有结构都被折叠成一个只包含原始类型的根结构。这样,在结构中绝对不会使用预先不知道大小的类型,因此定义的顺序变得无关紧要。自然,这种混乱对用户来说是隐藏的,并且只供编译器的“眼睛看到”,而用户端保持结构化和可读性。不言而喻,但为了与常规 C/C++ 代码兼容,保留了二进制足迹,折叠结构与使用聚合或继承的常规结构是二进制相同的。

所以我的问题是:这听起来像一个好主意吗?我错过了什么可能出错的地方?

编辑:我的目标只是解决循环依赖的 C/C++ 相关困难,而不是“鸡或蛋”逻辑悖论。显然,如果两个对象相互包含而不导致某种形式的无限循环,这是不可能的。

【问题讨论】:

  • 令人印象深刻,但内联扩展有什么真正的“要点”吗?访问b.A_a而不是b.a.a时生成的代码有什么不同吗?我希望它完全一样,所以你做了很多工作来“优化”一些没有太多好处的东西。只是问问。
  • @unwind - 好处是循环依赖变成了一个已经灭绝且无关紧要的问题。
  • 啊,我明白了。如果您的类 C 示例以相反的顺序使用 AB,可能会更清楚。如图所示,C 声明很好,不存在循环问题。
  • @unwind - 代码演示了解决问题的概念,而不是问题本身,仅在文本中进行了解释。我认为人们会阅读问题的正文而不仅仅是源代码;)
  • 但是如果你打算使用BA_* 成员,就好像它是A 一样(即,将其转换为A*),你将在对齐、填充方面遇到无穷无尽的问题之类的。

标签: c++ c inline circular-dependency expansion


【解决方案1】:

你不能安全地使用指向子结构的指针,因为你不能通过指向原始成员来获得指向“兼容类型”的指针。例如。之后

struct Foo {
    short a;
    int b;
};

struct Bar {
    struct Foo foo;
};

struct Bar bar;

指针&bar.foo&bar.foo.a 有不同的类型,不能互换使用。它们也不能在不违反严格的别名规则的情况下转换为彼此的类型,从而触发未定义的行为。

可以通过每次内联整个struct定义来避免该问题:

struct Bar {
    struct { short a; int b; } foo;
};

现在&bar.a 是指向struct {short; int;} 的指针,它是struct Foo 的兼容类型。

struct 类型成员和原始成员之间也可能存在填充/对齐差异,但我找不到这些示例。

【讨论】:

  • 我通过手动插入填充字节而不是手动插入来保持二进制兼容性,但我的编译器这样做是为了符合 C 编译器在聚合结构时所做的事情。
猜你喜欢
  • 2020-11-12
  • 1970-01-01
  • 2017-06-24
  • 1970-01-01
  • 2013-02-04
  • 2010-10-27
相关资源
最近更新 更多