这是一个伤心的故事,有多少小伙伴在写C语言代码时遇到过的情景!

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

答案往往是:

与编译器有关;

可能但不保证初始化为0;

未确定。

总之,全部都是些一本正经的形而上答案,这很令人讨厌。

但凡一些人给你滔滔不绝地扯编译器,C库,处理器体系结构却给不出一个实际场景复现问题的时候,这人大概率在扯淡。

其实,这个问题本身就是错误的问法,说全了能讲10万字,我们只要能在特定场景下确定其特定行为就OK了,当然,这就需要设计一个比较OK的实验。

在演示一个实际代码行为之前,先给出一个知识,CPU不认识变量,更无法识别变量的名字,CPU只会从特定的内存位置取值或者将值存到特定的内存位置,因此当问一个变量的值是多少的时候,必须要知道这个变量对应的值被保存在什么地方。

来看下面的代码:

#include <stdio.h>

void func1()

{

  int a;

  printf("func1:%d\n", a);

  a = 12345;

}

void func2()

{

  int b;

  printf("func2:%d\n", b);

}

void func4()

{

  int d;

  printf("func3:%d\n", d);

}

void func3()

{

  int c;

  printf("func3:%d\n", c);

  c = 54321;

  func4();

}

void test_call()

{

  func3();

}

int main(int argc, char **argv)

{

  func1();

  func2();

  test_call();

}

——————————

我们有func1~func4一共4个函数,其内部均有一个未初始化的局部变量,它们的值到底是多少呢?

对于这种局部变量,它们的值取决于:

变量在栈中的位置。

变量对应的栈位置在 之前 有没有被store过。

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

验证非常简单,试一下就知道了:

[root@localhost test]# ./a.out

func1:0

func2:12345

func3:0

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:

// int a;

00000000004005ad <func1>:

4005ad:  55                      push  %rbp

4005ae:  48 89 e5                mov    %rsp,%rbp

4005b1:  48 83 ec 10            sub    $0x10,%rsp

4005b5:  8b 45 fc                mov    -0x4(%rbp),%eax

4005b8:  89 c6                  mov    %eax,%esi

4005ba:  bf 90 07 40 00          mov    $0x400790,%edi

4005bf:  b8 00 00 00 00          mov    $0x0,%eax

4005c4:  e8 b7 fe ff ff          callq  400480 <printf@plt>

4005c9:  c7 45 fc 39 30 00 00    movl  $0x3039,-0x4(%rbp)

4005d0:  c9                      leaveq

4005d1:  c3                      retq

————————————

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

// int a = 2222;

00000000004005ad <func1>:

4005ad:  55                      push  %rbp

4005ae:  48 89 e5                mov    %rsp,%rbp

4005b1:  48 83 ec 10            sub    $0x10,%rsp

4005b5:  c7 45 fc 00 00 00 00    movl  $0x0,-0x4(%rbp)

4005bc:  8b 45 fc                mov    -0x4(%rbp),%eax

4005bf:  89 c6                  mov    %eax,%esi

4005c1:  bf 90 07 40 00          mov    $0x400790,%edi

4005c6:  b8 00 00 00 00          mov    $0x0,%eax

4005cb:  e8 b0 fe ff ff          callq  400480 <printf@plt>

4005d0:  c7 45 fc 39 30 00 00    movl  $0x3039,-0x4(%rbp)

4005d7:  c9                      leaveq

4005d8:  c3                      retq

————————————

仅仅差了一条指令:

4005b5: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)

————————————

初始化的操作是依靠实实在在的指令完成的。

总结一句:

函数返回在pop出当前栈帧的时候,并不会清理它遗留在栈帧里的数据,下个函数调用再次重用到该栈帧的内存时,未初始化的局部变量将会被遗留数据影响,从而变得不确定!

所以,记得初始化你的局部变量。

————————————

看到这里你是不是对编程语言又有了一点新的认知呢~

如果你喜欢这篇文章的话,动动小指,点个赞再走~

如果你想学编程,小编推荐一个C语言/C++编程学习基地【下图进入】!

 

一个活跃、高逼格、高层次的编程学习殿堂;编程入门只是顺带,思维的提高才有价值!

涉及:编程入门、游戏编程、网络编程、Windows编程、Linux编程、Qt界面开发、黑客等等......

C语言必踩神坑,世上本没有坑,摔的人多了,也就有了坑!的更多相关文章

  1. C语言必背18个经典程序

    C语言必背18个经典程序 1./*输出9*9口诀.共9行9列,i控制行,j控制列.*/ #include "stdio.h" main() {int i,j,result; for ...

  2. c语言学习书籍推荐《C语言学习路线图•C语言必须知道的300个问题》下载

    下载地址:点我 <C语言学习路线图•C语言必须知道的300个问题>以基础知识为框架,介绍了c语言各部分知识所对应的常见开发疑难问题,并作了透彻地解析.<C语言学习路线图•C语言必须知 ...

  3. 小白学习C语言必背的18个经典程序

    1./*输出9*9口诀.共9行9列,i控制行,j控制列.*/ #include "stdio.h" main() {int i,j,result; for (i=1;i<10 ...

  4. C语言那年踩过的坑--局部变量,静态变量,全局变量在内存中存放的位置

    先看几个概念: 1.bss是英文block started by symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0.bss段属于静态内存分配.它的初始 ...

  5. C语言必掌握知识点

    个人总结,学c的赶快看 1-.++a 和 a++ 的差别:           ++a  先加在赋值  a++ 先赋值在加  后者赋给变量b的值为a而不是a+1后的值 2-.按位与  同为1时为1,其 ...

  6. C语言必会面试题(3、耶稣有13个门徒,当中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个開始报号:1,2,3,1,2,3...。凡是报到“3”就退出圈子,...)

    3.耶稣有13个门徒.当中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个開始报号:1.2,3.1,2,3.... 凡是报到"3"就退出圈子.最后留在圈子 ...

  7. 新手必踩坑之display: inline-block

    今日励志语 往日不可追,来日犹可期,祝大家2019年继往开来 迷之间隙 我们创建一个导航列表,并将其列表 item 设置为 inline-block,主要代码如下: <div class=&qu ...

  8. 【踩坑记录】记一次MySQL主从复制延迟的坑

    最近开发中遇到的一个MySQL主从延迟的坑,记录并总结,避免再次犯同样的错误. 情景 一个活动信息需要审批,审批之后才能生效.因为之后活动要编辑,编辑后也可能触发审批,审批中展示的是编辑前的活动内容, ...

  9. 用C语言协助办公_01 找出所有不对劲的人

    近期想出一系列用C语言协助办公的视频教程,这是第一个.具体的移步:https://chuanke.baidu.com/v6658388-240377-1789288.html

随机推荐

  1. leetcode刷题-88.合并两个有序数组

    题目 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组. 说明: 初始化 nums1 和 nums2 的元素数量分别为 m ...

  2. Jenkins下Vue自动部署(一)

    1,服务器上安装docker http://www.runoob.com/docker/ubuntu-docker-install.html?tdsourcetag=s_pctim_aiomsg 2, ...

  3. linux系统jdk安装

    1.软件包下载:官网 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html下载linu ...

  4. python中绝对值的表达式

    abs(x) print(abs(23)) #23print(abs(-45)) #45  

  5. [补题]求a[i]+a[j]+i-j的最大值,要求i<j

    题目 如题. 例: 输入: 5 11 6 5 18 12 输出: 29 题解 思路: 一直是按着(a[i]+a[j])+(i-j)想后序的思路,不应该限制住自己,应该多考虑拆的方法.正确思路是把a[i ...

  6. Mybatis注解开发案例(入门)

    1.创建maven工程,配置pom.xml 文件. 2.创建实体类 3.创建dao接口 4.创建主配置文件SqlMapConfig.xml 5.在SqlMapConfig.xml中导入外部配置文件jd ...

  7. RabbitMQ消息积压的几种解决思路

    在日常工作中使用RabbitMQ偶尔会遇不可预料的情况导致的消息积压,一般出现消息积压基本上分为几种情况: 消费者消费消息的速度赶不上生产速度,这总问题主要是业务逻辑没设计好消费者和生产者之间的平衡, ...

  8. hw小技巧(转载)

    小弟也第一次参加hw,经过5天hw,确实也学到了许多的东西,但就本次分享而言,我分享一些我认为在hw里面值得注意的东西以及一些小技巧 0x01 信息收集 信息收集这个多西当然都是老生常谈了,你收集的东 ...

  9. Spring Boot 知识清单(一)SpringApplication

    爱生活,爱编码,微信搜一搜[架构技术专栏]关注这个喜欢分享的地方.本文 架构技术专栏 已收录,有各种JVM.多线程.源码视频.资料以及技术文章等你来拿. 一.概述 目前Spring Boot已经发展到 ...

  10. MGRE及实验

    tunnel 隧道:一种的简单的VPN技术: 普通的tunnel为点到点网络类型: 生成隧道接口,流量通过路由查询后,若通过隧道接口转发时,需要在原有的三层报头前,再添加一个公有地址间的报头: 将两个 ...