鱼C论坛

 找回密码
 立即注册
查看: 2024|回复: 17

[已解决]小甲鱼s1e39测试题第6题,求大佬解答一下

[复制链接]
发表于 2022-10-17 20:24:17 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
求大佬帮忙解答一下,忙活半天总算是弄出小甲鱼讲的结果a=1,b=2了
但是!必须得打印地址之后才能出现在这个结果
如果注释掉打印地址语句,就会变回a=0,b=2了
printf应该不影响内存分配吧,求大佬解答
最佳答案
2022-10-18 18:32:09
本帖最后由 jackz007 于 2022-10-18 19:12 编辑
Xianjing. 发表于 2022-10-18 17:33
我没有注释那一张打印了a的地址,显示的就是a在b之后一个字节的位置,这个时候打印出来就是1,2,但是如 ...


          原因终于被我找到了,看看两个可执行代码的 main() 函数反汇编
【有打印地址版本】:
00401350  /$  55                         push    ebp
00401351  |.  89E5                       mov     ebp, esp
00401353  |.  83E4 F0                    and     esp, FFFFFFF0
00401356  |.  83EC 20                    sub     esp, 20
00401359  |.  E8 22060000                call    00401980
0040135E  |.  C64424 1B 00               mov     byte ptr [esp+1B], 0          ; char a = 0
00401363  |?  C64424 1A 00               mov     byte ptr [esp+1A], 0          ; char b = 0
00401368  |?  8D4424 1A                  lea     eax, dword ptr [esp+1A]       ; eax = & b
0040136C  |?  894424 1C                  mov     dword ptr [esp+1C], eax       ; p = & b
00401370  |?  8B4424 1C                  mov     eax, dword ptr [esp+1C]       ; eax = & b
00401374  |?  C700 02010000              mov     dword ptr [eax], 102          ; * p = 258                                       
0040137A  |.  0FB64424 1A                movzx   eax, byte ptr [esp+1A]        ; eax = a                                     
0040137F  |?  0FBED0                     movsx   edx, al                       ; edx = a
00401382  |?  0FB64424 1B                movzx   eax, byte ptr [esp+1B]        ; eax = b
00401387  |?  0FBEC0                     movsx   eax, al                       ; eax = b
0040138A  |?  895424 08                  mov     dword ptr [esp+8], edx        ; edx = b = 2 进堆栈 - 准备打印
0040138E  |?  894424 04                  mov     dword ptr [esp+4], eax        ; eax = a = 1 进堆栈 - 准备打印
00401392  |?  C70424 24304000            mov     dword ptr [esp], 00403024     ;  ASCII "%d %d"
00401399   ?  E8 52080000                call    <jmp.&msvcrt.printf>          ; 调用 printf()
0040139E   .  8D4424 1A                  lea     eax, dword ptr [esp+1A]
004013A2   ?  894424 08                  mov     dword ptr [esp+8], eax
004013A6   ?  8D4424 1B                  lea     eax, dword ptr [esp+1B]
004013AA   ?  894424 04                  mov     dword ptr [esp+4], eax
004013AE   .  C70424 2B304000            mov     dword ptr [esp], 0040302B     ;  ASCII "%p %p"
004013B5   .  E8 36080000                call    <jmp.&msvcrt.printf>
004013BA   ?  B8 00000000                mov     eax, 0
004013BF   ?  C9                         leave
004013C0   >  C3                         retn
【无打印地址版本】:
00401350  /$  55                         push    ebp
00401351  |.  89E5                       mov     ebp, esp
00401353  |.  83E4 F0                    and     esp, FFFFFFF0
00401356  |.  83EC 20                    sub     esp, 20
00401359  |.  E8 02060000                call    00401960
0040135E  |.  C64424 1F 00               mov     byte ptr [esp+1F], 0          ; char a = 0
00401363  |.  C64424 17 00               mov     byte ptr [esp+17], 0          ; char b = 0
00401368  |.  8D4424 17                  lea     eax, dword ptr [esp+17]       ; eax = & b
0040136C  |.  894424 18                  mov     dword ptr [esp+18], eax       ; p = & b
00401370  |.  8B4424 18                  mov     eax, dword ptr [esp+18]       ; eax = & b
00401374  |.  C700 02010000              mov     dword ptr [eax], 102          ; * p = 258 
0040137A  |.  0FB64424 17                movzx   eax, byte ptr [esp+17]        ; eax = b
0040137F  |.  0FBED0                     movsx   edx, al                       ; edx = b
00401382  |.  0FBE4424 1F                movsx   eax, byte ptr [esp+1F]        ; eax = a
00401387  |.  895424 08                  mov     dword ptr [esp+8], edx        ; b 进堆栈 - 准备调用 printf()
0040138B  |.  894424 04                  mov     dword ptr [esp+4], eax        ; a 进堆栈 - 准备调用 printf()
0040138F  |.  C70424 24304000            mov     dword ptr [esp], 00403024     ; |ASCII "%d %d"
00401396  |.  E8 35080000                call    <jmp.&msvcrt.printf>          ; \printf
0040139B  |.  B8 00000000                mov     eax, 0
004013A0  |.  C9                         leave
004013A1  \.  C3                         retn
    主要聚焦下面的不同点:
【有打印地址版本】:
0040135E  |.  C64424 1B 00               mov     byte ptr [esp+1B], 0          ; char a = 0
00401363  |?  C64424 1A 00               mov     byte ptr [esp+1A], 0          ; char b = 0
【无打印地址版本】:
0040135E  |.  C64424 1F 00               mov     byte ptr [esp+1F], 0          ; char a = 0
00401363  |.  C64424 17 00               mov     byte ptr [esp+17], 0          ; char b = 0
       有打印地址版本的 a、b 内存地址符合预期,b 在前,a 在后,存储地址相差 1 个字节;无打印地址版本 a、b 内存地址不符合预期,虽然 b 在前,a 在后,符合预期,但是,存储地址却相差了 8 个字节,这样,当 * p = 258 覆盖内存的时候,只能覆盖 b,根本不法覆盖到 a,所以,打印 a、b 的时候,b 是覆盖后的值,a 是原始值,所以,就看到了 2 和 0。

       之所以有这种不同的结果,应该和编译器对局部变量的内存分配策略有关,我使用 gcc,和楼主在 Linux 系统下所使用的编译器同源,所以,可以重现楼主在 Linux 系统下看到的相同情况。

       基于以上分析结论,只要把 a、b 为 2 个 char 型变量的定义改为一个 2 元素 char 型数组即可完美解决问题
#include <stdio.h>

int main(void)
{
        char b[2] = {0}                   ;
        int * p = (int *) b               ;
        * p = 258                         ;
        printf("%d %d\n" , b[0] , b[1])   ;
        // printf("%p %p\n"  , & a , & b) ;
}
        楼主不妨一试
     
未注释.png
未注释结果.png
1499400b31abcd9f36326691a843637.png
fd3357e47ffaf3c2712d1cf8472b82f.png
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-10-17 20:27:40 | 显示全部楼层
         你不贴代码,怎么讨论问题?难道,需要我自己手敲全部代码然后再陪你讨论?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-17 20:35:20 | 显示全部楼层
建议 图文并茂。
还好是代码短。
应该是你自己运行了2段不同的代码,跟注不注释 打印地址那行没关系。
20221017_203352.png
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-17 20:48:31 | 显示全部楼层
本帖最后由 jackz007 于 2022-10-17 23:00 编辑
        char a = 0 , b = 0             ;
        int * p = (unsigned int *) & b ;
        * p = 258                      ;
        p 是整型指针,此操作会向变量 b 开始的内存写入整型数 258, 258 十六进制形式是 0x102,我们的电脑都是 Intel 架构,CPU 采用 little endian 模式,整型数 0x102 的 4 个字节在内存中是按:02 01 00 00 的先后顺序排列的,p 指向了 b,那么,b 对应的一个字节是 02 , a 和 b 相邻,如果 a 所占用的内存在 b 的后面,就应该对应字节 01,于是,就打印出了 2 和 1。
        如果打印的是 2 和 0,那么,说明 a 对应的内存并不在 b 的后面,而是在 b 的前面,具体在前在后,主要和编译器有关。所以,如果打印的是 2 和 0,那么,只要调整一下 a、b 的定义顺序就可以了:
        char b = 0 , a = 0             ;  // 把 b 放前面,a 放后面试试

        这个代码之所以需要打印 & a 和 & b 的地址,就是为了判断它们俩在内存中的位置到底谁前谁后,从楼主贴出的图片中可以看到,在输出是 2 和 1 的图片下方就是两个变量的内存地址,他们之间显然存在着 b 在前(0x7ffc9607d9b6),a 在后(0x7ffc9607d9b7),地址相差 1 个字节的关系。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-17 21:44:37 | 显示全部楼层
本帖最后由 jackz007 于 2022-10-17 23:03 编辑
ba21 发表于 2022-10-17 20:35
建议 图文并茂。
还好是代码短。
应该是你自己运行了2段不同的代码,跟注不注释 打印地址那行没关系。


          老大,你是什么编译器,char 型变量也讲究 4 字节对齐呀,楼主要的效果在你那里恐怕是无法看到了,你的 b 和 a 的内存地址居然相差了 4 个字节,完全的不可思议!
          看看楼主展示的图片,b(0x7ffc9607d9b6)、a (0x7ffc9607d9b7)内存地址必须只相差一个字节,而且,它们的内存位置必须是 b 在前,a 在后才能看到效果。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-10-18 17:31:01 | 显示全部楼层
jackz007 发表于 2022-10-17 20:27
你不贴代码,怎么讨论问题?难道,需要我自己手敲全部代码然后再陪你讨论?

抱歉抱歉,第一次用不知道怎么贴,我就粘图片了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-10-18 17:31:45 | 显示全部楼层
本帖最后由 Xianjing. 于 2022-10-18 17:35 编辑
ba21 发表于 2022-10-17 20:35
建议 图文并茂。
还好是代码短。
应该是你自己运行了2段不同的代码,跟注不注释 打印地址那行没关系。


我两段代码并没有重新敲啊,就是加了两个斜杠注释掉那句话,结果就变了,所以很不解,还有就是不理解你的这个char为什么占用了四个字节
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-10-18 17:33:48 | 显示全部楼层
jackz007 发表于 2022-10-17 20:48
p 是整型指针,此操作会向变量 b 开始的内存写入整型数 258, 258 十六进制形式是 0x102,我们的 ...

我没有注释那一张打印了a的地址,显示的就是a在b之后一个字节的位置,这个时候打印出来就是1,2,但是如果注释掉,就只加了两个斜杠,结果就变了,很不理解,不过小甲鱼的答案原理我能看懂,很感谢你的耐心解答
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-18 17:45:14 | 显示全部楼层
Xianjing. 发表于 2022-10-18 17:31
我两段代码并没有重新敲啊,就是加了两个斜杠注释掉那句话,结果就变了,所以很不解,还有就是不理解你 ...

char 哪里占了4个字节了?你从哪里看出来的?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-18 18:32:09 | 显示全部楼层    本楼为最佳答案   
本帖最后由 jackz007 于 2022-10-18 19:12 编辑
Xianjing. 发表于 2022-10-18 17:33
我没有注释那一张打印了a的地址,显示的就是a在b之后一个字节的位置,这个时候打印出来就是1,2,但是如 ...


          原因终于被我找到了,看看两个可执行代码的 main() 函数反汇编
【有打印地址版本】:
00401350  /$  55                         push    ebp
00401351  |.  89E5                       mov     ebp, esp
00401353  |.  83E4 F0                    and     esp, FFFFFFF0
00401356  |.  83EC 20                    sub     esp, 20
00401359  |.  E8 22060000                call    00401980
0040135E  |.  C64424 1B 00               mov     byte ptr [esp+1B], 0          ; char a = 0
00401363  |?  C64424 1A 00               mov     byte ptr [esp+1A], 0          ; char b = 0
00401368  |?  8D4424 1A                  lea     eax, dword ptr [esp+1A]       ; eax = & b
0040136C  |?  894424 1C                  mov     dword ptr [esp+1C], eax       ; p = & b
00401370  |?  8B4424 1C                  mov     eax, dword ptr [esp+1C]       ; eax = & b
00401374  |?  C700 02010000              mov     dword ptr [eax], 102          ; * p = 258                                       
0040137A  |.  0FB64424 1A                movzx   eax, byte ptr [esp+1A]        ; eax = a                                     
0040137F  |?  0FBED0                     movsx   edx, al                       ; edx = a
00401382  |?  0FB64424 1B                movzx   eax, byte ptr [esp+1B]        ; eax = b
00401387  |?  0FBEC0                     movsx   eax, al                       ; eax = b
0040138A  |?  895424 08                  mov     dword ptr [esp+8], edx        ; edx = b = 2 进堆栈 - 准备打印
0040138E  |?  894424 04                  mov     dword ptr [esp+4], eax        ; eax = a = 1 进堆栈 - 准备打印
00401392  |?  C70424 24304000            mov     dword ptr [esp], 00403024     ;  ASCII "%d %d"
00401399   ?  E8 52080000                call    <jmp.&msvcrt.printf>          ; 调用 printf()
0040139E   .  8D4424 1A                  lea     eax, dword ptr [esp+1A]
004013A2   ?  894424 08                  mov     dword ptr [esp+8], eax
004013A6   ?  8D4424 1B                  lea     eax, dword ptr [esp+1B]
004013AA   ?  894424 04                  mov     dword ptr [esp+4], eax
004013AE   .  C70424 2B304000            mov     dword ptr [esp], 0040302B     ;  ASCII "%p %p"
004013B5   .  E8 36080000                call    <jmp.&msvcrt.printf>
004013BA   ?  B8 00000000                mov     eax, 0
004013BF   ?  C9                         leave
004013C0   >  C3                         retn
【无打印地址版本】:
00401350  /$  55                         push    ebp
00401351  |.  89E5                       mov     ebp, esp
00401353  |.  83E4 F0                    and     esp, FFFFFFF0
00401356  |.  83EC 20                    sub     esp, 20
00401359  |.  E8 02060000                call    00401960
0040135E  |.  C64424 1F 00               mov     byte ptr [esp+1F], 0          ; char a = 0
00401363  |.  C64424 17 00               mov     byte ptr [esp+17], 0          ; char b = 0
00401368  |.  8D4424 17                  lea     eax, dword ptr [esp+17]       ; eax = & b
0040136C  |.  894424 18                  mov     dword ptr [esp+18], eax       ; p = & b
00401370  |.  8B4424 18                  mov     eax, dword ptr [esp+18]       ; eax = & b
00401374  |.  C700 02010000              mov     dword ptr [eax], 102          ; * p = 258 
0040137A  |.  0FB64424 17                movzx   eax, byte ptr [esp+17]        ; eax = b
0040137F  |.  0FBED0                     movsx   edx, al                       ; edx = b
00401382  |.  0FBE4424 1F                movsx   eax, byte ptr [esp+1F]        ; eax = a
00401387  |.  895424 08                  mov     dword ptr [esp+8], edx        ; b 进堆栈 - 准备调用 printf()
0040138B  |.  894424 04                  mov     dword ptr [esp+4], eax        ; a 进堆栈 - 准备调用 printf()
0040138F  |.  C70424 24304000            mov     dword ptr [esp], 00403024     ; |ASCII "%d %d"
00401396  |.  E8 35080000                call    <jmp.&msvcrt.printf>          ; \printf
0040139B  |.  B8 00000000                mov     eax, 0
004013A0  |.  C9                         leave
004013A1  \.  C3                         retn
    主要聚焦下面的不同点:
【有打印地址版本】:
0040135E  |.  C64424 1B 00               mov     byte ptr [esp+1B], 0          ; char a = 0
00401363  |?  C64424 1A 00               mov     byte ptr [esp+1A], 0          ; char b = 0
【无打印地址版本】:
0040135E  |.  C64424 1F 00               mov     byte ptr [esp+1F], 0          ; char a = 0
00401363  |.  C64424 17 00               mov     byte ptr [esp+17], 0          ; char b = 0
       有打印地址版本的 a、b 内存地址符合预期,b 在前,a 在后,存储地址相差 1 个字节;无打印地址版本 a、b 内存地址不符合预期,虽然 b 在前,a 在后,符合预期,但是,存储地址却相差了 8 个字节,这样,当 * p = 258 覆盖内存的时候,只能覆盖 b,根本不法覆盖到 a,所以,打印 a、b 的时候,b 是覆盖后的值,a 是原始值,所以,就看到了 2 和 0。

       之所以有这种不同的结果,应该和编译器对局部变量的内存分配策略有关,我使用 gcc,和楼主在 Linux 系统下所使用的编译器同源,所以,可以重现楼主在 Linux 系统下看到的相同情况。

       基于以上分析结论,只要把 a、b 为 2 个 char 型变量的定义改为一个 2 元素 char 型数组即可完美解决问题
#include <stdio.h>

int main(void)
{
        char b[2] = {0}                   ;
        int * p = (int *) b               ;
        * p = 258                         ;
        printf("%d %d\n" , b[0] , b[1])   ;
        // printf("%p %p\n"  , & a , & b) ;
}
        楼主不妨一试
     
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2022-10-19 08:05:54 | 显示全部楼层
jackz007 发表于 2022-10-18 18:32
原因终于被我找到了,看看两个可执行代码的 main() 函数反汇编
【有打印地址版本】:

感谢感谢!!!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 08:40:09 From FishC Mobile | 显示全部楼层
本帖最后由 jackz007 于 2022-10-19 09:04 编辑
ba21 发表于 2022-10-18 17:45
char 哪里占了4个字节了?你从哪里看出来的?


      你贴的图片上有啊,a ( 0018ff44),b (0018ff40)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 17:34:07 | 显示全部楼层
jackz007 发表于 2022-10-19 08:40
你贴的图片上有啊,a ( 0018ff44),b (0018ff40)

32位地址不应该是4个字节吗?
64位地址不应该是8个字节吗?
那何来char占用了4个字节?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 17:40:33 | 显示全部楼层
本帖最后由 jackz007 于 2022-10-19 17:43 编辑
ba21 发表于 2022-10-19 17:34
32位地址不应该是4个字节吗?
64位地址不应该是8个字节吗?
那何来char占用了4个字节?


        看看 3 楼你自己贴的图片,难道不是你自己的代码用 "%p" 打印的 a 和 b 的内存地址吗?
        再说了,难道 64 位的操作系统就不能编译和运行 32 位的程序?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 17:44:13 | 显示全部楼层
jackz007 发表于 2022-10-19 17:40
难道不是你自己的代码用 "%p" 打印的 a 和 b 的内存地址吗?
        再说了,难道 64 位的操 ...

那打印地址 4个字节有何问题? 按你们说的应该是要打印1个字节才合理?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 17:49:32 | 显示全部楼层
本帖最后由 jackz007 于 2022-10-19 17:52 编辑
ba21 发表于 2022-10-19 17:44
那打印地址 4个字节有何问题? 按你们说的应该是要打印1个字节才合理?


        我说的是 a 和 b 的内存地址相差了 4 个字节很意外,这个距离本来应该是 1 个字节的,并不是说指针长度是 4 个字节有什么不对。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-19 19:06:52 | 显示全部楼层
jackz007 发表于 2022-10-19 17:49
我说的是 a 和 b 的内存地址相差了 4 个字节很意外,这个距离本来应该是 1 个字节的,并不是说 ...

编译器内存优化相关。
内存对齐知识。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-10-20 23:37:10 | 显示全部楼层
如果注释掉打印地址语句,就会变回a=0,b=2了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-27 11:51

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表