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
- 看到了,还是以C++类的___thiscall _优先,ecx传递this指针(第一个参数),edx传递第二个参数a,后面b参数入栈。
- 在 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