在像 Swift 这样的高级语言中,最常见和最强大的编译器优化之一是
copy-on-write
机制,它基本上将每个按值传递转变为按引用传递,直到实际需要该值为止更改实际制作真实副本的时间点。
由于 C/C++ 对于指针、引用、按值传递和按引用传递具有显式语义,因此其编译器是否保证完全尊重这些语义?
void FSlow(BigObject x) { /* only access BigObject, never modify */ }
void FFastRef(const BigObject& x) { /* only access BigObject, never modify * }
void FFastPointer(const BigObject* const x) { /* only access BigObject, never modify * }
即如果他们认为这不会改变结果,他们能否有效地将
FSlow
重写为 FFastRef
/FFastPointer
?
我想我对 C 和 C++ 标准对此的说法以及实际编译器在实践中通常如何表现(如果符合标准)感兴趣。
由于 C/C++ 对于指针、引用、按值传递和按引用传递具有显式语义,因此其编译器是否保证完全尊重这些语义?
不。他们的规范根据抽象机器描述了 C 和 C++ 行为。抽象机的某些相当少的行为被归类为“可观察行为”。只要产生的可观察行为与使用相同输入运行相同程序时指定抽象机产生的行为相匹配,一致的实现就可以表现出任何它喜欢的行为。这是编译器实现的几乎所有优化的基本理由。
在 C 语言中,可观察到的行为是
volatile
访问对象即如果他们决定不会改变结果,他们是否可以有效地将 FSlow 重写为 FFastRef/FFastPointer?
原则上他们可以,前提是他们还确定不能有任何对
FSlow()
的调用,而编译器没有机会正确调整。实际上,这是一个相当高的门槛。单独编译是做出这样的决定的一个问题,函数指针也是如此。
但是,如果
FSlow()
相对较小,那么更常见且可能的优化就是简单地将其内联到可以这样做的调用函数中(与可以调整其调用约定的位置大致相同)。
我想我对 C 和 C++ 标准对此的说法以及实际编译器在实践中通常如何表现(如果符合标准)感兴趣。
规范很少提及编译器(“翻译器”)的行为。它们主要是根据程序必须表现出的行为来编写的。
不,不能,因为:
FSlow
适用于对象的副本,而 FFastRef
适用于对象的引用const &
表示无法通过此引用修改该对象。但是,如果原始对象是可变的,则可以从函数外部(即从另一个线程)修改该对象。这会强制 FFastREf
在每次访问时始终加载该值,而 FSlow
使用副本。现在有句话说,对对象的非同步访问(至少一次写入)是未定义的行为。我不是 100% 确定,但我认为如果编译器可以证明对
BigObject
的访问在函数内部是不同步的,那么这意味着它不能从函数外部修改,因此理论上可以优化对它的访问在这种情况下。