【问题标题】:Could C++ or C99 theoretically be compiled to equally-portable C90?C++ 或 C99 理论上可以编译成同样便携的 C90 吗?
【发布时间】:2011-03-18 08:53:54
【问题描述】:

这是一个大问题,所以让我先解决一些问题:

  1. 让我们忽略一些 C++ 功能无法在 C 中实现的事实(例如,支持链接到的任何全局静态对象的 pre-main 初始化)。
  2. 这是一个关于理论上可行的思想实验。请不要写信说这有多难(我知道),或者我应该做 X。这不是一个实际问题,而是一个有趣的理论问题。 :)

问题是:理论上是否有可能将 C++ 或 C99 编译为 C89 并且与原始源代码一样可移植

Cfront 和 Comeau C/C++ 已经将 C++ 编译为 C。但是,据 Comeau 的销售人员称,对于 Comeau,他们生产的 C 不是便携式的。我自己没有使用过 Comeau 编译器,但我推测造成这种情况的原因是:

  1. INT_MAX、offsetof()等宏已经被扩展,它们的扩展是平台特定的。
  2. #ifdef等条件编译已经解决。

我的问题是这些问题是否可能以一种强有力的方式被克服。换句话说,是否可以编写一个完美 C++ 到 C 的编译器(以不支持的 C++ 特性为模)?

诀窍在于,您必须将宏扩展到足以进行可靠解析的程度,然后将它们折叠回未扩展的形式(因此它们再次可移植且独立于平台)。但是否存在根本不可能做到这一点的情况?

任何人都很难断然地说“是的,这是可能的”,但我很想看到任何具体的反例:由于某种深层原因无法以这种方式编译的代码 sn-ps。我对 C++ 和 C99 的反例都感兴趣。

我将从一个粗略的例子开始,只是为了说明我认为反例可能是什么样子。

#ifdef __SSE__
#define OP <
#else
#define OP >
#endif

class Foo {
 public:
  bool operator <(const Foo& other) { return true; }
  bool operator >(const Foo& other) { return false; }
};

bool f() { return Foo() OP Foo(); }

这很棘手,因为OP 的值以及此处生成的方法调用是特定于平台的。但是编译器似乎有可能识别出语句的解析树依赖于宏的值,并将宏的可能性扩展为:

bool f() {
#if __SSE__
   return Foo_operator_lessthan(...);
#else
   return Foo_operator_greaterthan(...);
#endif
}

【问题讨论】:

  • 有可能,但不会很漂亮。特别是 C99 具有 VLA,在 C90 中实现它们的唯一方法是使用 mallocfree,并为每个函数添加额外的不可见 jmp_buf 类型参数以处理代码执行 @987654328 的情况@ out of functions 包含 VLA。

标签: c++ c compiler-construction c-preprocessor


【解决方案1】:

这不仅在理论上可行,而且在实践中也很简单——将 LLVM 与 cbe 目标一起使用。

【讨论】:

  • +1 供参考,但文档建议“C 后端有很多问题,并且没有得到积极维护。不建议依赖它来处理任何严重的事情。”所以我不会认为这是一个解决方案。我也看不出它的输出是否可移植。
  • @Josh Haberman,我在实践中经常使用cbe,生成的代码至少在 arm、x86_32 和 x86_64 之间是可移植的。我遇到的唯一问题(很容易解决)与向量有关。
【解决方案2】:

理论上所有图灵完备的语言都是等价的。

您可以将 C++ 编译为目标代码,然后将其反编译为纯 C 或使用纯 C 编写的解释器。

【讨论】:

  • -1:你的稻草人不满足要求,因为如果你跨平台使用反编译的目标代码,它肯定不会与原始源代码具有相同的语义,因为所有预处理器解析都已经发生。此外,图灵完备性并没有说明什么样的程序转换是可能的。显然,任何可以用 C++ 编写的程序在 C 中都具有等效的功能,但这并不一定意味着可以编写一个算法来进行翻译。
【解决方案3】:

理论上当然可以先将任何东西编译成 C,但这样做并不实际,特别是对于 C++。

对于 Foo 运算符

bool isLess(const struct Foo * left, const struct Foo * right );

作为函数签名。 (如果 C90 不允许 bool 则返回 int 或 char,类似的旧 C 版本不允许 const,请不要使用它)。

虚函数比较棘手,需要函数指针。

struct A
{
   virtual int method( const std::string & str );
};

struct A
{
   int (*method)( struct A*, const struct string *);
};

a.method( "Hello" );


a.method( &a, create_String( "hello" ) ); 
          // and take care of the pointer returned by create_String

【讨论】:

  • “这样做是不切实际的,特别是对于 C++。” - 好吧,OP 明确表示,这不是关于什么是实用,而只是关于它是否可能
【解决方案4】:

有许多细微的差别。例如,考虑以下行:

int i = UINT_MAX;

IIRC,在 C++ 中,这分配了一个实现定义的值。在 C99 和 C89 中,它分配一个实现定义的值,或引发一个实现定义的信号。因此,如果您在 C++ 中看到这一行,则不能直接将其传递给未经修改的 C89 编译器,除非您做出不可移植的假设,即它不会引发信号。

顺便说一句,如果我记错了,想想你自己的例子,即与相对简单的表达式相关的标准差异......

因此,正如“grep”所说,您可以这样做,因为 C89 是一种足够丰富的语言来表达一般计算。基于同样的理由,您可以编写一个发出 Perl 源代码的 C++ 编译器。

不过,根据您的问题,您正在想象编译器将对原始代码进行一组已定义的修改,以使其编译为 C89。事实上,即使对于 C++ 或 C99 中的简单表达式,发出的 C89 可能看起来与原始源代码完全不同。

另外,我忽略了可能标准库的某些部分您无法实现,因为 C89 不提供这些功能,所以您最终会得到一个“编译器”但不是完整的实现。我不知道。正如 dribeas 指出的那样,像 VLA 这样的低级函数存在问题——基本上你不能将 C89“堆栈”用作 C99“堆栈”。相反,您必须从 C89 动态分配内存以用于 C99 源代码中所需的自动变量。

【讨论】:

  • 您的 C++ 到 C 编译器可以始终使用无符号类型并模拟有符号比较。
  • @R..:确实,整数值可以在编译器中被视为既不是有符号也不是无符号,只是“环绕”,并且对于比较和除法,您可以根据它们对它们进行单独的有符号/无符号操作方式。或者它可以使用原始char 存储来存储所有内容,并在此之上实现所有算术运算。这个问题似乎假设 C89、C99 和 C++ 之间的相似性会影响答案,但实际上它们并没有,我只是试图提供一些例子来消除这样一种想法,即解决方法是“调整代码成形”,就像它一样。
  • 嗯,我认为相似之处确实会影响答案。它们是唯一让这种“编译为 C90”的方法有任何高效的希望。如果您处理 longjmp/exception 问题和签名算术问题,我相信其余的“翻译”可以保持相当简单。
【解决方案5】:

一个大问题是例外。可以使用setjmplongjmp 等来模拟它们,但与真正的设备感知展开引擎相比,这总是非常低效。

【讨论】:

    【解决方案6】:

    http://www.comeaucomputing.com

    没有比工作示例更好的可行性证明了。 Comeau 是最符合标准的 c++03 编译器之一,并且支持即将发布的标准的许多功能,但它并不会真正生成二进制代码。它只是将你的 c++ 代码翻译成可以用不同的 C 后端编译的 c 代码。

    至于可移植性,我认为这是不可能的。如果没有编译器特定的扩展,有些功能是无法实现的。想到的第一个例子是 C99 动态数组:int n; int array[n];,它不能在纯 C89 (AFAIK) 中实现,但可以在 alloca 等扩展之上实现。

    【讨论】:

    • "它只是将你的 c++ 代码翻译成可以用不同的 C 后端编译的 c 代码。"这不是真的,至少科莫自己的销售人员是这样认为的。他们写信给我:“所以,简而言之,生成的 C 代码不足,因为它只针对一个平台(其中平台不仅包括 CPU,还包括操作系统、C 编译器等)”
    猜你喜欢
    • 2018-12-08
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多