C高阶4:内存

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语言里,结构体所占的内存是连续的,但是各个成员之间的地址不一定是连续的。所以就出现了"字节对齐"。

  • 字节对齐默认原则

    1. 结构体变量的大小,一定是其最大的数据类型的大小的整数倍,如果某个数据类型大小不够,就填充字节。
    2. 结构体变量的地址,一定和其第一个成员的地址是相同的。
  • 练习

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()----------------

上面结果每次执行结果会有所变化,不同计算机结果也会不同。但是会有如下规律:

  1. 局部变量、静态变量、动态分配内存、字符串常量与函数分别放在一起,即使在不同的函数中。
  2. 变量的存放地址大小有如下特点:

    字符串常量与代码 < 静态变量 < 动态分配内存 < 局部变量

  3. 静态变量、动态分配内存、字符串常量与函数的相邻变量地址是递增的。局部变量相邻变量地址是递减的。
  4. 字符串常量与函数是在一起的。

上面说明代码在内存中是按类型存放在不同的区里面的。

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段属于静态内存分配。

    dechex是前面三个区域的和,dec是十进制,hex是十六进制。

  • 没有显示的区段

    No. 区段 含义
    1 栈(stack) 存放程序临时创建的局部变量,也就是函数括弧{}中定义的变量(不包括static声明的变量)。在函数被调用时,参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
    2 堆(heap) 存放程序动态分配的内存段,大小并不固定,可动态扩张或缩减。当调用malloc()等函数分配内存时,堆被扩张;当调用free()等函数释放内存时,堆被缩减。

你可能感兴趣的:(C高阶4:内存)