1.stdint.h标准头文件

stdint.h 是C99标准引入的标准整数类型头文件,C++中也可用 cstdint 。具有以下特点:

使用 stdint.h 配套的格式宏-->根据当前平台自动替换为正确的格式符:

!使用前需包含头文件**<inttypes.h>**,格式宏本身是字符串,需要双引号包裹。

uint8_t age = 18;
printf("年龄:%"PRIu8"", age);

2.C语言从源码到可执行文件经历的四个阶段

四个阶段分别是:预处理、编译、汇编、链接

(1)预处理器预处理阶段 hello.c -->hello.i

该阶段的主要任务是处理带 # 的语句,包括加载头文件、宏替换、条件编译等。

(2)编译器编译阶段 hello.i -->hello.s

(3)汇编器汇编阶段 hello.s -->hello.o

汇编器将汇编语言翻译成机器认识的机器代码,得到 .o 为后缀的二进制文件,该文件也称目标文件。

(4)链接器链接阶段 多个目标文件 -->一个可执行文件

汇编得到的hello.o 是一个不完整的目标文件,里面用到了printf 函数,但printf 函数的实现不在hello.o 中,它只存了一个待填充的跳转地址。
链接器会根据代码里的外部引用,去标准库中找到printf 函数所在的目标文件(以及其他依赖的目标文件),将它们合并为一个整体:

3.四个阶段的内存地址分配问题

先理解佬的超全C内存管理&视频版

相对地址:相对于当前目标文件的偏移地址,汇编阶段生成;
绝对地址:程序运行时在内存中的真实地址,链接阶段生成;
存储段:目标文件、可执行文件的内存分区,包括.text代码段、.data数据段、.rodata只读数据段,堆区、栈区。

4.变量的存储区域和初始化

内存区域 存储内容 初始化规则 生命周期
.data 已初始化的全局或者静态变量 显示赋初始值 程序运行全程
.bss 未初始化的全局或者静态变量 默认初始化为0 程序运行全程
局部变量 无默认初始化值(残留值) 函数调用时创建,结束销毁

.bss段:只记录变量大小,不存储初始值。程序启动时操作系统将.bss段的所有变量置0,不占用可执行文件保存在硬盘上的大小;而 .data 段会占用,存储了初始化值。

5.声明函数的作用

6.头文件

头文件的作用

为源文件提供接口信息,让编译器知道有哪些函数/类型/变量可以用,但不用关心具体实现,让接口和实现分离。

头文件中可以写什么

核心原则:只写“声明”和“不占内存的定义”,不要写“会分配内存的定义”。

//结构体定义
typedef struct{
    int id;
    char name[20];
}student;

//枚举定义
enum Color {RED, GREEN, BLUE};
extern int g_count;

7.头文件重复包含问题

假设你的代码结构如下:

此时 void func_b(); 会被导入两次,重复包含可能带来以下后果:

头文件重复包含问题如何解决?

#pragma once
- 写在头文件第一行,告诉编译器当前头文件仅处理一次;
- 兼容性:主流编译器(GCC/Clang/MSVC)全支持,工业项目首选
// b.h
#ifndef B_H_
#define B_H_
void fun_c();
#endif

#ifndef/#define/#endif 包裹头文件内容,通过唯一宏名标记是否已处理。

8.C内存布局笔记

Image

(1).文本段:.text ,存放机器码,编译链接阶段已经确定大小,只读权限。

objdump -h 可执行文件

(2).静态数据区:分为未初始化的.bss 和已初始化的.data.rodata

Image

未初始化数据区 .bss :大小在编译时确定,运行时可读可写

#反汇编
objdump -h 可执行文件

已初始化数据区.data.rodata

(3).动态数据区:运行时确定,分为堆和栈
栈:编译器自动化管理区域,用于局部变量存储和函数调用,遵循先进后出原则。

堆:适用于动态分配的内存区,灵活,开发者可手工申请+管理

(4). 连续内存和分散内存在数据读取速度方面的差异
CPU在读取数据时,会一次性将目标数据及其邻居一同加载到高速缓存中,因此,

(5).地址空间布局随机化ASLR

(6)现代操作系统(空间较足,堆和栈没有相撞的可能)内存布局

Image