Xianjing. 发表于 2022-10-17 20:24:17

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

求大佬帮忙解答一下,忙活半天总算是弄出小甲鱼讲的结果a=1,b=2了
但是!必须得打印地址之后才能出现在这个结果
如果注释掉打印地址语句,就会变回a=0,b=2了
printf应该不影响内存分配吧,求大佬解答

jackz007 发表于 2022-10-17 20:27:40

         你不贴代码,怎么讨论问题?难道,需要我自己手敲全部代码然后再陪你讨论?

ba21 发表于 2022-10-17 20:35:20

建议 图文并茂。
还好是代码短。
应该是你自己运行了2段不同的代码,跟注不注释 打印地址那行没关系。

jackz007 发表于 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 个字节的关系。

jackz007 发表于 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 在后才能看到效果。

Xianjing. 发表于 2022-10-18 17:31:01

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

抱歉抱歉,第一次用不知道怎么贴,我就粘图片了

Xianjing. 发表于 2022-10-18 17:31:45

本帖最后由 Xianjing. 于 2022-10-18 17:35 编辑

ba21 发表于 2022-10-17 20:35
建议 图文并茂。
还好是代码短。
应该是你自己运行了2段不同的代码,跟注不注释 打印地址那行没关系。

我两段代码并没有重新敲啊,就是加了两个斜杠注释掉那句话,结果就变了,所以很不解,还有就是不理解你的这个char为什么占用了四个字节

Xianjing. 发表于 2022-10-18 17:33:48

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

我没有注释那一张打印了a的地址,显示的就是a在b之后一个字节的位置,这个时候打印出来就是1,2,但是如果注释掉,就只加了两个斜杠,结果就变了,很不理解,不过小甲鱼的答案原理我能看懂,很感谢你的耐心解答

ba21 发表于 2022-10-18 17:45:14

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

char 哪里占了4个字节了?你从哪里看出来的?

jackz007 发表于 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 , 0          ; char a = 0
00401363|?C64424 1A 00               mov   byte ptr , 0          ; char b = 0
00401368|?8D4424 1A                  lea   eax, dword ptr        ; eax = & b
0040136C|?894424 1C                  mov   dword ptr , eax       ; p = & b
00401370|?8B4424 1C                  mov   eax, dword ptr        ; eax = & b
00401374|?C700 02010000            mov   dword ptr , 102          ; * p = 258                                       
0040137A|.0FB64424 1A                movzx   eax, byte ptr       ; eax = a                                    
0040137F|?0FBED0                     movsx   edx, al                     ; edx = a
00401382|?0FB64424 1B                movzx   eax, byte ptr       ; eax = b
00401387|?0FBEC0                     movsx   eax, al                     ; eax = b
0040138A|?895424 08                  mov   dword ptr , edx      ; edx = b = 2 进堆栈 - 准备打印
0040138E|?894424 04                  mov   dword ptr , eax      ; eax = a = 1 进堆栈 - 准备打印
00401392|?C70424 24304000            mov   dword ptr , 00403024   ;ASCII "%d %d"
00401399   ?E8 52080000                call    <jmp.&msvcrt.printf>          ; 调用 printf()
0040139E   .8D4424 1A                  lea   eax, dword ptr
004013A2   ?894424 08                  mov   dword ptr , eax
004013A6   ?8D4424 1B                  lea   eax, dword ptr
004013AA   ?894424 04                  mov   dword ptr , eax
004013AE   .C70424 2B304000            mov   dword ptr , 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 , 0          ; char a = 0
00401363|.C64424 17 00               mov   byte ptr , 0          ; char b = 0
00401368|.8D4424 17                  lea   eax, dword ptr        ; eax = & b
0040136C|.894424 18                  mov   dword ptr , eax       ; p = & b
00401370|.8B4424 18                  mov   eax, dword ptr        ; eax = & b
00401374|.C700 02010000            mov   dword ptr , 102          ; * p = 258
0040137A|.0FB64424 17                movzx   eax, byte ptr       ; eax = b
0040137F|.0FBED0                     movsx   edx, al                     ; edx = b
00401382|.0FBE4424 1F                movsx   eax, byte ptr       ; eax = a
00401387|.895424 08                  mov   dword ptr , edx      ; b 进堆栈 - 准备调用 printf()
0040138B|.894424 04                  mov   dword ptr , eax      ; a 进堆栈 - 准备调用 printf()
0040138F|.C70424 24304000            mov   dword ptr , 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 , 0          ; char a = 0
00401363|?C64424 1A 00               mov   byte ptr , 0          ; char b = 0
【无打印地址版本】:
0040135E|.C64424 1F 00               mov   byte ptr , 0          ; char a = 0
00401363|.C64424 17 00               mov   byte ptr , 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 = {0}                   ;
      int * p = (int *) b               ;
      * p = 258                         ;
      printf("%d %d\n" , b , b)   ;
      // printf("%p %p\n", & a , & b) ;
}
      楼主不妨一试
   

Xianjing. 发表于 2022-10-19 08:05:54

jackz007 发表于 2022-10-18 18:32
原因终于被我找到了,看看两个可执行代码的 main() 函数反汇编
【有打印地址版本】:



感谢感谢!!!

jackz007 发表于 2022-10-19 08:40:09

本帖最后由 jackz007 于 2022-10-19 09:04 编辑

ba21 发表于 2022-10-18 17:45
char 哪里占了4个字节了?你从哪里看出来的?

      你贴的图片上有啊,a ( 0018ff44),b (0018ff40)

ba21 发表于 2022-10-19 17:34:07

jackz007 发表于 2022-10-19 08:40
你贴的图片上有啊,a ( 0018ff44),b (0018ff40)

32位地址不应该是4个字节吗?
64位地址不应该是8个字节吗?
那何来char占用了4个字节?

jackz007 发表于 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 位的程序?

ba21 发表于 2022-10-19 17:44:13

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

那打印地址 4个字节有何问题? 按你们说的应该是要打印1个字节才合理?

jackz007 发表于 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 个字节有什么不对。

ba21 发表于 2022-10-19 19:06:52

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

编译器内存优化相关。
内存对齐知识。

china2022 发表于 2022-10-20 23:37:10

如果注释掉打印地址语句,就会变回a=0,b=2了
页: [1]
查看完整版本: 小甲鱼s1e39测试题第6题,求大佬解答一下