(理论)编译器和不同的传递类型 新序言 我对构造器理论不太了解,这是一个关于C++语言的理论构造函数以及它如何编译非常相似的方法到汇编程序(进一步深入二进制代码)的理论问题。在三个不同的方法中,汇编程序是否相似,每个方法都接受相同数量的参数,但提供相同的功能(可能是一个打印两个整数值的简单方法?)
每个方法都以一对整数作为参数,但方式不同:一个方法通过值传递,另一个通过引用传递,最后一个通过地址传递。由于pass-by-reference和address变量必须作用于提供的内存位置的实际值,因此它们(除了访问值的代码之外)在编译版本中是否包含相同的代码(理论)编译器和不同的传递类型 新序言 我对构造器理论不太了解,这是一个关于C++语言的理论构造函数以及它如何编译非常相似的方法到汇编程序(进一步深入二进制代码)的理论问题。在三个不同的方法中,汇编程序是否相似,每个方法都接受相同数量的参数,但提供相同的功能(可能是一个打印两个整数值的简单方法?),c++,assembly,compiler-construction,pass-by-reference,compiler-optimization,C++,Assembly,Compiler Construction,Pass By Reference,Compiler Optimization,每个方法都以一对整数作为参数,但方式不同:一个方法通过值传递,另一个通过引用传递,最后一个通过地址传递。由于pass-by-reference和address变量必须作用于提供的内存位置的实际值,因此它们(除了访问值的代码之外)在编译版本中是否包含相同的代码 public void Foo (int a, int b) { std:cout << a << " " << b <<endl; } public void Bar (int* a
public void Foo (int a, int b)
{
std:cout << a << " " << b <<endl;
}
public void Bar (int* a, int* b)
{
// (aside from dereferencing code) the same code as Foo
}
public void FooBar (int& a, int& b)
{
// again, the same code (fundamentally) here
}
假设此语言的编译器工作正常,没有bug,并且进行了充分的优化
在这样一个完美的系统中,三种方法的输出程序集和二进制代码是否都不匹配?如果不是,它们之间会有巨大的差异吗
澄清:假设这些方法非常简单,因为它们不会改变传入参数的值。在完美系统中,是的 然而,这种优化水平相当极端
Bar
和FooBar
几乎与Foo
相同,但事实上它们几乎相同,这使得编译器很难检测到相似性,因为它基本上是对生成的代码进行区分,然后必须计算出差异有多大
如果您需要这种级别的优化,那么最好是这样编写代码
public void Foo(int a, int b)
{
// Whatever
}
public void Bar (int* a, int* b)
{
Foo(*a, *b);
}
public void FooBar (int& a, int& b)
{
Foo(a, b);
}
<>现在编译器可以选择将代码> Foo为
对于第一个(Foo),如果不知道将来如何使用函数,则取决于您是否真的允许修改函数声明样式。如果它们的接口假设它们可以在指针下更改值(即使在非常极端的条件下),则不允许只传递值的优化。但是,假设经常调用函数,编译器和/或运行时可以将其更改为无指针的变量,并直接传递值。(但是,更可能的是,将应用一些其他优化,例如全功能内联。)显然,如果代码确实修改了
a
和b
,则前两个函数和后两个函数的语义完全不同;这显然会迫使编译器生成不同的代码;除此之外,如果这三个函数在给定程序中的每种情况下的行为都是相同的,那么编译器当然可以根据“仿佛”规则将它们简化为一个函数
但是这个问题没有多大意义——或者至少,在“完美系统”(不可能存在的东西)中询问二进制代码生成的具体差异(非常具体的实现细节)对我来说没有什么意义
回到真实系统,首先要考虑内联;如果函数是内联的,则它们与父函数的其余代码混合,并且每个扩展的输出可能不同(可以有不同的寄存器分配、不同的指令混合,以最大限度地提高管道利用率,…)
因此,比较应该是关于每个函数的“独立”输出
我希望第二个和第三个扩展到几乎完全相同的代码:引用是指针的语法糖,而*NULL
引用不能被考虑在内,因为*a
和*b
可能在条(它告诉优化器假定它们永远不会是代码< null >代码>)。而且,我不知道区分指针和引用的任何C++ abi。< /P>
至于Foo
,这将取决于许多因素:
- 如果我们正在编译一个库,编译器不能自由地做它想做的任何事情,函数必须遵循某种ABI;因此,首先,在最后两种情况下,参数实际上是指针,在第一种情况下是值(根据平台ABI的不同,其结果会有所不同)
- 如果编译器不能使用LTCG,并且我们希望使用来自其他模块的这些函数(即,这些函数没有标记为
静态
),则这一点也适用
- 在最后两种情况下,为了生成相同的输出,编译器可能需要证明引用/指针指向不同的值,以生成与
Foo
相同的输出
- 此外,它必须能够证明
a
和b
在整个函数中没有改变;特别是,在每次外部(=非完全内联)函数调用之后,指向的对象可能已经改变;这两个都可能是复杂的任务,而且,如果程序由多个模块组成,它们可能需要LTCG
因此,我实际期望发生的是:
- 对于独立版本,
Foo!=Bar
和FooBar
;Bar==FooBar
- 至于内联版本,编译器可能会有一个更简单的时间来确定将“
Bar
和FooBar
转换为相同语义的Foo
”的条件,但当然,生成的代码与不同函数的代码混合的事实将导致
public void Foo(int a, int b)
{
// Whatever
}
public void Bar (int* a, int* b)
{
Foo(*a, *b);
}
public void FooBar (int& a, int& b)
{
Foo(a, b);
}