C++中i=i+1,i+=1,i++,++i的区别(从汇编层面分析)

 
Category: C_C++

写在前面

在线编译工具链与反汇编工具: https://godbolt.org/;

使用的编译器: x86_64-clang16.0.

内置类型

i = i + 1

int main() {
    int i = 0;
    i = i + 1;
}

对应的汇编代码:

        mov     dword ptr [rbp - 4], 0 ;; i = 0
        ;;-------------- i = i + 1; -------------------
        mov     dword ptr [rbp - 8], 0 ;; new_i = 0
        mov     eax, dword ptr [rbp - 8] ;; eax = new_i
        add     eax, 1 ;; eax += 1
        mov     dword ptr [rbp - 8], eax ;; new_i = eax

相当于执行了四个步骤:

  1. 开辟一个新的内存地址i(不同于之前的i), 记为new_i, 并赋值为0
  2. 找一个寄存器放置new_i
  3. eax寄存器执行自增
  4. new_i赋值为eax

i += 1

int main() {
    int i = 0;
    i += 1;
}
        mov     dword ptr [rbp - 4], 0 ;; i = 0
        ;;--------------- i += 1; -------------------
        mov     eax, dword ptr [rbp - 4] ;; eax = i
        add     eax, 1 ;; eax += 1
        mov     dword ptr [rbp - 4], eax ;; i = eax

三个步骤(少了new_i的操作, 直接在原变量位置操作)

  1. 存寄存器, 将i的值赋给eax
  2. eax自增
  3. 自增后的eax传回i

i++

运算

int main() {
    int i = 0;
    i++;
}

和++i的没区别.

        mov     dword ptr [rbp - 4], 0;; i = 0
        ;;------------ i++; -------------
        mov     eax, dword ptr [rbp - 4] ;; eax = i
        add     eax, 1 ;; eax += 1
        mov     dword ptr [rbp - 4], eax ;; i = eax

赋值

主要讨论赋值的情况.

int main() {
    int i = 0;
    int c = i++; // c = 0, i = 1
}
        mov     dword ptr [rbp - 4], 0 ; int i = 0;
        ;;------------ int c = i++; -------------------
        mov     eax, dword ptr [rbp - 4] ; eax = i
        mov     ecx, eax ; ecx = eax
        add     ecx, 1 ;; ecx += 1
        mov     dword ptr [rbp - 4], ecx ;; i = ecx
        mov     dword ptr [rbp - 8], eax ;; c = eax

五个步骤:

  1. 存寄存器eax: eax中放置i的值
  2. 新开一个寄存器ecx, 存i的副本
  3. ecx自增
  4. ecx赋值给i
  5. eax赋值给c

++i

运算

int main() {
    int i = 0;
    ++i;
}

和i++的没区别.

        mov     dword ptr [rbp - 4], 0;; i = 0
        ;;------------ i++; -------------
        mov     eax, dword ptr [rbp - 4] ;; eax = i
        add     eax, 1 ;; eax += 1
        mov     dword ptr [rbp - 4], eax ;; i = eax

赋值

主要讨论赋值的情况

int main() {
    int i = 0;
    int d = ++i; // d = 1, i = 1
}
        mov     dword ptr [rbp - 4], 0 ;; i = 0;
        ;;------------ int d = ++i; -------------------
        mov     eax, dword ptr [rbp - 4] ;; eax = i
        add     eax, 1 ;; eax += 1
        mov     dword ptr [rbp - 4], eax ;; i = eax, 相当于对原对象自增
        mov     dword ptr [rbp - 8], eax ;; d = eax

四个步骤:

  1. 寄存器eax存i的值
  2. eax自增
  3. eax的值赋给i
  4. eax的值赋给d

自定义类型

class I {
    int x;

public:
    I() : x(0) {}
    I(int _x) : x(_x) {}
    I operator+(const I& rhs) {
        // 返回临时对象, 如果返回引用,
        // 则会报错(返回的实际上是被销毁的局部对象)
        I tmp;
        tmp += rhs; // 调用operator+=
        return tmp;
    }
    I& operator+=(I inc) {
        this->x += inc.x;
        return *this;
    }
    I& operator++() { // 传引用使之可以链式调用
        ++this->x;
        return *this;
    }
    I operator++(int) {
        I tmp(*this);
        ++*this; // 调用operator++(int)
        return tmp;
    }
};

反汇编:


i++

int main() {
    I i;
    i++;
}

下面的反汇编代码仅针对i++;这一行.

    lea     rdi, [rbp - 8] ;; 读取i的地址(lea和mov的区别)
    xor     esi, esi ;; esi置为0
    call    I::operator++(int) ;; 调用后置++
    mov     dword ptr [rbp - 16], eax ;; eax的值赋给i

下面针对后置++反汇编:

I::operator++(int):                              # @I::operator++(int)
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     qword ptr [rbp - 16], rdi
        mov     dword ptr [rbp - 20], esi
        mov     rdi, qword ptr [rbp - 16]
        mov     eax, dword ptr [rdi]
        mov     dword ptr [rbp - 8], eax
        call    I::operator++()
        mov     eax, dword ptr [rbp - 8]
        add     rsp, 32
        pop     rbp
        ret

总结

效率方面: (汇编代码很容易看出, 每一次开辟新变量以及每一次赋值都要花费CPU时间)

i = i + 1 < i += 1 < i++ < ++i

这是针对内置类型的情况, 实际自定义类型的话, 还要考虑对象的拷贝和移动等操作.