C语言内存划分全解析:从栈到代码区的深度解读🔍
🔥温馨提示🔥:使用电脑端阅读,获取更好体验🚀
【参考】史上最强C语言教程----数据在内存中的存储_char 内存中 补码 ascii-CSDN博客
【强烈推荐】4小时彻底掌握C指针 - 顶尖程序员图文讲解 - UP主翻译校对 (已完结)>
【强烈推荐】一个在线编译器Compiler Explorer (godbolt.org)
它能够直观展示源代码编译为汇编的全过程,便于开发者对比不同编译器、优化选项下的代码生成效果,深入理解编译原理与优化技术。
文章目录
- C语言内存划分全解析:从栈到代码区的深度解读🔍
- C程序的内存分配
- 数据类型
- 基本数据类型(Primitive Data Types)
- 特殊类型(Special Types)
- 构造数据类型(Derived or Composite Data Types)
- 附加类型注解
- 类型别名(Type Aliases)
- 字符串(Strings)
- 数据类型在内存中的存储方式
- 整型(Integer Types)
- 原码、反码、补码
- 大小端
- 浮点型(Floating-Point Types)
- 浮点数存储规则
- 浮点数精度丢失问题
- 布尔型(Boolean Type)
- 数组(Arrays)
- 指针(Pointers)
- 结构体(Structures)和联合体(Unions)
- 枚举(Enumerations)
- 字符串(Strings)
C程序的内存分配
C语言的内存划分主要包括以下几个部分:
-
栈(Stack)
- 栈是
用于存储函数调用时产生的局部变量、函数参数以及返回地址的空间
。栈空间是由编译器自动分配和释放的,遵循后进先出(LIFO)原则。当函数调用发生时,系统会在栈上为局部变量分配空间,函数执行完毕返回时,这部分空间会被自动回收。
- 栈是
-
堆(Heap)
- 堆是
程序运行时动态分配内存的地方
,程序员通过调用malloc()
、calloc()
、realloc()
等函数来申请内存,并通过free()
函数释放内存。堆上的内存分配是动态的,生命周期从分配时刻开始,直到显式地释放为止,如果不释放,可能会导致内存泄漏,直至程序结束操作系统可能回收这部分资源。
- 堆是
-
全局区/静态存储区(Static Storage Area)
包括初始化全局变量、未初始化全局变量以及静态变量
。这些变量在程序开始运行前就已经分配好内存,它们的生存期贯穿整个程序运行期间。初始化过的全局变量和静态变量存储在数据段(Data Segment),未初始化的全局变量和静态变量存储在BSS段(Block Started by Symbol Segment),其中BSS段并不实际存储变量的初始值,而是记录其所需的内存大小。
-
常量区(Read-Only Memory, ROM)
存储常量和字符串字面量
,这部分内存不可修改,内容在程序加载时由操作系统直接载入内存,属于程序的只读部分。
-
代码区(Text Segment)
- 也称为代码段,存储可执行代码,即编译后的机器指令。程序在执行时按照代码区的指令流顺序执行或通过跳转指令进行非顺序执行。
总结来说,C语言的内存布局包括:
- 栈:自动分配和释放,存储函数内的局部变量和函数调用相关信息。
- 堆:动态分配和释放,存储运行时需要的动态内存。
- 全局区/静态存储区:静态分配,存储全局变量和静态变量。
- 常量区:存储常量和字符串字面量。
- 代码区:存储可执行程序的机器指令。
数据类型
C语言提供了多种数据类型,以便程序员能够定义不同类型的数据变量和常量。以下是C语言中的基本数据类型和构造数据类型:
基本数据类型(Primitive Data Types)
-
整型(Integer Types)
char
: 通常占用1字节(8位),可以存储字符或整数(有符号或无符号,取决于系统)。short int
或short
: 短整型,通常占用2字节(16位)。int
: 基本整型,通常占用4字节(32位),用于存储整数值。long int
或long
: 长整型,至少与int
同样大,但在某些系统上可能更大,例如占用4或8字节。long long int
或long long
: 双长整型,占用至少8字节(64位)。
注意
:这个地方为什么char类型也归为整型家族里面呢?因为char类型的数据本质上也是在内存中存储的其ascii码值,而其ascii码就是整数,所以char类型自然也就列为整型家族里面了。 -
浮点型(Floating-Point Types)
float
: 单精度浮点型,通常占用4字节(32位)。double
: 双精度浮点型,通常占用8字节(64位)。long double
: 长双精度浮点型,至少与double
同样精确,但在某些系统上可能提供更高的精度,占用更多字节。
-
布尔型(Boolean Type)
- C语言标准中并没有内置的布尔类型,但可通过整型实现类似的功能,通常用
int
表示,0
表示 false,非零值表示 true。
- C语言标准中并没有内置的布尔类型,但可通过整型实现类似的功能,通常用
特殊类型(Special Types)
void
: 表示没有类型,通常用于函数返回类型表示不返回值,或指针类型表示通用指针。
构造数据类型(Derived or Composite Data Types)
- 数组(Arrays): 一组相同类型的有序元素集合。
- 指针(Pointers): 存储其他变量地址的数据类型。
- 结构体(Structures): 由多个不同类型的数据成员组成的复合类型。
- 联合体(Unions): 具有多个成员,但同一时间只有一个成员可以存储值的复合类型。
- 枚举(Enumerations): 一组命名的整数值标签。
附加类型注解
- 修饰符(Modifiers): 如
signed
和unsigned
可以用来指定整数是有符号还是无符号。 - 尺寸限定符(Size Specifiers): 在ANSI C中,可以通过
short
、long
和long long
对整数类型进一步限定其长度。
类型别名(Type Aliases)
- 通过
typedef
关键字可以为现有类型创建一个新的名称(别名)。
字符串(Strings)
- C语言中字符串实际上是字符数组,通常以空字符
\0
结束。
数据类型在内存中的存储方式
在C语言中,不同数据类型在内存中的存储方式如下:
整型(Integer Types)
原码、反码、补码
计算机中的整数(有符号数)有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位负整数的三种表示方法各不相同。
原码
- 对于正数,原码就是其二进制形式。
- 负数与其绝对值的二进制只有符号位不同,符号位为1。
例如:+5 的原码是 00000101,-5 的原码是 10000101(假设这是一个 8 位系统,最高位为符号位)。
反码
- 对于正数,反码与原码相同。
- 对于负数,除了符号位之外,其他各位都取反(0 变成 1,1 变成 0)。而最高位的符号位不变,仍保持为 1。
例如:-5 的反码是 11111010(除符号位外,其余位翻转)。
补码
- 对于正数,补码同样与原码相同。
- 对于负数,补码是其反码的基础上加 1 得到的。
例如:-5 的补码是 11111011,这是通过先求反码再加 1 得到的。
注意:
(1)正数的原、反、补码都相同
。
(2)对于整形来说:数据存放内存中其实存放的是补码
。那么为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。总结而言,有两点原因:一是为了进行负数的有关运算;二是为了提高运算的效率。(简化了算术逻辑单元的设计,并使得加减法运算更为简单有效。)
大小端
在计算机科学中,“大小端”(Endianess) 是指多字节数据
(如整数或浮点数)在内存中存储顺序的不同方式,尤其当这些数据跨越多个字节时。大小端模式决定了在多字节序列中,最高有效字节(Most Significant Byte, MSB)和最低有效字节(Least Significant Byte, LSB)的存放位置。
大端模式(Big Endian)
在大端模式下,多字节数据的最高有效字节存储在最低地址处,随后的字节按照递减的重要性依次存储在更高的地址上。比如一个16位的整数0x1234在大端模式下会存储为12在低地址,34在高地址。
即:数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
小端模式(Little Endian)
在小端模式下,多字节数据的最低有效字节存储在最低地址处,随后的字节按照递增的重要性依次存储在更高的地址上。同一个16位的整数0x1234在小端模式下则会存储为34在低地址,12在高地址。
即:数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。
大小端只是针对多字节的,对于仅有一个字节的char类型来说,无所谓大小端。
注:这两种模式的选择主要依赖于硬件体系结构,不同架构的处理器可能采用不同的字节序。例如,大多数基于Intel和AMD的个人计算机使用的是小端模式,而一些网络设备和大型机(如IBM的System/360系列后继产品)倾向于使用大端模式。理解大小端概念对于处理跨平台数据交换以及网络通信非常重要。
由下图可知,int型是采用补码的形式存储,在计算机中以小端顺序存储。
- char:占用1个字节,存储一个字符或整数值。有符号char会根据系统是采用补码或其他方式存储,无符号char则存储0到255之间的整数。
- short int/short:占用至少2个字节,存储较小的整数,其内部存储结构同理整型。
- int:通常占用4个字节(32位系统),以二进制
补码
的形式存储有符号整数或无符号整数。 - long int/long:至少与int一样大,具体取决于编译器和系统,可能是4或8个字节,存储方式与int相同。
- long long int/long long:至少占用8个字节,存储更大的整数,同样采用
补码
表示。
浮点型(Floating-Point Types)
浮点数存储规则
浮点数在内存中的存储遵循IEEE 754标准,这是一种广泛接受的二进制浮点数算术标准。对于单精度浮点数(float
)和双精度浮点数(double
),其内存布局如下:
单精度浮点数(float,32位):
- 符号位(Sign bit):最高位1位,0表示正数,1表示负数。
- 指数部分(Exponent):紧接着的8位,存储偏移指数(biased exponent)。
- 尾数(Mantissa或Fraction):剩下的23位,存储尾数部分,实际存储的是去掉首位1后的二进制小数部分。
存储规则:
- 符号位决定了数的正负。
- 指数部分存储的是实际指数加上一个偏移量(对于单精度浮点数,偏移量通常是127)。
- 尾数部分存储的是二进制小数部分,实际存储时默认第一个二进制位为1,因此只存储剩余的23位。当取出时,会自动在前面补充一个隐含的1。
双精度浮点数(double,64位):
- 符号位:同样是最高位1位。
- 指数部分:接着的11位,存储偏移指数(对于双精度,偏移量通常是1023)。
- 尾数部分:剩下的52位,同样存储去掉首位1后的二进制小数部分。
特别说明:
- 当指数全为0且尾数不全为0时,表示一个非常小的非零数(亚正规数)。
- 当指数全为1且尾数全为0时,表示无穷大或NaN(Not-a-Number)。
取值范围:
- 单精度浮点数大致范围为±1.17549435e-38 ~ ±3.40282347e+38。
- 双精度浮点数大致范围为±2.2250738585072014e-308 ~ ±1.7976931348623157e+308。
在实际编程中,C语言程序员通常不需要手动管理浮点数在内存中的具体布局,编译器会根据IEEE 754标准自动处理浮点数的存储和计算。
浮点数精度丢失问题
从一个经典例子谈起:
js语言示例:
var f1 = 0.1;
var f2 = 0.2;
console.log(f1+f2); // 您认为结果是0.3? 真实结果是 0.30000000000000004
java语言示例:
public static void main(String[] args) {
double d1 = 0.1;
double d2 = 0.2;
System.out.println(d1+d2); //您认为结果是0.3? 真实结果是: 0.30000000000000004
}
5分钟彻底搞懂"浮点数精度丢失"问题 - 知乎 (zhihu.com)
- float:通常占用4个字节,按照IEEE 754标准存储单精度浮点数,包括一个符号位、8位指数和23位尾数(小数部分)。
- double:通常占用8个字节,按照IEEE 754标准存储双精度浮点数,包括一个符号位、11位指数和52位尾数。
- long double:至少与double一样精确,有的系统中可能占用16个字节,存储更多的尾数以提高精度。
布尔型(Boolean Type)
- C语言没有内置布尔类型,但通常通过整型来模拟,1字节的char或int类型,0表示false,非零表示true。
数组(Arrays)
- 数组在内存中是连续分配的,每个元素按照其类型所占字节数顺序排列。
指针(Pointers)
- 指针变量占用的内存大小与CPU架构和操作系统有关,通常为4或8个字节(32位或64位系统)。指针存储的是所指向变量的内存地址。
结构体(Structures)和联合体(Unions)
结构体的字节对齐(超详细:规则+例子+原因)_结构体字节对齐规则-CSDN博客
3分钟理解C语言字节对齐(小白扫盲篇) - 知乎 (zhihu.com)
- 结构体和联合体的内存布局是其成员按照定义顺序依次排列。结构体的所有成员都会占用各自应有的内存空间,而联合体在同一时间内只会存储一个成员,共享相同的内存区域。
枚举(Enumerations)
- 枚举类型的存储大小至少与int一致,每个枚举值实际上是整数值。
字符串(Strings)
- 字符串实际上是字符数组,每个字符占用1个字节,末尾附加上一个空字符’\0’,总共占用数组长度加1个字节。