x+=y和x=x+;C#中的y(x和y都是简单类型)?

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将直接修改,而无需创建任何新对象

在C/C++中

复合赋值运算符将简单赋值运算符与另一个二进制运算符组合。复合赋值运算符执行附加运算符指定的操作,然后将结果赋给左操作数。例如,复合赋值表达式,如

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]