c++ 代码块的局部成员栈布局

本文探讨了C++代码块中局部成员在栈上的布局,通过对比Win32和ARM64平台的汇编代码,发现无论是否有析构函数,局部成员在函数开始时已分配栈空间。析构方法的调用发生在作用域结束时,由编译器自动处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

c++ 代码块的局部成员栈布局

背景:

c++ 代码块是非常常用的方式, 例如if 分支,循环分支,还有些加锁的时候需要,锁的力度比较小的时候,都需要代码块,理解函数栈对你理解协程也有一定的帮助,可以说代码块在c++开发的各个方面,但是c++代码块的局部成员,在栈中怎么布局的,一直比较模糊,本次从win32,arm64不同的平台下都去看下代码块的栈 布局

测试代码

首先看下测试代码

C++
int TestStackA()
{
        int a = 0x55;
        int b = 0xaa;
        int c = 0xcc;
        int d = 0xee;
        int e = 0x11;
        return 0x1234;
}

这个代码段很简单,没有代码块,我们转成汇编看下

C++
00A315F0  push        ebp 
00A315F1  mov         ebp,esp 
00A315F3  sub         esp,0FCh;分配栈空间 
00A315F9  push        ebx 
00A315FA  push        esi 
00A315FB  push        edi 
00A315FC  lea         edi,[ebp+FFFFFF04h] 
00A31602  mov         ecx,3Fh 
00A31607  mov         eax,0CCCCCCCCh  ;把分配的栈空间设置成cccccccc
00A3160C  rep stos    dword ptr es:[edi] 
00A3160E  mov         dword ptr [ebp-8],55h  ;a
00A31615  mov         dword ptr [ebp-14h],0AAh ;b
00A3161C  mov         dword ptr [ebp-20h],0CCh  ;c
00A31623  mov         dword ptr [ebp-2Ch],0EEh  ;d
00A3162A  mov         dword ptr [ebp-38h],11h  ;e
00A31631  mov         eax,1234h  ;返回值
00A31636  pop         edi 
00A31637  pop         esi 
00A31638  pop         ebx 
00A31639  mov         esp,ebp 
00A3163B  pop         ebp 
00A3163C  ret 

这段汇编很简单,接下来我们对比TestB代码块的汇编

C++
int TestStackB()
{
        int a = 0x55;
        int b = 0xaa;
        {
                int c = 0xcc;
                {
                        int d = 0xee;
                }
               
        }
        int e = 0x11;
        return 0x1234;
}

C++
00A31640  push        ebp 
00A31641  mov         ebp,esp 
00A31643  sub         esp,0FCh 
00A31649  push        ebx 
00A3164A  push        esi 
00A3164B  push        edi 
00A3164C  lea         edi,[ebp+FFFFFF04h] 
00A31652  mov         ecx,3Fh 
00A31657  mov         eax,0CCCCCCCCh 
00A3165C  rep stos    dword ptr es:[edi] 
00A3165E  mov         dword ptr [ebp-8],55h 
00A31665  mov         dword ptr [ebp-14h],0AAh 
00A3166C  mov         dword ptr [ebp-20h],0CCh 
00A31673  mov         dword ptr [ebp-2Ch],0EEh 
00A3167A  mov         dword ptr [ebp-38h],11h 
00A31681  mov         eax,1234h 
00A31686  pop         edi 
00A31687  pop         esi 
00A31688  pop         ebx 
00A31689  mov         esp,ebp 
00A3168B  pop         ebp 
00A3168C  ret

    你会惊奇的发现栈空间布局是一摸一样的,也就是说块代码的成员变量,在函数进来的分配栈空间的时候就已经分配好了

接下来我们看下带析构方法的栈空间是什么样的

C++
int TestStackA()
{
        int a = 0x55;
        int b = 0xaa;
        int c = 0xcc;
        std::string str;
        int d = 0xee;
        int e = 0x11;
        return 0x1234;
}
 

C++
00BC15F0  push        ebp 
00BC15F1  mov         ebp,esp 
00BC15F3  sub         esp,130h 
.......
00BC1615  mov         dword ptr [ebp-4],eax 
00BC1618  mov         dword ptr [ebp-0Ch],55h 
00BC161F  mov         dword ptr [ebp-18h],0AAh 
00BC1626  mov         dword ptr [ebp-24h],0CCh 
00BC162D  lea         ecx,[ebp-48h] 
00BC1630  call        00BA6640  ;调用str构造方法
00BC1635  mov         dword ptr [ebp-54h],0EEh 
00BC163C  mov         dword ptr [ebp-60h],11h ;int e
00BC1643  mov         dword ptr [ebp+FFFFFED4h],1234h 
00BC164D  lea         ecx,[ebp-48h] 
00BC1650  call        00B70A80  ;调用str的析构方法
00BC1655  mov         eax,dword ptr [ebp+FFFFFED4h] 
00BC165B  push        edx 
00BC165C  mov         ecx,ebp 
00BC165E  push        eax 
00BC165F  lea         edx,ds:[00BC168Ch] 
00BC1665  call        017FDCE0 

可以看出来的str析构方法是在int e = 0x11;赋值语句之后的,接下来我们看下另外代码块的str具体汇编

C++
int TestStackB()
{
        int a = 0x55;
        int b = 0xaa;
        {
                int c = 0xcc;
                {
                        std::string str;
                        int d = 0xee;
                }
               
        }
        int e = 0x11;
        return 0x1234;
}

C++
00BC16B0  push        ebp 
00BC16B1  mov         ebp,esp 
00BC16B3  ......
00BC16D5  mov         dword ptr [ebp-4],eax 
00BC16D8  mov         dword ptr [ebp-0Ch],55h 
00BC16DF  mov         dword ptr [ebp-18h],0AAh 
00BC16E6  mov         dword ptr [ebp-24h],0CCh 
00BC16ED  lea         ecx,[ebp-48h] 
00BC16F0  call        00BA6640  ;调用str构造方法
00BC16F5  mov         dword ptr [ebp-54h],0EEh 
00BC16FC  lea         ecx,[ebp-48h] 
00BC16FF  call        00B70A80  ;调用str析构方法
00BC1704  mov         dword ptr [ebp-60h],11h  ;int e
00BC170B  mov         eax,1234h 
00BC1710  push        edx 
00BC1711  mov         ecx,ebp 
00BC1713  push        eax 

可以看的出来 str析构方法在是int e之前调用的,也就是说出作用于调用析构方法,完全是编译器编译的发生的,而真正的内存在函数栈分配的时候就已经有了

接下来我们看下arm64的栈空间是什么样的

c代码还是上边的

TestStackA的arm64汇编

C++
    0x100001408 <+0>:  sub    sp, sp, #0x20
    0x10000140c <+4>:  mov    w8, #0x55
    0x100001410 <+8>:  str    w8, [sp, #0x1c]
    0x100001414 <+12>: mov    w8, #0xaa
    0x100001418 <+16>: str    w8, [sp, #0x18]
    0x10000141c <+20>: mov    w8, #0xcc
    0x100001420 <+24>: str    w8, [sp, #0x14]
    0x100001424 <+28>: mov    w8, #0xee
    0x100001428 <+32>: str    w8, [sp, #0x10]
    0x10000142c <+36>: mov    w8, #0x11
->  0x100001430 <+40>: str    w8, [sp, #0xc]
    0x100001434 <+44>: mov    w0, #0x1234
    0x100001438 <+48>: add    sp, sp, #0x20
    0x10000143c <+52>: ret   

TestStackB的arm64汇编

C++
    0x100001440 <+0>:  sub    sp, sp, #0x20
    0x100001444 <+4>:  mov    w8, #0x55
    0x100001448 <+8>:  str    w8, [sp, #0x1c]
    0x10000144c <+12>: mov    w8, #0xaa
    0x100001450 <+16>: str    w8, [sp, #0x18]
    0x100001454 <+20>: mov    w8, #0xcc
    0x100001458 <+24>: str    w8, [sp, #0x14]
    0x10000145c <+28>: mov    w8, #0xee
    0x100001460 <+32>: str    w8, [sp, #0x10]
    0x100001464 <+36>: mov    w8, #0x11
->  0x100001468 <+40>: str    w8, [sp, #0xc]
    0x10000146c <+44>: mov    w0, #0x1234
    0x100001470 <+48>: add    sp, sp, #0x20
    0x100001474 <+52>: ret   
 

可以看的出来一模一样

我们在看下 带str的汇编

C++
test`TestStackA:
    0x1000052d8 <+0>:  sub    sp, sp, #0x50
    0x1000052dc <+4>:  stp    x29, x30, [sp, #0x40]
    0x1000052e0 <+8>:  add    x29, sp, #0x40
    0x1000052e4 <+12>: mov    w8, #0x55
    0x1000052e8 <+16>: stur   w8, [x29, #-0x4]
    0x1000052ec <+20>: mov    w8, #0xaa
    0x1000052f0 <+24>: stur   w8, [x29, #-0x8]
    0x1000052f4 <+28>: mov    w8, #0xcc
    0x1000052f8 <+32>: stur   w8, [x29, #-0xc]
    0x1000052fc <+36>: mov    w8, #0xee
    0x100005300 <+40>: stur   w8, [x29, #-0x10]
    0x100005304 <+44>: add    x0, sp, #0x18
    0x100005308 <+48>: str    x0, [sp, #0x8]
    0x10000530c <+52>: bl     0x100005330               ; std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string at string:1832
    0x100005310 <+56>: ldr    x0, [sp, #0x8]
    0x100005314 <+60>: mov    w8, #0x11;int e
->  0x100005318 <+64>: str    w8, [sp, #0x14]
    0x10000531c <+68>: bl     0x100007b18               ; symbol stub for: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string()
    0x100005320 <+72>: mov    w0, #0x1234
    0x100005324 <+76>: ldp    x29, x30, [sp, #0x40]
    0x100005328 <+80>: add    sp, sp, #0x50
    0x10000532c <+84>: ret

可以看的出来str析构方法是在mov    w8, #0x11 也就是int e之后

C++
test`TestStackB:
    0x10000535c <+0>:  sub    sp, sp, #0x50
    0x100005360 <+4>:  stp    x29, x30, [sp, #0x40]
    0x100005364 <+8>:  add    x29, sp, #0x40
    0x100005368 <+12>: mov    w8, #0x55
    0x10000536c <+16>: stur   w8, [x29, #-0x4]
    0x100005370 <+20>: mov    w8, #0xaa
    0x100005374 <+24>: stur   w8, [x29, #-0x8]
    0x100005378 <+28>: mov    w8, #0xcc
    0x10000537c <+32>: stur   w8, [x29, #-0xc]
    0x100005380 <+36>: add    x0, sp, #0x18
    0x100005384 <+40>: str    x0, [sp, #0x8]
    0x100005388 <+44>: bl     0x100005330               ; std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string at string:1832
    0x10000538c <+48>: ldr    x0, [sp, #0x8]
    0x100005390 <+52>: mov    w8, #0xee
    0x100005394 <+56>: str    w8, [sp, #0x14]
    0x100005398 <+60>: bl     0x100007b18               ; symbol stub for: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string()
    0x10000539c <+64>: mov    w8, #0x11;int e
->  0x1000053a0 <+68>: str    w8, [sp, #0x10]
    0x1000053a4 <+72>: mov    w0, #0x1234
    0x1000053a8 <+76>: ldp    x29, x30, [sp, #0x40]
    0x1000053ac <+80>: add    sp, sp, #0x50
    0x1000053b0 <+84>: ret   
 

可以看的出来str析构方法是在mov    w8, #0x11 也就是int e之前

总结:

arm64 做法跟win32基本是一致的,代码块里的局部成员的空间在函数进来的时候,分配函数栈空间的时候就已经分配出来的, 代码块的成员析构方法在出作用域的时候会调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值