嵌入式C语言-P28
1. 代码打印结果是什么?浮点数在内存中如何存储?
1.1. 代码打印结果
结论:打印结果是不确定的(Undefined Behavior),并且代码中存在一个明显的错误。
让我们先分析一下提供的代码:
#include<stdio.h>
int main()
{
float a = 1.2; // 定义一个浮点数a,并赋值为1.2
int *p; // 定义一个指向整型(int)的指针p
p = &a; // 【问题所在】将浮点数a的地址强制赋值给一个整型指针
printf("the p1 is %x\n", *p); // 【另一个错误】试图以十六进制格式打印*p
}
这段代码有两个核心问题:
-
指针类型不匹配:
p是一个int*(整型指针),而&a是一个float*(浮点型指针)。将一种类型的地址直接赋给另一种类型的指针违反了C语言的“严格别名规则”(Strict Aliasing Rule),会导致未定义行为(Undefined Behavior)。这意味着程序的输出结果是不确定的,它可能因编译器、平台、甚至每次编译而异。 -
变量名错误:在
printf语句中,您试图打印*p1,但程序中只定义了指针p,没有定义p1。这会导致编译错误。我假设您的本意是打印*p。
**所以,如果我们修正变量名错误(将 _p1 改为 _p),代码可以编译运行,但结果呢?__
printf中的 %x格式说明符要求传入一个 unsigned int类型的参数,但它实际接收到的是 *p,也就是程序将一个 float在内存中的二进制位模式强行解释成了一个 int,然后再把这个 int值传递给 printf。
因此,printf不会输出浮点数 1.2的值,而是会输出 1.2在内存中的那个二进制位模式所对应的十六进制整数。
一个可能的输出示例:
在32位小端序机器上,单精度浮点数 1.2的二进制位模式对应的十六进制数是 0x3f99999a。
所以,程序可能会输出:
the p is 3f99999a
但请务必注意:这只是众多可能性中的一种。由于这是未定义行为,在不同的系统(如64位系统)、不同的编译器或不同的优化选项下,结果可能完全不同,程序甚至可能崩溃。
1.2. 浮点数在内存中如何存储?
浮点数(如C语言中的 float)在内存中遵循 IEEE 754标准 进行存储。以32位的单精度(float)为例,其存储结构分为三个部分:
| 部分 | 长度 | 用途 |
|---|---|---|
| 符号位 (Sign) | 1 bit | 决定数的正负。0表示正数,1表示负数。 |
| 指数位 (Exponent) | 8 bits | 存储偏移后的指数(后面会解释)。 |
| 尾数位 (Mantissa/Fraction) | 23 bits | 存储规格化后的小数部分。 |
其结构如下图所示:
31 30 23 22 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|S| Exponent | Fraction |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
存储过程(以 a = 1.2为例):
-
转换为二进制科学计数法
-
首先将十进制的
1.2转换为二进制。1.2的二进制表示是一个循环小数:1.0011001100110011...(无限循环011)。 -
将其规范化为科学计数法形式:
1.0011001100110011... × 2^0
-
-
分解三个部分
-
符号位 (S):因为
1.2是正数,所以S = 0。 -
指数位 (E):科学计数法中的指数是
0。但IEEE 754规定指数不能为负数,所以采用一个偏移值(Bias) 来存储。对于单精度浮点数,偏移值是127。因此,存储的指数值 = 实际指数 + 偏移值 =0 + 127 = 127。127的二进制是01111111。
-
尾数位 (M):在科学计数法
1.xxxx × 2^exp中,小数点前的1是固定的(称为“隐含的1”),因此只需要存储小数点后的部分0011001100110011...。由于尾数位只有23位,需要对无限循环的数字进行舍入(Rounding)。00110011001100110011001(保留23位,并对后续位进行舍入)
-
-
组合并存储
-
将这三部分按顺序(S、E、M)组合成一个32位的二进制数:
0(S)01111111(E)00110011001100110011001(M) -
将这个二进制数转换为十六进制,就是
0x3f99999a(这也是上面打印结果可能为3f99999a的原因)。
-
1.3. 总结
-
代码打印结果:由于指针类型不匹配导致的未定义行为,结果不确定。修正变量名后,它可能会输出浮点数
1.2的内存二进制位模式所对应的十六进制数(如3f99999a),但切勿依赖此结果。 -
浮点数存储:遵循 IEEE 754标准,分为符号位、指数位(经过偏移)和尾数位(隐含前导1)三部分。
如果你想安全地观察浮点数的内存表示,正确的方式是使用联合体(union)或 memcpy:
#include <stdio.h>
#include <string.h>
int main() {
float a = 1.2;
unsigned int b;
// 使用memcpy安全地复制比特位
memcpy(&b, &a, sizeof(a));
printf("The hex representation is: %x\n", b);
return 0;
}
2. 定义 int *p;,p这个指针变量可以存一个地址或多个地址吗?怎么存的?
这是一个关于C指针核心概念的非常好的问题。我们来详细拆解一下。
2.1. 直接回答
对于代码中定义的 int *p,这个指针变量 p只能存储一个内存地址。
它通过在内存中开辟一块小空间(通常是4或8字节,取决于系统是32位还是64位)来专门存放这一个地址值。
2.2. 详细解释:指针变量如何存储地址?
我们可以把计算机内存想象成一个巨大的、连续的房间公寓楼,每个房间都有一个唯一的门牌号(内存地址)。房间里的住客就是存储的数据。
-
指针变量自身也是一个“住户”
当您声明
int *p;时,系统会在内存楼里为p这个指针变量本身分配一个小房间(例如,房间号0x7ffc8a9b5c64)。这个房间的大小是固定的,刚好足够存放一个门牌号(地址)。在32位系统是4字节,64位系统是8字节。 -
指针变量的“工作”是记住另一个房间的地址
指针变量房间里不放普通“住客”(比如整数、浮点数),它只放另一个房间的门牌号。
-
在您的代码
p = &a;中,&a就是获取浮点数变量a所住房间的门牌号。 -
然后,这个门牌号被写进了
p自己的房间里。 -
现在,我们说指针
p指向了变量a。
-
-
访问数据——按图索骥
当您使用
*p时(这称为解引用),CPU 就会去指针p的房间里看一看,读出里面记录的门牌号,然后根据这个门牌号找到目标房间,最后访问或修改那个房间里的数据。
这个过程可以用下图清晰地展示:
flowchart TD
A[代码: float a = 1.2;] --> A_sub[系统为变量a分配内存]
A_sub --> A_mem[内存地址: 0x7ffd42d3<br>(示例)<br>数据: 1.2]
B[代码: int *p;] --> B_sub[系统为指针p分配内存]
B_sub --> B_mem[内存地址: 0x7ffcdeca<br>(示例)<br>数据: 待赋值]
C[代码: p = &a;] --> C_op[将a的地址存入p]
C_op --> B_mem
B_mem -.->|存入| Addr[地址值: 0x7ffd42d3]
D[代码: *p] --> D_op[解引用]
B_mem -.->|读出地址| D_op
D_op --> A_mem
2.3. 为什么不能 p = &a;?—— 类型不匹配
根据上图,我们可以看到关键问题:p是一个 int *(整型指针)。这意味着它承诺:“我房间里记的门牌号,对应的那个房间里住的是一个 int 类型的住户。”
但您却强行把 a(一个 float 类型的住户)的地址塞给了它。
这会导致两个问题:
-
编译器警告/错误:编译器知道这个承诺不一致,所以会给出警告(Warning),因为这是一种类型错误。
-
未定义行为:当你强行用
*p(用整型的方式)去访问一个float数据时,程序会把float在内存中的二进制格式强行解释成int的格式,导致打印出的结果看似随机(实际上是1.2的IEEE 754二进制编码值),而非您期望的1.2。
2.4. 如何正确存储多个地址?
如果您需要让一个指针变量“关联”或“指向”多个地址,通常有以下几种正确的方式:
-
指针数组:数组里的每个元素都是一个指针,每个指针都可以指向一个地址。
int x = 10, y = 20, z = 30; int *arr_of_ptrs[3]; // 一个数组,包含3个整型指针 arr_of_ptrs[0] = &x; // 第一个指针指向x的地址 arr_of_ptrs[1] = &y; // 第二个指针指向y的地址 arr_of_ptrs[2] = &z; // 第三个指针指向z的地址 -
指向指针的指针:一个指针,其指向的目标是另一个指针。
int a = 100; int *p1 = &a; // p1 指向 a 的地址 int **p2 = &p1; // p2 指向 p1 的地址
2.5. 总结
| 问题 | 答案 |
|---|---|
int *p能存几个地址? |
只能存一个。 |
| 怎么存的? | 在内存中开辟一块小空间(4/8字节),专门用来存放一个地址值。 |
为什么代码 p = &a;是错误的? |
类型不匹配。p承诺指向 int,但 a是 float。 |
| 如何正确指向多个地址? | 使用指针数组或多级指针。 |
希望这个解释能帮助你彻底理解指针和地址的关系。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)