# 面试经历

真丢脸,上来张嘴就说错了大小端,虽然说后面说对了。基本估计就没啥好印象了.
上来先问了一下校园经历,巴拉巴拉说了自己在 ACM、人工智能、Web、ICAN 的经历.
然后问了有没有考计算机等级证书 (我突然质疑专业性,不过技术面的感觉还是有很深底子的).
问了一下专业骨干课程,计算机就是那几大件,计算机网络,操作系统,组成原理,数据结构
接着就开始问 C 语言基础知识了,感觉是栽了 (一开始就错了,真丢脸).
问了大小端,问了 堆和栈使用的区别 (这里又栽了,全局变量,静态变量存放的位置弄错了), 问了 static 和 const 对变量的影响 (我有点扯多了,整的我好像在背答案).
其他的就开始过项目了。让我说一个项目,我分享了一下电磁炮的项目,面试官好像不太感兴趣,就问了一下中断的使用中断的安排.
又分享了无线智能节点,面试官问了线程安排和线程管理 (他们似乎对这个很感兴趣), 内存保护,多线程调度,同步等,这一块得好好补一补

# 知识点总结

# 大小端

小端:低位数据存放在低地址.

记住这一个就好啦,大端相反,以免和我一样,绕进去了
PS: 网络流的字节序是大端
思考一下,为什么要存在大小端.
网络中对数据的处理更多的是人指定的协议,对于人来说更加适应大端数据处理的方式,所以网络字节序是大端
而在机器中使用小端是因为机器不知道高低字节只会按顺序处理,先读第一个字节再读第二个字节依次反复。如果按大端存储就会需要,先读到的就是高位字节,后读到的就是低位字节.
还有就是一些历史包袱的缘故导致的两种情况共存

# 内存分区

# C 语言中的内存分区

区域存放内容说明
栈区据补变量值、函数的参数值由编译器自动分配释放 FILO
堆区new/malloc 出来的空间一般由程序员分配释放,若程序员不是发,程序结束后由 OS 回收。与数据结构中的堆并不一样,分配方式类似于链表
常数区局部变量 / 全局变量的常量字符串也存放在这里,程序结束后由 OS 释放
静态区全局变量 / 静态变量初始化的全局变量和静态变量在一块区域内 ( RW ), 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 (ZI), 程序结束后由 OS 统一释放
代码区二进制代码存放函数体的二进制代码

PS: 堆中的数据需要用户自行回收,放置内存泄漏
PS: 静态变量 和 全局变量
属于静态存储方式的变量不一定是静态变量,例如全局变量属于静态存储,但是不一定是静态变量,必须由 static 修饰的变量才属于静态外部变量,或称静态全局变量.

  • 局部变量改为静态变量后改变了其存储方式,即改变列生存周期
  • 全局变量改为静态变量后改变了其作用域,限制了使用范围 (在链接的过程中会体现)

# 例子

#include <stdio.h>
#include <string.h>
int Global_var = 0;       // 全局初始化区
int *Global_Unknown_ptr;  // 全局未初始化区
int main(void){
  int stack_var;                          // 栈
  char stack_str_buf[] = "ABCD";          // "ABCD\0" 在常量区,stack_str_buf 在栈上
  char *stack_p2;                         // 栈
  char *stack_str_ptr = (char*)("12345"); // "12345\0" 在常量区,stack_str_ptr 在栈上
  static int static_var= 0;               // 全局 (静态) 变量初始化区
  Global_Unknown_ptr = (char*)malloc(10); // 
  stack_p2= (char*)malloc(20);            // 申请到的 10 和 20 字节的空间在堆区
  strcpy(Global_Unknown_ptr, "12345");    //"12345\0" 存放在常量区,编译器可能会将它与 stack_str_ptr 所指向的值优化在一个地址
  free(Global_Unknown_ptr);
  free(stack_p2);
  return 0;
}

# 动 / 静态内存分配

程序被加载到内存中,这块内存存在两个属性:静态分配内存和动态分配内存

属性描述
静态分配内存在编译和链接的时候就确定好内存
动态分配内存在程序加载、调用、执行的时候分配 / 回收内存

# 内存分区详解

属性描述
.text也称为代码段 (Code), 用户来存放程序执行代码,同时也可能会包含一些常量。该段内存为静态分配,只读。这块内存是共享的,当多个相同进程存在时,公用一个 text
.data也称为 GVAR (Global value) 用来存放已经初始化的非 0 全局变量。静态分配. data 又可分配为读写 (RW) 区和只读 (RO) 区.(RO) 区保存常量所以也被称为 .constdata ; RW 则是普通全局非 0 变量,静态变量也存储在其中
.bss存放程序中初始化的变量和 0 值全局变量。静态分配,在程序开始通常会被清 0

这三段内存组成了我们编写的程序本体,但是一个程序想要运行起来,还需要更多的数据以及数据间的交互。否则,这个程序就是一个特例化的程序,不具有通用性,没有价值的死程序.
所以程序的运行需为数据交互提供一个平台 —— 堆栈.

PS: .textdata 段都在可执行文件中,由系统从可执行文件中加载; .bss 段不在可执行文件内,由系统初始化.

# 堆栈

属性描述
存放 Automatic Variable, 按内存生长方向由高到低方向生长,其最大值编译确定 (与编译器相关), 速度快,但自由性差,最大空间不大
自由申请的空间,按内存地址由低向高方向生长,其大小由系统内存 / 虚拟内存上限决定。速度慢,但自由性大,可用空间大

每个线程都会由自己的栈,但是堆的空间是公用的

具体内存空间分配如图,地址由下往上生长,分别表示 .text , .data , bss 内存分布

堆 (Heap) 往高地址方向生长,栈 (Stack) 往低地址方向生长

# C/C++ 中的内存分区

在 C/C++ 中,通常可以把内存分为 4 个分区:栈、堆、全局 / 静态存储区和常量存储区

#

通常用于在那些编译器件就可以确定大小的变量,用于在函数作用域内创建,离开作用域后自动销毁变量的存储区。通常是局部变量、函数参数等的存储空间。存储空间是连续的买两个紧密挨着定义的局部变量他们的存储空间也是紧挨着的。栈的大小是有限的,不要定义过大的数组.

#

通常是用于编译期间无法确定大小的变量,其存储空间不连续,一般为用户动态开辟,自行释放。如果开辟后没有释放那么就会出现内存泄漏的问题.
两个紧挨着的指针,开辟出来的空间不一定是相邻的,内存碎片就是这么产生的.
堆的大小几乎不受限制,理论上每个程序最大可达 4GB.

# 全局 / 静态存储区

类似于栈,通常用于那些在编译期间就能确定存储大小的变量,但存储在这的静态变量全局变量在程序运行期间是全局可见

# 常量存储区

用于在编译期间能确定存储大小的常量,在运行期间,存储区内的常量是全局可见的. 这里要特别明确的是,这是一块只允许存放常量,不允许修改的内存

# 四区对比

比较项全局 / 静态区常量存储区
存储内容局部变量变量全局变量、静态变量常量
作用域函数作用域函数作用域或语句块作用域//
编译期间大小是否确定
大小1MB4GB//
内存分配方式地址由高向低减少地址由低向高增加//
内存是否可修改

# 进程在内存中的映射

若存在如下关系调用

void func_1(void);
void func_2(void);
void func_3(void);
...
int main(void){
  ...
  func_1();
  ...
  return 0;
}
void func_1(void){
  ...
  func_2();
  ...
}
void func_2(void){
  ... 
  func_3();
  ...
}
...

则当程序载入操作系统中,其对应的内存映射如下:

(高地址)
+---------------------------------+
|             ......              |   ...略去了一些无关的区域
+---------------------------------+
| env string (环境变量字符串)      |\
+---------------------------------+ \
| argv string (命令行字符串)       |  \
+---------------------------------+   \
| env pointer (环境变量指针)       |  `SHELL`环境变量和命令行参数保存区
+---------------------------------+   /
| argv pointer (命令行参数指针)    |  /
+---------------------------------+ /
| argc (命令行参数个数)            |/
+---------------------------------+
|         main 函数的栈帧          |\
+---------------------------------+ \
|         func_1 函数栈帧          | \
+---------------------------------+   \
|         func_2 函数栈帧          |   \
+---------------------------------+     stack(栈区)    
|         func_3 函数栈帧          |    /
+---------------------------------+   /
|                                 |  /
            ...   ...               /
|                                 |/
+---------------------------------+
|         heap (堆区)             | 
+---------------------------------+
|      Uninitialized(bss) data    | 非初始化数据区
+---------------------------------+
|      Initialized data           | 初始化数据区
+---------------------------------+
|         Text                    | 代码区
+---------------------------------+
(内存低地址)

# 函数的栈帧

包含了函数的参数,至于被调用函数的 参数 是放在 调用函数的栈帧 还是被调用函数栈帧,则依赖于不同系统的实现.
函数的栈帧中的局部变量以及恢复该函数的主调函数的栈帧 (即前一个栈帧) 所需要的数据,包含了主调函数的下一条执行指令的地址
函数调用时所建立的栈帧包含下面的信息:

  1. 函数的返回地址。返回地址 是存放在 主调函数的栈帧 还是被 调用函数的栈帧 里,取决于不同系统的实现
  2. 主调用函数的帧栈信息,栈底和栈顶
  3. 为函数的局部变量分配栈空间
  4. 为被调用函数的参数分配的空间取决具体的不同系统的系统实现

# 未初始化数据区 (BSS)

用于存放程序的静态变量,这部分内存都是被初始化为零的;
而初始化数据区用于存放可执行文件里的初始化数据.
这两个区统称为数据区.

PS: 并不给该段的数据分配空间,仅仅是记录了数据所需空间的大小

# 初始化数据区 (Data)

初始化数据区用于存放可执行文件里的初始化数据.

PS: 为数据分配空间,数据保存在目标文件中

# 代码区 (Text)

只读区,存放了程序的代码.
任何尝试对该区的写操作会导致段违法出错.
代码区是被多个运行该可执行文件的进程所共享的.

# 小结

随着函数调用层数的增加,函数栈帧是一块块地向内存低地址方向延伸的
随着进程中函数调用层数的减少 (即各函数调用的返回), 栈帧会一块块地被遗弃而向内存的高址方向回缩;
各函数的栈帧大小随着函数的性质的不同而不等,由函数的局部变量的数目决定;
进程对内存的动态申请是发生在 Heap (堆) 里的,随着系统动态分配给进程的内存数量的增加,Heap (堆) 有可能向高址或低址延伸,这依赖于不同 CPU 的实现,但一般来说是向内存的高地址方向增长的.
在未初始化数据区 (BSS) 或者 Stack (栈区) 的增长耗尽了系统分配给进程的自由内存的情况下,进程将会被阻塞,重新被操作系统用更大的内存模块来调度运行.

# 验证

#include <stdio.h>
#include <stdlib.h>
int init_global_var = 1;               // 全局 RW 区域
int uninit_global_var;                 // 全局 ZI 区域
static int init_static_global_var = 2; // 全局 RW 区域
static int uninit_static_global_var;   // 全局 ZI 区域
void output(int a);
int main(void)
{
    // 定义局部变量
    int var = 2;
    static int init_static_local_var = 2, uninit_static_local_var;
    int init_loacl_val = 1;
    char *p;
    char str[10] = "😒";
    // 定义字符串常量
    char *str1 = "❤";
    char *str2 = "( o=^•ェ•)o ┏━┓";
    // 动态分配
    int *ptr_1 = (int *)malloc(4);
    int *ptr_2 = (int *)malloc(4);
    // 释放
    free(ptr_1);
    free(ptr_2);
    printf("栈区: 变量地址================================================\n");
    printf("var:                                    %p\n", &var);
    printf("init_loacl_val:                         %p\n", &init_loacl_val);
    printf("str_arr:                                %p\n", &str[0]);
    printf("ptr_1:                                  %p\n", &p);
    printf("ptr_1:                                  %p\n", &ptr_1);
    printf("ptr_2:                                  %p\n\n", &ptr_2);
    printf("堆区: 动态申请地址============================================\n");
    printf("malloc_4:                               %p\n", ptr_1);
    printf("malloc_4:                               %p\n\n", ptr_2);
    printf("全局区-全局变量和静态变量======================================\n");
    printf(".bss段----------------------------------------------------\n");
    printf("全局外部无初值uninit_global_var:        %p\n", &uninit_global_var);
    printf("静态外部无初值uninit_static_global_var: %p\n", &uninit_static_global_var);
    printf("静态内部无初值uninit_static_local_var:  %p\n\n", &uninit_static_local_var);
    printf(".data段---------------------------------------------------\n");
    printf("全局外部有初值init_global_var:          %p\n", &init_global_var);
    printf("静态外部有初值init_static_global_var:   %p\n", &init_static_global_var);
    printf("静态内部有初值init_static_local_var:    %p\n\n", &init_static_local_var);
    printf(".文字常量区---------------------------------------------------\n");
    printf("文字常量地址str1:                       %p\n", str1);
    printf("文字常量地址str2:                       %p\n\n", str2);
    printf(".代码区---------------------------------------------------\n");
    printf("函数地址:                               %p\n", &main);
    printf("函数地址:                               %p\n", &output);
    output(var);
    return 0;
}
void output(int a)
{
    int func_var = 10;
    printf("函数参数                                %p\n", &a);
    printf("函数变量                                %p\n", &func_var);
    printf("(*≧︶≦))( ̄▽ ̄* )ゞ\n");
    printf("\n");
}

Windows
Linux


大道五十,天衍四十九,人遁其一!

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

黑羊 支付宝

支付宝