x+=y和x=x+;C#中的y(x和y都是简单类型)?
在C/C++中 复合赋值运算符将简单赋值运算符与另一个二进制运算符组合。复合赋值运算符执行附加运算符指定的操作,然后将结果赋给左操作数。例如,复合赋值表达式,如x+=y和x=x+;C#中的y(x和y都是简单类型)?,c#,operator-keyword,C#,Operator Keyword,在C/C++中 复合赋值运算符将简单赋值运算符与另一个二进制运算符组合。复合赋值运算符执行附加运算符指定的操作,然后将结果赋给左操作数。例如,复合赋值表达式,如 expression1+=expression2 可以理解为 expression1=expression1+expression2 但是,复合赋值表达式并不等同于扩展版本,因为在加法操作和赋值操作中,复合赋值表达式只对表达式1求值一次,而扩展版本对表达式1求值两次 (引自) 例如: 对于i+=2,i将直接修改,而无需创建任何新对象
expression1+=expression2
可以理解为
expression1=expression1+expression2
但是,复合赋值表达式并不等同于扩展版本,因为在加法操作和赋值操作中,复合赋值表达式只对表达式1求值一次,而扩展版本对表达式1求值两次
(引自)
例如:
i+=2代码>,i
将直接修改,而无需创建任何新对象
i=i+2
,首先将创建i
的副本。复制的一个将被修改,然后被分配回i
在C#中,像
+=
这样的运算符不允许重载。所有像int
或double
这样的结构都声明为readonly struct
(这是否意味着C#中的所有结构实际上都是不可变的?)
我想知道在C#中,是否有一个表达式可以强制直接修改对象(至少对于简单类型),而不创建任何无用的实例
而且,如果构造器和解构器没有副作用,C#编译器是否可以按预期将表达式
x=x+y
优化为x+=y
。形式x+=y
是x=x+y
回答此类问题时,可以使用SharpLab
因为生成的IL和JITted代码在这两种情况下都是相同的。C# 当您将C#编译成.NET程序集时,代码是用MSIL(Microsoft中间语言)编写的。这使得代码可以移植。NET运行时将JIT编译它以供执行 MSIL是一种堆栈语言。它不知道目标硬件的细节(例如CPU有多少个寄存器)。只有一种方法可以编写该添加:
ldloc.0
ldloc.1
add
stloc.0
加载堆栈中的第一个本地,加载第二个,添加※它们,设置堆栈中的第一个本地
※:add
从堆栈中弹出两个元素,添加它们,并将结果推回到堆栈中
因此,x=x+y
和x+=y
将产生相同的代码
当然,之后会发生一些优化。JIT编译器将把它转换成实际的机器代码 这就是我所看到的: 因此,我们将
[ebp-4]
复制到ecx
,添加[ebp-8]
,然后将ecx
复制回[ebp-4]
所以。。。寄存器ecx
是一个无用的实例吗
这就是SharpLab,这就是JIT。理论上,不同的编译器可以在不同的平台上将代码转换成不同的东西 您可以将.NET代码AOT编译为本机映像,这将更加积极地进行优化。尽管如此,我不认为您将如何改进一个简单的加法。哦,我知道,它可能会看到你不使用这个值并删除它,或者可能会看到你总是添加相同的值并用常量替换它 可能值得注意的是,现代.NET JIT能够在执行过程中继续优化代码(它会很快生成一个优化效果不佳的代码本机版本,稍后,一旦它准备好,就用更好的版本替换它)。这个决定来自这样一个事实:在JIT运行时,性能取决于创建本机代码所需的时间和本机代码运行所需的时间
C++ <>让我们看看C++做什么。这是我在使用(默认设置※)时看到的
x=x+y
和x+=y
:
指令mov
,add
,mov
与我们从SharpLab获得的指令相匹配,具有不同的寄存器选择
※:x86-64 gcc 9.3带-g-o/tmp/compiler-explorer-compiler 2020424-22672-17cap6k.bjoj/output.s-masm=intel-s-fdiagnostics color=always/tmp/compiler-explorer-compiler 2020424-22672-17cap6k.bjoj/example.cpp
添加编译器选项
-O
使代码消失。这是有道理的,因为我没有使用它。“两种简单类型”,我猜你的意思是“都重载+运算符”,在C/C++中有“简单”类型没有(例如bool),我们知道“-不,我们没有。这是胡说八道。@Henkholtman我怀疑这句话也有问题。。。但我不知道足够的C来判断。谢谢你指出这一点。如果一种语言能做到这一点,那会很奇怪,不是吗?@Sweeper-optimizer比这里建议的要好得多。1) 你不必担心。2) 如果你感兴趣,你将不得不测量。在目标情况下,环绕代码也会影响这一点。在“仿佛规则”中,我在.NET端有一个例子:MSIL是一种堆栈语言,它不了解寄存器,这并不意味着当代码运行时,它将在堆栈上执行所有操作,而不使用寄存器。一件事是语言的语义,另一件事是编译器实际做的事情。+1,为了完整起见,您可以添加一个M3
方法,显示i=j+2
的jitted代码,并突出显示我实际上想要强调的优化,即如果在compoun中使用后增量运算符,其行为可能会有所不同
ldloc.0
ldloc.1
add
stloc.0
mov ecx, [ebp-4]
add ecx, [ebp-8]
mov [ebp-4], ecx
mov eax, DWORD PTR [rbp-8]
add DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]