从内存堆栈视角,给这段共用体代码做个 "内存透视"
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
return 0;
}
咱们先打个比方:共用体(union)就像一个 "多功能储物盒"—— 盒子只有一个,但可以放不同类型的东西(整数、浮点数、字符串),但每次只能放一种,新东西放进去会把旧的覆盖掉。而栈就像家里的临时置物台,这个盒子用完就收走,不占地方。
先看懂代码的 "迷惑行为"
这段代码定义了一个Data共用体,里面可以放整数i、浮点数f或字符串str。然后在main里创建了data变量,先存 10,再存 220.5,最后存字符串 "C Programming",然后打印这三个值。运行结果会很奇怪:
plaintext
data.i : 1917853767
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
整数和浮点数都乱了,只有字符串正常。这不是 bug,而是共用体的特性!今天咱们就从内存角度扒开这个 "多功能盒子" 的秘密。
内存区域聚焦:栈是唯一舞台
C 程序内存的三大块里,这段代码只用到了栈:
- 栈(Stack):像临时置物台,main函数运行时在这里给共用体变量分配空间,函数结束后自动释放。
- 全局区和堆:这段代码没用到,暂时忽略。
逐行拆解:共用体在栈上的 "覆盖术"
1. 共用体定义:设计 "多功能盒子图纸"
c
运行
union Data {
int i; // 整数(4字节)
float f; // 浮点数(4字节)
char str[20]; // 字符串(20字节)
};
- 这是共用体的 "设计图纸",告诉编译器:这个盒子能放三种东西,但整个盒子的大小由最大的成员决定(这里str[20]最大,所以盒子总大小是 20 字节)。
- 重点:图纸不占运行时内存,就像你画的 "多功能盒子" 设计图不会占地方。
2. 创建共用体变量:栈上分配 "20 字节盒子"
c
运行
int main() {
union Data data; // 共用体变量
// ...
}
- 程序运行时,main函数的栈帧在栈上创建,给data分配了 20 字节的连续空间(因为str[20]是最大成员)。这个空间就是那个 "多功能盒子",所有成员都共用这 20 字节 —— 就像一个盒子,既可以当 4 字节的小格子用(存i或f),也可以当 20 字节的长格子用(存str)。
- 栈帧里的data初始布局(20 字节,地址连续):
- plaintext
┌─────────────────────────────────────┐
│ 字节0 字节1 字节2 ... 字节19 │ ← 共20字节,初始值是栈上的随机垃圾
└─────────────────────────────────────┘
3. 给共用体赋值:"新内容覆盖旧内容"
c
运行
data.i = 10; // 第一步:存整数10
data.f = 220.5; // 第二步:存浮点数220.5(覆盖前面的10)
strcpy(data.str, "C Programming"); // 第三步:存字符串(覆盖前面的220.5)
- 第一步:存i=10
整数i占 4 字节,往盒子的前 4 字节(字节 0-3)存入 10 的二进制(00000000 00000000 00000000 00001010)。此时字节 4-19 还是随机值。 - 第二步:存f=220.5
浮点数f也占 4 字节,会覆盖前 4 字节,存入 220.5 的二进制(浮点数有特殊编码规则)。此时前 4 字节已经不是 10 了,后面 16 字节还是随机值。 - 第三步:存字符串"C Programming"
字符串str占 20 字节,会从字节 0 开始逐个存入字符:'C',' ','P','r','o','g','r','a','m','m','i','n','g','\0'(共 14 个字符),剩下的字节 6-19 被填充 '\0'。这一步会覆盖整个 20 字节,包括前 4 字节 —— 所以之前存的i和f全被冲掉了!
4. 打印结果:"读的是同一块内存,但解析方式不同"
c
运行
printf("data.i : %d\n", data.i); // 读前4字节,按整数解析
printf("data.f : %f\n", data.f); // 读前4字节,按浮点数解析
printf("data.str : %s\n", data.str); // 读20字节,按字符串解析
- 打印data.i:读前 4 字节(现在是 'C',' ','P','r' 的 ASCII 码),强行按整数规则解析,得到一个乱码数字(1917853767)。
- 打印data.f:读前 4 字节,强行按浮点数规则解析,结果更离谱(因为这 4 字节本来是字符,不是浮点数编码)。
- 打印data.str:读整个 20 字节,按字符串规则解析(直到 '\0'),所以正常显示 "C Programming"。
- 就像你用收音机听 CD:CD 里存的是音乐(字符串),但你非要用收音机的方式(整数 / 浮点数解析)听,出来的只能是噪音。
5. 程序结束:栈帧 "整体清空"
c
运行
return 0;
- main函数结束,栈帧被释放,data的 20 字节空间被回收(临时置物台上的盒子被收走)。
共用体的内存本质:"一块内存,多种解读"
- 大小由最大成员决定:不管存什么,共用体变量的大小永远等于最大成员的大小(这里 20 字节)。
- 成员共享同一块内存:新成员赋值会覆盖旧成员(因为地址重叠),所以同一时间只能用一个成员。
- 解析方式决定值:同一块内存,按不同类型解析(整数 / 浮点数 / 字符串)会得到不同结果,就像同样的二进制,用 txt 打开是文字,用 jpg 打开可能是乱图。
关键结论:共用体是 "内存里的多面镜"
- 共用体变量data在栈上占 20 字节,所有成员都共享这 20 字节 —— 这是和结构体最本质的区别(结构体成员各占空间,不重叠)。
- 它的价值是 "节省内存":当你知道某块内存同一时间只会存一种类型时,用共用体比结构体更省空间(比如 20 字节 vs 28 字节(4+4+20))。
- 但要小心 "覆盖陷阱":存新值会冲掉旧值,所以使用时必须清楚当前存的是什么类型,否则会读错数据(就像你得知道盒子里现在放的是饼干还是手机,否则可能会一口咬下去)。
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
printf( "Memory size occupied by data : %d\n", sizeof(data));
return 0;
}
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
data.i = 10;
printf( "data.i : %d\n", data.i);
data.f = 220.5;
printf( "data.f : %f\n", data.f);
strcpy( data.str, "C Programming");
printf( "data.str : %s\n", data.str);
return 0;
}
标题:
- 《C 语言共用体:栈上的 "多功能盒子",存新值会覆盖旧值》
- 《从内存看共用体:同一块空间,多种解读方式的 "魔术"》
简介:
通过分析共用体代码的内存分配,揭示共用体成员共享同一块内存(大小由最大成员决定)、新值覆盖旧值的特性,解析打印结果混乱的原因,展现共用体节省内存但需注意类型匹配的特点。
关键词:
#C 语言共用体 #栈内存 #内存共享 #类型解析 #内存覆盖