C ++:二进制级别缺乏标准化


14

为什么ISO / ANSI没有在二进制级别上标准化C ++?C ++存在许多可移植性问题,这仅是由于缺乏二进制级别的标准化。

Don Box写道(引自他的《基本COM》一书,“ COM作为更好的C ++”一章)

C ++和可移植性


一旦决定将C ++类作为DLL分发,就会面临C ++的基本弱点之一,即二进制级别的标准化不足。尽管ISO / ANSI C ++草案工作论文试图对将编译的程序以及运行它们的语义产生的影响进行编码,但它并未尝试标准化C ++的二进制运行时模型。第一次,当客户端尝试从C ++开发环境(而不是用于构建FastString DLL的环境)中链接FastString DLL的导入库时,此问题将首次显现。

还有更多的好处,或者由于缺少二进制标准化而损失了吗?


这是更好地问上programmers.stackexchange.com,看到它是如何的更多的是主观题?
Stephen Furlani 2010年

1
其实我的相关问题:stackoverflow.com/questions/2083060/...
阿拉克

4
Don Box是一个狂热者。忽略他。
John Dibling 2010年

8
嗯,C也没有通过ANSI / ISO在二进制级别上进行标准化。OTOH C具有事实上的标准ABI,而不是法律上的标准。C ++没有这样的标准化ABI,因为不同的制造商对其实现有不同的目标。例如,Windows SEH之上的VC ++附带的异常。POSIX没有SEH,因此采用该模型是没有道理的(因此G ++和MinGW不使用该模型)。
Billy ONeal 2010年

3
我认为这是一个功能,而不是弱点。如果您将实现绑定到特定的ABI,则我们将永远不会有创新,并且新硬件将受限于该语言的设计(并且由于每个新版本之间有15年的间隔,这在硬件行业是很长的时间)创新思想,使代码更有效地执行将不会产生。代价是可执行文件中的所有代码必须由相同的编译器/版本构建(这是一个问题,但不是主要问题)。

Answers:


16

具有二进制兼容编译形式的语言是一个相对较新的阶段[*],例如JVM和.NET运行时。C和C ++编译器通常会发出本机代码。

优点是不需要JIT,字节码解释器,VM或任何其他类似的东西。例如,您不能将在计算机启动时运行的引导程序代码编写为可移植的Java字节码,除非该机器可以本地执行Java字节码,或者您具有从Java到非二进制兼容本机的某种转换器。可执行代码(理论上:不确定在实践中对于引导程序代码是否可以建议这样做)。您可以或多或少地用C ++编写它,尽管即使在源代码级别上也不是可移植的 C ++,因为它会使魔术硬件地址混乱很多。

缺点是,当然本机代码只能运行在所有在它被编译为架构,以及可执行文件只能由了解他们的可执行格式的加载器加载,并且只用了相同的架构链接和呼叫到其他可执行文件 ABI。

即使您走了这么远,将两个可执行文件链接在一起也只有在以下情况下才能正常工作:(a)您没有违反“单一定义规则”,如果使用不同的编译器/选项/其他方式编译它们,这很容易做到,这样他们就使用同一类的不同定义(在标头中,或者因为它们各自针对不同的实现进行静态链接);(b)根据每个编译时有效的编译器选项,所有相关的实现细节(例如结构布局)都是相同的。

对于C ++标准定义所有这些,将消除实现者当前可获得的许多自由。实现者正在使用这些自由,尤其是在用C ++(和C,具有相同问题)编写非常低级的代码时。

如果您想为二进制便携式目标编写类似于C ++的东西,可以使用针对.NET和Mono的C ++ / CLI,以便(希望)可以在Windows以外的其他地方运行.NET。我认为可以说服MS的编译器生成将在Mono上运行的纯CIL程序集。

例如LLVM可能还可以做一些事情来创建二进制可移植的C或C ++环境。不过,我不知道会出现任何广泛的例子。

但是所有这些都依赖于修复C ++使实现依赖于实现的许多事情(例如类型的大小)。然后,可移植二进制文件所在的环境必须在要运行代码的系统上可用。通过允许不可移植的二进制文件,C和C ++可以到达便携式二进制文件无法实现的地方,这就是为什么标准根本没有说明二进制文件的原因。

然后,在任何给定的平台上,尽管标准并没有停止执行,但是实现通常仍不提供不同选项集之间的二进制兼容性。如果Don Box不满意,根据编译器选项,Microsoft的编译器可以从同一源生成不兼容的二进制文件,那么这就是他需要抱怨的编译器团队。C ++语言不会禁止编译器或OS固定所有必要的细节,因此,一旦您将自己限制在Windows范围内,C ++并不是根本问题。Microsoft已选择不这样做。

这种差异通常表现为您可能会出错并导致程序崩溃的又一件事,但是例如在不兼容的dll调试版本与dll发行版本之间,效率可能会得到可观的提高。

[*]我不确定这个主意是什么时候首次发明的,大概是1642年左右,但是与C ++致力于防止二进制定义可移植性的设计决策相比,它们的当前流行程度还相对较新。


@Steve但是C在i386和AMD64上具有定义明确的ABI,因此我可以将指向GCC版本X编译的函数的指针传递给MSVC版本Y编译的函数。使用C ++函数无法做到这一点。
user877329 2015年

7

跨平台和跨编译器的兼容性不是C和C ++的主要目标。它们诞生于一个时代,其目的是针对特定于平台和特定于编译器的时间和空间的最小化。

摘自Stroustrup的“ C ++的设计与演变”:

“明确的目标是在运行时,代码紧凑性和数据紧凑性方面匹配C。实现的理想目标是,可以将带有类的C用于可以使用的C。”


1
+1-完全一样。一个人如何构建可在ARM和Intel盒上使用的标准ABI?没道理!
Billy ONeal 2010年

1
不幸的是,它失败了。您可以执行C的所有工作...除了在运行时动态加载C ++模块外。您必须“还原”为在公开的界面中使用C函数。
gbjbaanb

6

这不是一个错误,这是一个功能!这使实现者可以自由地在二进制级别优化其实现。小尾数形式的i386及其后代并不是唯一存在或确实存在的CPU。


6

报价单中描述的问题是由于完全避免了符号名修饰方案的标准化引起的(我认为“ 二进制级别的标准化 ”在这方面是一个误导性短语,尽管该问题与编译器的应用程序二进制接口( ABI)。

C ++将函数或数据对象的签名和类型信息及其类/命名空间成员身份编码为符号名称,并且允许不同的编译器使用不同的方案。因此,静态库,DLL或目标文件中的符号将不会与使用其他编译器(甚至可能是同一编译器的不同版本)编译的代码链接。

通过不同编译器使用的方案示例,可能比这里更好地描述和解​​释了该问题。

这里还解释了故意缺乏标准化的原因。


3

ISO / ANSI的目的是使C ++语言标准化,这个问题似乎很复杂,需要数才能更新语言标准和编译器支持。

由于二进制文件需要在不同的CPU的体系结构和不同的OS环境上运行,因此二进制文件的兼容性要复杂得多。


是的,但是引用中描述的问题实际上与“二进制级别兼容性”(尽管作者使用了该术语)没有任何关系,除了在“应用程序二进制接口”中定义了这些东西之外。他实际上是在描述不兼容的名称处理方案的问题。

@Clifford:名称处理方案只是二进制级别兼容性的一个子集。后者更像一个总括术语!
纳瓦兹

我怀疑尝试在Windows计算机上运行Linux二进制文件是否存在问题。如果每个平台都有一个ABI,情况会好得多,因为至少脚本语言可以在同一平台上动态加载和运行二进制文件,或者应用程序可以使用由不同编译器构建的组件。您今天不能在Linux上使用C dll,并且没有人抱怨,但是C dll仍然可以通过python应用程序加载,这是从中受益的地方。
gbjbaanb 2012年

2

正如Andy所说,跨平台兼容性不是一个大目标,而广泛的平台和硬件实现是目标,最终结果是,您可以为多种系统编写符合标准的实现。二进制标准化实际上将使其无法实现。

C兼容性也很重要,这会使它变得很复杂。

随后,人们进行了一些努力来标准化一部分实现的ABI


该死,我忘了C兼容性。好点,+ 1!
安迪·托马斯

1

我认为在当今解耦的模块化编程世界中,缺少C ++标准是一个问题。但是,我们必须定义我们想要的标准。

在他们的头脑中没有人愿意为二进制文件定义实现或平台。因此,您不能使用x86 Windows dll并在x86_64 Linux平台上开始使用它。那会有点。

但是,人们想要的是与C模块相同的东西-二进制级别的标准化接口(即,一旦编译)。当前,如果要在模块化应用程序中加载dll,则可以导出C函数并在运行时绑定到它们。您不能使用C ++模块来做到这一点。如果可以,那就太好了,这也意味着用一个编译器编写的dll可以由另一个编译器加载。当然,您仍然无法加载为不兼容平台构建的dll,但这不是需要修复的问题。

因此,如果标准组织定义了模块公开的接口,那么在加载C ++模块时我们将具有更大的灵活性,我们不必将C ++代码公开为C代码,并且我们可能会得到更多使用脚本语言中的C ++语言。

我们也不必遭受诸如COM之类的尝试为该问题提供解决方案的事情。


1
+1。是的,我同意。这里的其他答案基本上是通过说二进制标准化将禁止特定于体系结构的优化来解决这个问题。但这不是重点。没有人在争论某种跨平台的二进制可执行格式。问题是没有动态加载C ++模块的标准接口
查尔斯·索尔维亚

1

C ++存在许多可移植性问题,这仅是由于缺乏二进制级别的标准化。

我不认为这是如此简单。提供的答案已经为缺乏对标准化的关注提供了很好的理由,但是C ++可能太丰富了的语言,因此不适合与C作为ABI标准真正竞争。

我们可以进行名称重整,这是由于函数重载,vtable不兼容性,跨越模块边界抛出异常的不兼容性等导致的。所有这些都是一个真正的难题,我希望它们至少可以使vtable布局标准化。

但是,ABI标准不仅仅在于使在一个编译器中生成的C ++ dylib能够被另一个由不同编译器构建的二进制文件使用。使用ABI跨语言。如果它们至少能涵盖第一部分,那就太好了,但是我看不到C ++在通用的ABI级别上能够真正与C竞争,因此对于制作兼容性最强的dylib至关重要。

想象一下这样导出的一对简单函数:

void f(Foo foo);
void f(Bar bar, int val);

...然后想像FooBar为班级带参数构造,拷贝构造函数,移动构造函数和非平凡的析构函数。

然后以Python / Lua / C#/ Java / Haskell / etc的场景为例。开发人员尝试导入此模块并以其语言使用它。

首先,我们需要一个名称处理标准,以了解如何利用函数重载来导出符号。这是一个简单的部分。但是,它实际上不应该被称为“ mangling”。由于dylib的用户必须按名称查找符号,因此此处的重载应导致名称看起来不完整。也许符号名称可能是类似的名称"f_Foo" "f_Bar_int"。我们必须确保它们不会与开发人员实际定义的名称冲突,也许会保留一些符号/字符/约定以供ABI使用。

但是现在情况更加艰难。例如,Python开发人员如何调用移动构造函数,复制构造函数和析构函数?也许我们可以将其作为dylib的一部分导出。但是如果FooBar导出到不同的模块怎么办?我们是否应该复制此dylib中关联的符号和实现?我建议我们这样做,因为它可能很快变得非常烦人,否则开始不得不在多个dylib接口中纠缠在一起,只是为了在此处创建对象,在此处传递,在此处复制,然后在此处销毁。尽管相同的基本关注点在C中可能有所应用(只是更手动/显式地),但C倾向于仅出于人们对其编程的方式而避免这种情况。

这只是尴尬的一小部分。当f上面的函数之一BazException向JavaScript中抛出一个(也是一个带有构造函数和析构函数并派生std :: exception的C ++类)时,会发生什么?

充其量,我认为我们只能希望将一个ABI标准化,该ABI可以从一个C ++编译器生成的二进制文件转换为另一个C ++编译器生成的二进制文件。当然,那很好,但是我只是想指出这一点。通常,伴随着这样的担忧来分配一个可以跨编译器使用的通用库,通常也希望使其真正具有通用性和兼容性。

建议的解决方案

在努力寻找多年使用COM风格接口的API / ABI C ++接口方法后,我建议的解决方案是成为“ C / C ++”(双关语)开发人员。

使用C创建那些通用ABI,并使用C ++来实现。我们仍然可以做诸如导出函数之类的事情,这些函数使用显式函数将指针返回到不透明的C ++类,以在堆上创建和销毁此类对象。即使我们完全使用C ++来实现,也要尝试从ABI角度爱上C语言的美感。可以使用函数指针表对抽象接口进行建模。将这些内容包装到C API中很繁琐,但是随之而来的发行版本的好处和兼容性将使其变得非常有价值。

然后,如果我们不喜欢直接使用此接口(至少出于RAII原因,我们可能不应该使用该接口),则可以将所有需要的东西包装在随SDK一起提供的静态链接C ++库中。C ++客户端可以使用它。

Python客户端不希望直接使用C或C ++接口,因为无法制作这些pythonique。他们希望将其包装到自己的pythonique接口中,因此,实际上,我们只是导出最低限度的C API / ABI以使其尽可能简单,这实际上是一件好事。

我认为,更多的C ++行业将从中受益,而不是顽固地交付COM样式的接口等等。这也将使我们的生活更加轻松,因为使用这些dylib的用户不必大惊小怪地使用ABI。C使它变得简单,并且从ABI的角度来看,它的简单性使我们能够创建自然而简单地针对所有FFI运作的API / ABI。


1
“使用C来创建那些通用ABI,并使用C ++来实现。” ...我和许多其他人一样!
纳瓦兹

-1

我不知道为什么它没有在二进制级别上标准化。但是我知道我该怎么做。在Windows上,我声明函数extern“ C” BOOL WINAPI。(当然,使用任何函数类型都可以替换BOOL。)它们可以干净地导出。


2
但是,如果您声明它extern "C",它将使用C ABI,这是常见PC硬件上的事实上的标准,即使它不是由任何委员会强加的。
Billy ONeal 2010年

-3

使用unzip foo.zip && make foo.exe && foo.exe,如果你希望你的源的便携性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.