• Home
  • 写文
  • 关于
    • jlweb Blog photo

      jlweb Blog

      occupied with moon theme of jelly

    • 详情
    • Github
    • Steam
  • 文章
    • 所有文章
    • 所有标签
  • 项目
  • 主站
search clear

常见例子汇编

10 Dec 2023

阅读时长 ~8 分钟

编辑

1.lambda 汇编调用过程

int n {};
auto funs = [n] () mutable {
    return ++n;
};
funs();
funs();

C++ insight: click

  class __lambda_6_14
  {
    public: 
    inline /*constexpr */ int operator()()
    {
      return ++n;
    }
    
    private: 
    int n;
    
    public:
    __lambda_6_14(int & _n)
    : n{_n}
    {}
    
  };

如果没有加mutable则会变为: inline /*constexpr */ int operator()() const

#include <iostream>
int square(int num)
{
  int n = {};
    
  class __lambda_6_14
  {
    public: 
    inline /*constexpr */ int operator()() const
    //常成员函数
    {
      return n;
    }
    
    private: int n;
    
    public:
    __lambda_6_14(int & _n): n{_n}
    {}
    
  };
  
  __lambda_6_14 funs = __lambda_6_14{n};
  int b = static_cast<const __lambda_6_14>(funs).operator()();
    //常对象实例才可以调用 const 成员函数
  static_cast<const __lambda_6_14>(funs).operator()();
  return 0;
}
_n$ = -8                                                ; size = 4
_funs$ = -4                                   ; size = 4
_num$ = 8                                         ; size = 4
int square(int) PROC                                    ; square
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     DWORD PTR _n$[ebp], 0
        lea     eax, DWORD PTR _n$[ebp]
        push    eax
        lea     ecx, DWORD PTR _funs$[ebp]
        call    <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>(int const &) ; <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>
        lea     ecx, DWORD PTR _funs$[ebp]
        call    <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::operator()(void) ; <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::operator()
        lea     ecx, DWORD PTR _funs$[ebp]
        call    <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::operator()(void) ; <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::operator()
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
int square(int) ENDP                                    ; square

_this$ = -4                                   ; size = 4
_<n>$ = 8                                         ; size = 4
默认构造函数
<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>(int const &) PROC ; <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     DWORD PTR _this$[ebp], ecx
        mov     eax, DWORD PTR _this$[ebp]
        mov     ecx, DWORD PTR _<n>$[ebp]
        mov     edx, DWORD PTR [ecx]
        mov     DWORD PTR [eax], edx
        mov     eax, DWORD PTR _this$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     4
<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>(int const &) ENDP ; <lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>::<lambda_41ba799f4cfe0531d69dc71ad1bbcdb9>

tv66 = -8           ; 临时变量 tv66 在栈中的相对偏移为 -8,大小为 4 字节
_this$ = -4         ; 函数参数 this 指针在栈中的相对偏移为 -4,大小为 4 字节

<lambda_3e268e853469f7c2a02c8b4edb2b84f5>::operator()(void) PROC ; 函数名为 operator(),返回类型为 void,参数列表为空
        push    ebp        ; 保存当前栈帧基址指针 ebp 到栈中
        mov     ebp, esp   ; 将当前栈顶指针 esp 赋值给 ebp,建立新的栈帧
        sub     esp, 8     ; 为函数分配 8 字节的栈空间,用于存储局部变量和函数调用时的临时数据
        mov     DWORD PTR _this$[ebp], ecx ; 将函数参数 this 指针存储到 _this$ 变量所在的内存地址中
        mov     eax, DWORD PTR _this$[ebp] ; 将 _this$ 变量的值(即当前对象的指针)存储到寄存器 eax 中
        mov     ecx, DWORD PTR [eax]      ; 将寄存器 eax 中存储的指针所指向的内存地址中的值存储到寄存器 ecx 中,即将当前对象的成员变量 x 的值加载到寄存器 ecx 中
        add     ecx, 1                   ; 将寄存器 ecx 中的值加 1,实现 x 的自增操作
        mov     DWORD PTR tv66[ebp], ecx ; 将寄存器 ecx 中的值存储到临时变量 tv66 所在的内存地址中
        mov     edx, DWORD PTR _this$[ebp] ; 将 _this$ 变量的值(即当前对象的指针)存储到寄存器 edx 中
        mov     eax, DWORD PTR tv66[ebp]  ; 将临时变量 tv66 的值存储到寄存器 eax 中
        mov     DWORD PTR [edx], eax      ; 将寄存器 eax 中的值存储到寄存器 edx 所指向的内存地址中,即将自增后的值赋给 x
        mov     eax, DWORD PTR tv66[ebp]  ; 将临时变量 tv66 的值存储到寄存器 eax 中
        mov     esp, ebp                 ; 将基址指针 ebp 的值赋给堆栈指针 esp,用于恢复函数现场
        pop     ebp                      ; 将栈顶元素弹出并存储到基址指针 ebp 中,用于恢复函数现场
        ret     0                        ; 返回函数调用者,返回值为 0
<lambda_3e268e853469f7c2a02c8b4edb2b84f5>::operator()(void) ENDP ; 函数结束
square(int)::{lambda()#1}::operator()():
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        lea     edx, [rax+1]
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        pop     rbp
        ret
square(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 24
        mov     DWORD PTR [rbp-20], edi
        mov     DWORD PTR [rbp-4], 0  //默认 int n{};
        mov     eax, DWORD PTR [rbp-4]
        mov     DWORD PTR [rbp-8], eax //把lambda匿名类里n的初始化编译合并到square里了
                            //使用square里的二号局部变量当作lambda的private n;
        lea     rax, [rbp-8]
        mov     rdi, rax	//x86-64下默认rdi存放this指针第一个参数
        call    square(int)::{lambda()#1}::operator()()
        lea     rax, [rbp-8]
        mov     rdi, rax
        call    square(int)::{lambda()#1}::operator()()
        mov     eax, 0
        leave
        ret
__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L7
        cmp     DWORD PTR [rbp-8], 65535
        jne     .L7
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
.L7:
        nop
        leave
        ret
_GLOBAL__sub_I_square(int):
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret

:::success 不同架构传参时候,使用寄存器不同,比如x86传递this指针时候就ecx, x86-64这里用的是rdi 具体看C++函数调用方式 :::

2.x86 __fastcall调用时 this指针的传递是否还是ecx?

int main(void)
{
    int n {};
    class __lambda_6_14
    {
    public: 
    inline /*constexpr */ int __fastcall operator()(int a, int b)
    {
        return a+b+n++;
    }

    private: 
    int n;

    public:
    __lambda_6_14(int & _n)
    : n{_n}
    {}

};

    __lambda_6_14 funs = __lambda_6_14{n};
    int b = funs.operator()(3,4);

    return 0;
}
_b$ = -12                                     ; size = 4
_funs$ = -8                                   ; size = 4
_n$ = -4                                      ; size = 4
_main   PROC
    push    ebp
    mov     ebp, esp
    sub     esp, 12                           ; 0000000cH
    mov     DWORD PTR _n$[ebp], 0
                    lea     eax, DWORD PTR _n$[ebp]
    push    eax
    lea     ecx, DWORD PTR _funs$[ebp]
    call    `int main(void)'::`2'::__lambda_6_14::__lambda_6_14(int &) ; `main'::`2'::__lambda_6_14::__lambda_6_14
    push    4
    mov     edx, 3
    lea     ecx, DWORD PTR _funs$[ebp]
    call    int `int main(void)'::`2'::__lambda_6_14::operator()(int,int) ; `main'::`2'::__lambda_6_14::operator()
    mov     DWORD PTR _b$[ebp], eax
    xor     eax, eax
    mov     esp, ebp
    pop     ebp
    ret     0
_main   ENDP
  1. 看到了,还是以C++类的___thiscall _优先,ecx传递this指针(第一个参数),edx传递第二个参数a,后面b参数入栈。
  2. 在 C++ 中,构造函数和析构函数是特殊的成员函数,它们的名称与类名称相同,没有返回值,并且不能被直接调用。编译器在对象创建和销毁时自动调用构造函数和析构函数。因此,这些函数的调用约定必须与编译器和操作系统的默认约定一致,否则会导致编译错误。

在 Microsoft Visual C++ 编译器中,默认使用的是 __thiscall 调用约定,这是一种类似于 stdcall 的约定,用于将对象指针作为隐式的第一个参数传递。如果在构造函数或析构函数中使用了其他的调用约定,就会导致编译错误,出现类似于 “illegal calling convention” 的错误信息。 继续放出完整汇编,可以看到还是在main函数空间 的 ebp-8 栈上保存了lambda内部的n; lea ecx, DWORD PTR _funs$[ebp] 这里ecx作为访问默认构造函数(__thiscall方式)的唯一参数,也就是类的this指针

这里意思是把类实例空间存放到了main的栈空间上,为其开辟了4个字节; std::cout « sizeof(__lambda_6_14) « std::endl; 结果为4

_b$ = -12                                         ; size = 4
_funs$ = -8                                   ; size = 4
_n$ = -4                                                ; size = 4
_main   PROC
        push    ebp
        mov     ebp, esp
        sub     esp, 12                             ; 0000000cH
        mov     DWORD PTR _n$[ebp], 0
        lea     eax, DWORD PTR _n$[ebp]
        push    eax
        lea     ecx, DWORD PTR _funs$[ebp]
        call    `int main(void)'::`2'::__lambda_6_14::__lambda_6_14(int &) ; `main'::`2'::__lambda_6_14::__lambda_6_14
        push    4
        mov     edx, 3
        lea     ecx, DWORD PTR _funs$[ebp]
        call    int `int main(void)'::`2'::__lambda_6_14::operator()(int,int) ; `main'::`2'::__lambda_6_14::operator()
        mov     DWORD PTR _b$[ebp], eax
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main   ENDP
tv70 = -12                                          ; size = 4
_a$ = -8                                                ; size = 4
_this$ = -4                                   ; size = 4
_b$ = 8                                       ; size = 4
int `int main(void)'::`2'::__lambda_6_14::operator()(int,int) PROC      ; `main'::`2'::__lambda_6_14::operator(), COMDAT
        push    ebp
        mov     ebp, esp
        sub     esp, 12                             ; 0000000cH
        mov     DWORD PTR _a$[ebp], edx
        mov     DWORD PTR _this$[ebp], ecx
        mov     eax, DWORD PTR _a$[ebp]
        add     eax, DWORD PTR _b$[ebp]
        mov     ecx, DWORD PTR _this$[ebp]
        add     eax, DWORD PTR [ecx]
        mov     DWORD PTR tv70[ebp], eax
        mov     edx, DWORD PTR _this$[ebp]
        mov     eax, DWORD PTR [edx]
        add     eax, 1
        mov     ecx, DWORD PTR _this$[ebp]
        mov     DWORD PTR [ecx], eax
        mov     eax, DWORD PTR tv70[ebp]
        mov     esp, ebp
        pop     ebp
        ret     4
int `int main(void)'::`2'::__lambda_6_14::operator()(int,int) ENDP      ; `main'::`2'::__lambda_6_14::operator()


_this$ = -4                                   ; size = 4
__n$ = 8                                                ; size = 4
`int main(void)'::`2'::__lambda_6_14::__lambda_6_14(int &) PROC   ; `main'::`2'::__lambda_6_14::__lambda_6_14, COMDAT
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     DWORD PTR _this$[ebp], ecx
        mov     eax, DWORD PTR _this$[ebp]
        mov     ecx, DWORD PTR __n$[ebp]
        mov     edx, DWORD PTR [ecx]
        mov     DWORD PTR [eax], edx
        mov     eax, DWORD PTR _this$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     4
`int main(void)'::`2'::__lambda_6_14::__lambda_6_14(int &) ENDP   ; `main'::`2'::__lambda_6_14::__lambda_6_14

3.匿名对象的生命周期

首先定义一段代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <cstring>


using namespace std;
class Test{
    public:
        int value;
        Test(){
            value=1; 
        }
        ~Test(){
            
        }
};
Test* ptr;

void foo(const Test &t)
{
    Test &mutable_t = const_cast<Test &>(t);
    mutable_t.value = 42; // 修改 t.value 的值
    ptr = &mutable_t;
}

int main()
{
    //Test t2=Test();
    foo(Test()); // OK
    ptr->value = 10;
    return 0;
}

foo(Test()); 关于这里的匿名对象Test()的生命周期应该只有这一行表达式。 但是我们从汇编代码中可以发先,其匿名对象是在,数据段(Data Segement)区域进行生成的, 同时在这一行表达式结束后,我们依然能够通过全局ptr指针对匿名对象的属性进行修改。 但是试图访问 ptr->value 会报错: stack-use-after-scope

4.push_back和emplace_back

#include<string>
#include<vector>
using namespace std;
vector<string> vs;

void em(){
    vs.emplace_back("abcd");
}

void pu(){
    string temp("abcd");
    vs.push_back(move(temp));
}

int main(){
    return 0;
}
对应代码为:
void pu(){
    vs.push_back("abcd");
}
//////////////////////////////////////////////////////////////////////////////////
pu():
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 56
        lea     rax, [rbp-25]
        mov     QWORD PTR [rbp-24], rax
        nop
        nop
        lea     rdx, [rbp-25]
        lea     rax, [rbp-64]
        mov     esi, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-64]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:vs[abi:cxx11]
        call    std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::push_back(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        lea     rax, [rbp-25]
        mov     rdi, rax
        call    std::__new_allocator<char>::~__new_allocator() [base object destructor]
        nop
        jmp     .L18
        mov     rbx, rax
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        jmp     .L15
        mov     rbx, rax
pu():
        push rbp // 保存调用前的栈底指针
        mov rbp, rsp // 设置新的栈底指针
        push rbx // 保存rbx寄存器的值
        sub rsp, 56 // 在栈上分配56字节的空间
        lea rax, [rbp-25] // 计算字符串对象的地址
        mov QWORD PTR [rbp-24], rax // 将字符串对象的地址存入栈中
        nop // 空指令
        nop // 空指令
        lea rdx, [rbp-25] // 将字符串对象的地址存入rdx寄存器
        lea rax, [rbp-64] // 将字符串对象的地址存入rax寄存器
        mov esi, OFFSET FLAT:.LC0 // 将字符串"abcd"的地址存入esi寄存器
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
	// 调用std::string的构造函数,创建一个新的字符串对象
        call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&) 
        lea rax, [rbp-25] // 将字符串对象的地址存入rax寄存器
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
	// 调用析构函数销毁字符串对象内部的分配器对象
        call std::__new_allocator<char>::~__new_allocator() [base object destructor] 
        nop // 空指令
        lea rax, [rbp-64] // 将字符串对象的地址存入rax寄存器
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
	// 将字符串对象的右值引用移动到rsi寄存器中
        call std::remove_reference<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::type&& std::move<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) 
        mov rsi, rax // 将移动后的字符串对象的地址存入rsi寄存器
        mov edi, OFFSET FLAT:vs[abi:cxx11] // 将vector对象vs的地址存入edi寄存器
	// call push_back 将移动后的字符串对象添加到vector中
        call std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::push_back(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&) 
        lea rax, [rbp-64] // 将字符串对象的地址存入rax寄存器
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
	// 调用析构函数销毁字符串对象
        call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor] 
        jmp .L18 // 跳转到.L18标签处
        
        .L18:
        mov rbx, rax // 将rax寄存器的值存入rbx寄存器
        lea rax, [rbp-25] // 计算字符串对象的地址
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
	// 调用析构函数销毁字符串对象内部的分配器对象
        call std::__new_allocator<char>::~__new_allocator() [base object destructor] 
        nop // 空指令
        mov rax, rbx // 将rbx寄存器的值存入rax寄存器
        mov rdi, rax // 将字符串对象的地址存入rdi寄存器
        call _Unwind_Resume // 调用_Unwind_Resume函数恢复异常处理程序的控制权
.LC0:
        .string "abcd"
em():
        push    rbp
        mov     rbp, rsp
    
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:vs[abi:cxx11]
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::emplace_back<char const (&) [5]>(char const (&) [5])
        nop

        pop     rbp
        ret

5.虚函数加上const到底调用哪个

#include <iostream>

class Base {
public:
    virtual void display() const {
        std::cout << "I am Base class!" << std::endl;
    }

    virtual ~Base() {
    }
};

class Derive : public Base {
public:
    virtual void display() {
        std::cout << "I am Derive class!" << std::endl;
    }

    virtual ~Derive() {
    }
};

int main() {
    Base* pBase = new Derive();
    Derive* pDerive = new Derive();

    pBase->display();
    pDerive->display();

    delete pBase;
    delete pDerive;

    return 0;
}

基类使用了const,正确结果是 I am Base class! I am Derive class! 虚函数的要求是,函数原型相同,函数原型包括:函数返回值、函数名、参数列表、const修饰符。这里const修饰符包括函数返回值的修饰,函数形参的修饰,函数本身的修饰。只要有一处没有对上 ,那么就不是虚函数的override,而是调用基类的同名函数。所以对于基类的cosnt虚函数,如果子类重写忘记加上const,编译器会认为是基类的函数。 链接:https://blog.csdn.net/qq_66089313/article/details/131330739



🥁-CPP Share Tweet +1