你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C语言中,未初始化的局部变量到底是多少?

[复制链接]
gaosmile 发布时间:2020-8-31 23:29
C语言中,未初始化的局部变量到底是多少?
答案往往是:
  • 与编译器有关。
  • 可能但不保证初始化为0。
  • 未确定。

总之,全部都是些一本正经的形而上答案,这很令人讨厌。
但凡一些人给你滔滔不绝地扯编译器,C库,处理器体系结构却给不出一个实际场景复现问题的时候,这人大概率在扯淡。
其实,这个问题本身就是错误的问法,说全了能讲10万字,我们只要能在特定场景下确定其特定行为就OK了,当然,这就需要设计一个比较OK的实验。
在演示一个实际代码行为之前,先给出一个知识,CPU不认识变量,更无法识别变量的名字,CPU只会从特定的内存位置取值或者将值存到特定的内存位置,因此当问一个变量的值是多少的时候,必须要知道这个变量对应的值被保存在什么地方。
来看下面的代码:

  1. #include <stdio.h>

  2. void func1()
  3. {
  4.   int a;
  5.   printf("func1:%d\n", a);
  6.   a = 12345;
  7. }

  8. void func2()
  9. {
  10.   int b;
  11.   printf("func2:%d\n", b);
  12. }

  13. void func4()
  14. {
  15.   int d;
  16.   printf("func3:%d\n", d);
  17. }

  18. void func3()
  19. {
  20.   int c;
  21.   printf("func3:%d\n", c);
  22.   c = 54321;
  23.   func4();
  24. }

  25. void test_call()
  26. {
  27.   func3();
  28. }

  29. int main(int argc, char **argv)
  30. {
  31.   func1();
  32.   func2();

  33.   test_call();
  34. }
复制代码
我们有func1~func4一共4个函数,其内部均有一个未初始化的局部变量,它们的值到底是多少呢?
对于这种局部变量,它们的值取决于:
  • 变量在栈中的位置。
  • 变量对应的栈位置在 之前 有没有被store过。

可以看到,上述第一点标记了一个内存位置,第二点则是代码的行为,也就是说,只要有代码去store对应的位置, 且后续的代码没有reset该位置的值的话,该位置就会保留着原先被store后的值。
验证非常简单,试一下就知道了:

  1. [root@localhost test]# ./a.out
  2. func1:0
  3. func2:12345
  4. func3:0
  5. func3:0
复制代码

  • 按照函数调用栈帧的变化,func1的局部变量a和func2的局部变量b显然是位于同一个位置的,在func1被调用时,这是一块新的内存(可能在进入main之前有栈帧到达过这个位置),a的值取决于调入内存该位置的页面对应偏移的初始值,这取决于操作系统:

  • 操作系统在分配给程序页面时可能会将页面clear为零页。

栈的分配不会涉及C库,这里显然并不涉及C库的行为,但类似malloc分配的内存则涉及C库了。
打印结果,a的值为0,我们认为操作系统返回给了应用程序零页。接下来在func1中将其赋值12345之后函数返回,接下来调用func2的时候,在之前func1已经退出的栈帧位置重建栈帧,对应位置依然还是12345。
我没有看到func1的ret操作后面有stack清0的代码指令。效率考虑,也不该有这样的指令。
再看test_call函数,很明显,func3和func4调用使用的并不是同一个栈帧,因此即便是在func3中对c赋值了54321,也不会影响在其栈帧之上的func4的栈帧对应位置的值d。因此c和d的初始值均保持为0。
那么,初始化一个局部变量和不初始化一个局部变量,在指令层面上,区别在哪里呢?
很简单,亲眼看一下就知道,先看未初始化局部变量的func1:

  1. // int a;
  2. 00000000004005ad <func1>:
  3.   4005ad:   55                      push   %rbp
  4.   4005ae:   48 89 e5                mov    %rsp,%rbp
  5.   4005b1:   48 83 ec 10             sub    $0x10,%rsp
  6.   4005b5:   8b 45 fc                mov    -0x4(%rbp),%eax
  7.   4005b8:   89 c6                   mov    %eax,%esi
  8.   4005ba:   bf 90 07 40 00          mov    $0x400790,%edi
  9.   4005bf:   b8 00 00 00 00          mov    $0x0,%eax
  10.   4005c4:   e8 b7 fe ff ff          callq  400480 <printf@plt>
  11.   4005c9:   c7 45 fc 39 30 00 00    movl   $0x3039,-0x4(%rbp)
  12.   4005d0:   c9                      leaveq
  13.   4005d1:   c3                      retq
复制代码

  • 再看初始化局部变量a为2222的版本:

    1. // int a = 2222;
    2. 00000000004005ad <func1>:
    3.   4005ad:   55                      push   %rbp
    4.   4005ae:   48 89 e5                mov    %rsp,%rbp
    5.   4005b1:   48 83 ec 10             sub    $0x10,%rsp
    6.   4005b5:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
    7.   4005bc:   8b 45 fc                mov    -0x4(%rbp),%eax
    8.   4005bf:   89 c6                   mov    %eax,%esi
    9.   4005c1:   bf 90 07 40 00          mov    $0x400790,%edi
    10.   4005c6:   b8 00 00 00 00          mov    $0x0,%eax
    11.   4005cb:   e8 b0 fe ff ff          callq  400480 <printf@plt>
    12.   4005d0:   c7 45 fc 39 30 00 00    movl   $0x3039,-0x4(%rbp)
    13.   4005d7:   c9                      leaveq
    14.   4005d8:   c3                      retq
    复制代码
  • 仅仅差了一条指令:

    1. 4005b5:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
    复制代码


初始化的操作是依靠实实在在的指令完成的。
总结一句, 函数返回在pop出当前栈帧的时候,并不会清理它遗留在栈帧里的数据,下个函数调用再次重用到该栈帧的内存时,未初始化的局部变量将会被遗留数据影响,从而变得不确定!
所以,记得初始化你的局部变量。

收藏 评论0 发布时间:2020-8-31 23:29

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版