C/C++编译器在编译过程中可以通过按引用传递来优化按值传递吗?

zaq34kh6  于 2023-10-16  发布在  C/C++
关注(0)|答案(3)|浏览(191)

在更高级的语言中,例如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不会改变结果,他们能有效地将FSlow重写为FFastRef/FFastPointer吗?
我想我感兴趣的是C和C++标准对它的描述,以及实际编译器在实践中的行为,如果它由标准决定的话。

8yparm6h

8yparm6h1#

既然C/C对指针、引用、按值传递和按引用传递都有明确的语义,那么它的编译器能保证完全尊重这些语义吗?
不,他们的规范描述了C和C
在抽象机器方面的行为。抽象机器的某些相当少的行为被归类为“可观察行为”。一个符合的实现可以按照它喜欢的方式运行,只要产生的可观察行为与抽象机在运行相同程序时指定产生的行为相匹配。这是编译器实现的几乎所有优化的基本理由。
在C中,可观察到的行为是

  • volatile访问对象
  • 写入文件(在程序终止时判断)
  • 交互设备的I/O语义(主要与缓冲模式有关)

也就是说,如果他们认为不会改变结果,他们是否可以有效地将FSlow重写为FFastRef/FFastPointer?
原则上,他们可以这样做,前提是他们还确定不可能有任何编译器没有机会正确调整的FSlow()调用。实际上,这是一个相当高的门槛。单独编译是做出这种决定的一个问题,函数指针也是如此。
然而,如果FSlow()相对较小,那么更常见和可能的优化将是简单地将其内联到可能这样做的调用函数中(大致相同的地方,可以调整其调用约定)。
我想我感兴趣的是C和C++标准对它的描述,以及实际编译器在实践中的行为,如果它由标准决定的话。
规范很少提到编译器(“翻译器”)的行为。它们主要是根据程序必须表现出的行为来编写的。

ctehm74n

ctehm74n2#

不能,因为:

  • FSlow处理对象的副本,而FFastRef处理对象的引用
  • 在C++中,const &表示不能通过这个引用修改对象。但是,如果原始对象是可变的,则可以从函数外部修改对象(即,另一个线程)。这迫使FFastREf在每次访问时总是从原始对象加载值,而FSlow使用副本。

现在可以说,对对象的非同步访问(至少有一次写入)是未定义行为。我不是100%确定,但我认为如果编译器可以证明对BigObject的访问在函数内部是不同步的,那么这意味着它不能从函数外部修改,因此理论上可以优化对它的访问。

ghhkc1vu

ghhkc1vu3#

在gcc中优化它只有在函数是内联的情况下才有可能。IMO如果函数不是内联的,对象是不可变的,那么它就错过了优化。

int __attribute__((always_inline)) foo(const int *restrict x, size_t y, size_t z)
{
    for(size_t attempt = 0; attempt < y; attempt++)
    {
        if(x[y] == rand()) return x[y];
    }
    return 0;
}

void bar(void)
{
    const int arr[] = {1,2,3,4,5};
    printf("%d", foo(arr,100,rand()%(sizeof(arr)/sizeof(arr[0]))));
}

https://godbolt.org/z/o99d74oxT

相关问题