1. 结构体字节对齐
- 试一试
struct S1{
char c1;
char c2;
int n;
};
struct S2{
char c1;
int n;
char c2;
};
printf("sizeof(struct S1) = %ld\n",sizeof(struct S1));
printf("sizeof(struct S2) = %ld\n",sizeof(struct S2));
在C语言里,结构体所占的内存是连续的,但是各个成员之间的地址不一定是连续的。所以就出现了"字节对齐"。
-
字节对齐默认原则
- 结构体变量的大小,一定是其最大的数据类型的大小的整数倍,如果某个数据类型大小不够,就填充字节。
- 结构体变量的地址,一定和其第一个成员的地址是相同的。
练习
struct Box{
int height;
char a[10];
double width;
char type;
};
int main(void) {
struct Box box;
printf("box = %p\n", &box);
printf("box.height = %p\n", &box.height);
printf("box.a = %p\n", box.a);
printf("box.width = %p\n", &box.width);
printf("box.type = %p\n", &box.type);
printf("box = %d\n", sizeof(box));
return 0;
}
2. 内存四区
执行下面代码,分析代码中各种变量的地址特点。
#include
#include
void f1(){
int f1local1;
int f1local2;
static int f1static1;
static int f1static2;
int* f1dynamic1 = malloc(sizeof(int));
int* f1dynamic2 = malloc(sizeof(int));
printf("--------------f1()----------------\n");
printf("&f1local1 = %p\n",&f1local1);
printf("&f1local2 = %p\n",&f1local2);
printf("&f1static1 = %p\n",&f1static1);
printf("&f1static2 = %p\n",&f1static2);
printf("f1dynamic1 = %p\n",f1dynamic1);
printf("f1dynamic2 = %p\n",f1dynamic2);
printf("\"f1string1\" = %p\n","f1string1");
printf("\"f1string2\" = %p\n","f1string2");
printf("--------------f1()----------------\n");
free(f1dynamic1);
free(f1dynamic2);
}
void f2(){
int f2local1;
int f2local2;
static int f2static1;
static int f2static2;
int* f2dynamic1 = malloc(sizeof(int));
int* f2dynamic2 = malloc(sizeof(int));
printf("--------------f2()----------------\n");
printf("&f2local1 = %p\n",&f2local1);
printf("&f2local2 = %p\n",&f2local2);
printf("&f2static1 = %p\n",&f2static1);
printf("&f2static2 = %p\n",&f2static2);
printf("f2dynamic1 = %p\n",f2dynamic1);
printf("f2dynamic2 = %p\n",f2dynamic2);
printf("\"f2string1\" = %p\n","f2string1");
printf("\"f2string2\" = %p\n","f2string2");
printf("--------------f2()----------------\n");
free(f2dynamic1);
free(f2dynamic2);
}
int static1;
int static2;
int main(){
int local1;
int local2;
int* dynamic1 = malloc(sizeof(int));
int* dynamic2 = malloc(sizeof(int));
printf("&local1 = %p\n",&local1);
printf("&local2 = %p\n",&local2);
printf("&static1 = %p\n",&static1);
printf("&static2 = %p\n",&static2);
printf("dynamic1 = %p\n",dynamic1);
printf("dynamic2 = %p\n",dynamic2);
printf("\"string1\" = %p\n","string1");
printf("\"string2\" = %p\n","string2");
printf("&f1 = %p\n",&f1);
printf("&f2 = %p\n",&f2);
f1();
f2();
}
分析执行结果
&local1 = 0x7ffc8b8120fc
&local2 = 0x7ffc8b8120f8
&static1 = 0x601060
&static2 = 0x601064
dynamic1 = 0x1d10010
dynamic2 = 0x1d10030
"string1" = 0x400b8f
"string2" = 0x400ba7
&f1 = 0x40060d
&f2 = 0x400707
--------------f1()----------------
&f1local1 = 0x7ffc8b8120cc
&f1local2 = 0x7ffc8b8120c8
&f1static1 = 0x601050
&f1static2 = 0x601054
f1dynamic1 = 0x1d10050
f1dynamic2 = 0x1d10070
"f1string1" = 0x400a2f
"f1string2" = 0x400a4b
--------------f1()----------------
--------------f2()----------------
&f2local1 = 0x7ffc8b8120cc
&f2local2 = 0x7ffc8b8120c8
&f2static1 = 0x601058
&f2static2 = 0x60105c
f2dynamic1 = 0x1d10070
f2dynamic2 = 0x1d10050
"f2string1" = 0x400af7
"f2string2" = 0x400b13
--------------f2()----------------
上面结果每次执行结果会有所变化,不同计算机结果也会不同。但是会有如下规律:
- 局部变量、静态变量、动态分配内存、字符串常量与函数分别放在一起,即使在不同的函数中。
- 变量的存放地址大小有如下特点:
字符串常量与代码 < 静态变量 < 动态分配内存 < 局部变量
- 静态变量、动态分配内存、字符串常量与函数的相邻变量地址是递增的。局部变量相邻变量地址是递减的。
- 字符串常量与函数是在一起的。
上面说明代码在内存中是按类型存放在不同的区里面的。
gcc -S test.c
gcc --save-temp test.c
readelf -S ./a.out
2.1 栈区(stack)
由编译器自动分配和释放,主要是存放函数参数的值,局部变量的值。
2.2 堆区(heap)
由程序员自己申请分配和释放,需要malloc()
、calloc()
、realloc()
函数来申请,用free()
函数来释放如果不释放,可能出现指针悬空/野指针。
函数不能返回指向栈区的指针,但是可以返回指向堆区的指针。
2.3 数据区(data)
变量标有static
关键字,保存了静态变量。
1. 初始化的全局变量和初始化的静态变量,在一块区域;
2. 未初始化的全局变量和未初始化的静态变量,在一块区域,称作BSS(Block Started by Symbol:以符号开始的块);
3. 静态变量的生命周期是整个源程序,而且只能被初始化一次,之后的初始化会被忽略。
(如果不初始化,数值数据将被默认初始化为0
, 字符型数据默认初始化为NULL
)。
整个数据区的数组,在程序结束后由系统统一销毁。
2.4 代码区(code)
用于存放编译后的可执行代码,二进制码,机器码。
3. 堆和栈的区别
No. | 比较方面 | 栈 | 堆 |
---|---|---|---|
1 | 管理方式 | 由系统自动管理,以执行函数为单位 | 由程序员手动控制 |
2 | 空间大小 | 空间大小编译时确定(参数+局部变量) | 具有全局性,总体无大小限制。 |
3 | 分配方式 | 函数执行,系统自动分配;函数结束,系统立即自动回收。 | 使用new /malloc() 手动分配释放;使用delete /free() 手动释放 |
4 | 优点 | 使用方便,不需要关心内存申请释放。 | 可以跨函数使用。 |
5 | 缺点 | 只能在函数内部使用。 | 容易造成内存泄露。 |
4. 显示目标文件区段大小:size
命令
-
各区段含义
No. 区段 名称 含义 1 text
代码段(code segment/text segment) 存放程序执行代码的内存区域。该区域的大小在运行前已确定,且通常属于只读。可能包含一些只读的常数变量,例如字符串常量等。 2 data
数据段(data segment) 存放程序中已初始化的全局变量的内存区域。数据段属于静态内存分配。 3 bss
BSS段(bss segment) 存放程序中未初始化的全局变量的内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 dec
与hex
是前面三个区域的和,dec
是十进制,hex
是十六进制。 -
没有显示的区段
No. 区段 含义 1 栈(stack) 存放程序临时创建的局部变量,也就是函数括弧 {}
中定义的变量(不包括static
声明的变量)。在函数被调用时,参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。2 堆(heap) 存放程序动态分配的内存段,大小并不固定,可动态扩张或缩减。当调用 malloc()
等函数分配内存时,堆被扩张;当调用free()
等函数释放内存时,堆被缩减。