用 Keil5 理解 C 语言(一):变量内存和原码反码与补码
本文通过Keil5调试实践,探讨了C语言变量内存分配与数据编码问题。实验验证了char和int类型变量的存储空间差异(1字节和4字节),分析了变量赋值超出范围时的截断现象。重点讲解了原码、反码和补码的转换原理,通过-1+1的计算示例演示了补码在计算机运算中的必要性。文章还展示了如何在Keil中观察变量内存地址,并解释了符号位对数值解析的影响。最后指出,理解这些底层机制对嵌入式开发至关重要,为后续学
哈喽大家好,这里是Hello_Embed的第一篇个人笔记,记录了我跟韦东山老师学习嵌入式基础C语言的学习心得,希望对你有所帮助。
单片机是如何存储二进制数据的?
对于单片机来说,它是如何通过电路来存储和表示二进制数据的呢?没错,答案就是“电路”本身!
我们通常用一个高电平(比如 3.3V)来表示二进制中的 1,用一个低电平表示 0。一个二进制位(bit)就是由一个这样的电平状态表示的。当我们用 8 个这种高低电平的电路(通常由晶体管组成)组合在一起时,就构成了一个 字节(byte)。为了便于后续学习的理解,我们称这样八个晶体管为八位数据。
💡 补充:
1 byte = 8 bit(1 字节 = 8 位)
int a; //声明变量a,并分配4个字节
像上面这样的定义,编译器会在内存中为 a 分配 4 字节(即 32 位)的空间。这部分内存用于保存变量的值。
为了验证这一点,我们可以用 Keil5 来进行一个简单的测试
准备阶段:搭建测试环境
在keil中建立工程模板,我这里用的是江协科技的工程模板,相关资料可在b站上查找。
//text.c 这是要用的测试代码
#include <stdio.h>
int mymain(void)
{
volatile char c;
volatile int a;
c = 1;
a = 2;
return 0;
}
📌 说明:volatile 关键字的核心作用是告诉编译器:“这个变量是易变的”,不要对它进行优化处理。这样能确保每次访问都从内存中重新读取变量值。
模板代码示意图如下:
编译并下载程序后,确保烧录器与开发板连接正常,即可开始调试。
调试阶段:观察变量的变化
- 在第一个
mymain函数的第一个{处打上断点 - 选中变量c与a,右键添加到watch1观察窗口中

- 运行Run(F5)
- 运行到mymain处再用单步运行Step(F11)一行行调试
结果如下:
可以看到,变量c的值为0x01,而变量a的值是0x00000002。这是一个很有意思的现象。
这是为什么呢?我们来分析一下:
c是char类型,占 1 字节 = 8 位a是int类型,占 4 字节 = 32 位
上述是二进制显示,由于一个十六进制数位可表示 4 位二进制(2^4 = 16),所以我们可以得到:- 8 位二进制(
char) -> 2 位十六进制 =>0x01 - 32 位二进制(
int) -> 8 位十六进制 =>0x00000002
拓展:如何查看变量的内存地址?
除了使用 Watch 窗口查看变量值外,我们还可以在 Memory 窗口中查看变量的内存地址。
操作方式:在变量名前加上 & 取地址符号,比如 &a,就可以查看该变量的实际地址了。
关于原码、反码与补码
对于八位二进制数,0000 0000我们将bit7即最高位称为符号位,0为正1为负由此来区分正负数。但这样会出现如下错误:-1 + (+1)= 1000 0001 + 0000 0001 = 1000 0010 = -2。于是我们引出反码:将负数除了符号位的其余部分取反。我们再来计算这个式子-1 + (+1)= 1111 1110 + 0000 0001 = 1111 1111由于这个结果 1111 1111 在反码表示中并不等于 0,说明反码虽然改进了负数表示方式,但在加法运算中仍然存在歧义和进位问题。因此,计算机在实际运算中并不使用反码,而是使用更完善的 补码 。
- 正数:补码 = 原码
- 负数:补码 = 反码 + 1
我们再用补码计算一次:-1 + (+1)= 1111 1111 + 0000 0001 = 0000 0000(进位丢弃),于是我们终于得出了正确的结果“0”,说明补码确实是一种更适合计算机进行运算的编码方式。
有了这些编码知识我们来分析一个有意思的现象:
//修改text.c 测试当变量存放值超出数据类型时会发生什么现象
#include <stdio.h>
int mymain(void)
{
volatile char c;
volatile int a;
c = 1234567;
a = 0x12345678;
printf("c = %d\n\r", c);
printf("a = %d\n\r", a);
printf("sizeof(c) = %d\n\r", sizeof(c));
printf("sizeof(a) = %d\n\r", sizeof(a));
return 0;
}
编译后有1个警告:User\text.c(7): warning: #69-D: integer conversion resulted in truncation我们暂时不管它,将程序下载进系统板内,由于这部分用到串口通信我们直接看现象:c = 135 a = 305419896 sizeof(c) = 1 sizeof(a) = 4
在计算机内进行进制转换,如图:
可以看到二进制为很长的一段数字,但由于char类型变量占1字节也就是8位,取图上低位二进制数有1000 0111这就是keil往单片机存储c的二进制形式,我们将此二进制转为十进制恰好是“135”,说明keil把第一位“1”视作了数值位而非符号位。
我们对变量类型稍加修改就能改善这一情况:volatile char c->volatile unsigned char c,这样c就是一个有符号的整型变量,我们再次打印可得c = -121因为符号位为1,所以我们需要进行补码计算出它的原码(为什么上面不需要?因为上面的符号位被视为数值位,默认补零后就是正数直接计算即可)此时补码经过-1再取反可得:1111 1001取数值位111 1001转换为十进制则是121,加上符号位“1”就是“-121”。
其他数据类型
float:可赋小数double:更高精度小数- 其他类型如
long long、short等
由于这些类型在嵌入式开发中使用频率相对较低,本文暂不展开,感兴趣的同学可以进一步查阅资料。
写在最后
这是我跟着韦东山老师学习过程中的第一篇动手实验笔记。从变量声明、内存分配,到使用 Keil 调试查看变量值,希望这篇笔记对刚入门的朋友们有所帮助!
如果你也在学习嵌入式,欢迎一起交流成长!
我是 Hello_Embed,一个正在努力成长的嵌入式小白,我们下篇笔记再见!
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)