C语言基础阶段
一、C语言关键字
1. static(静态修饰符)
两种用法:
// 1. 限制全局变量和函数的作用域(只能在当前文件使用)
static int global_var = 10; // 只能在当前文件访问
static void func(void) { // 只能在当前文件调用
// ...
}
// 2. 延长局部变量的生命周期(只会初始化一次)
void test(void) {
static int count = 0; // 只在第一次调用时初始化为0
count++; // 每次调用count都会保持上次的值
printf("%d\n", count);
}
// 调用test()三次的输出:
// 第一次: 1
// 第二次: 2
// 第三次: 3
2. extern(外部声明)
作用:声明一个外部符号(函数/全局变量)可以在本文件中使用
// file1.c
int global_value = 100; // 定义全局变量
// file2.c
extern int global_value; // 声明外部变量,告诉编译器在别的文件中
extern void external_func(void); // 声明外部函数
void test(void) {
printf("%d\n", global_value); // 可以使用file1.c中的变量
}
3. const(常量修饰符)
作用:修饰符号为只读(99%情况下指的是变量)
const int MAX_SIZE = 100; // MAX_SIZE是只读的
// MAX_SIZE = 200; // 错误!不能修改const变量
// 指针相关的const
const char *p1; // p1指向的内容是常量(不能通过p1修改)
char * const p2; // p2本身是常量(不能修改p2指向的地址)
const char * const p3; // p3和它指向的内容都是常量
4. volatile(易失性修饰符)
作用:每次都从内存地址中访问数据,防止编译器优化
volatile int flag = 0; // 告诉编译器不要优化这个变量
// 常用于:
// 1. 多线程共享的变量
// 2. 硬件寄存器
// 3. 中断服务程序修改的变量
二、数组、指针、函数
1. 指针相关概念
// 基本指针
int a = 10;
int *p = &a; // p是指向int的指针
// 函数指针:指向函数的指针
int (*pfun)(int); // pfun指向返回int,参数为int的函数
// 对应的函数:int func(int a);
// 指针函数:返回值类型为指针的函数
int *func(int a); // func函数返回int指针
// 数组指针:指向数组的指针
int (*parr)[10]; // parr指向包含10个int的数组
// 对应的数组:int arr[10];
// 指针数组:元素类型为指针的数组
int *arr[10]; // arr是包含10个int指针的数组
// 函数指针数组:元素类型为函数指针的数组
int (*func_arr[10])(int); // 包含10个函数指针的数组
三、结构体和内存对齐
1. 结构体定义
// 定义一个新类型
struct Student {
char name[20];
int age;
float score;
};
// 使用
struct Student stu1;
stu1.age = 20;
// 或者使用typedef
typedef struct {
char name[20];
int age;
} Person;
Person p1; // 可以直接使用
2. 内存对齐
// 设置内存对齐方式
#pragma pack(1) // 按1字节对齐
struct Test1 {
char a;
int b;
}; // 大小为5字节
#pragma pack(4) // 按4字节对齐(默认)
struct Test2 {
char a;
int b;
}; // 大小为8字节(有3字节填充)
// Linux中的对齐方式
struct Test3 {
char a;
int b;
} __attribute__((packed)); // 按1字节对齐
3. 共用体(联合体)
union Data {
int i;
float f;
char str[20];
};
// 所有成员共用同一段内存
// 大小为最大成员的大小(20字节)
四、预处理和宏定义
1. 宏定义
// 简单宏
#define PI 3.14159
// 带参宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
// 使用
int max_val = MAX(10, 20);
int square_val = SQUARE(5); // ((5) * (5))
2. 条件编译
// 基本条件编译
#if 1
// 这部分代码会被编译
#else
// 这部分不会
#endif
// 防止头文件重复包含
#ifndef MY_HEADER_H
#define MY_HEADER_H
// 头文件内容
#endif
// 根据平台编译
#ifdef LINUX
// Linux专用代码
#elif defined(WINDOWS)
// Windows专用代码
#endif
五、C语言编译过程
4个阶段:
main.c → main.i → main.s → main.o → mainapp(bin)
↓ ↓ ↓ ↓ ↓
源代码 预处理后 汇编代码 目标文件 可执行程序
1. 预处理(Preprocessing)
处理内容:
-
去掉注释
-
宏替换
-
头文件展开
-
特殊符号处理
// main.c 中的代码
printf("%s %d i = %d\n", __FILE__, __LINE__, i);
// 预处理后变成(假设在test.c第5行)
printf("test.c 5 i = %d\n", i);
生成文件 :.i文件(符合C语法的纯文本文件)
2. 编译(Compilation)
作用:C语言源文件转换为汇编文件
-
进行C语言的语法分析
-
检查语法错误
-
生成中间代码
生成文件 :.s文件(汇编文件)
3. 汇编(Assembly)
作用:汇编程序转换为二进制机器码
-
将汇编指令转换为机器指令
-
生成可重定位的目标文件
生成文件 :.o文件(目标文件,二进制机器码)
4. 链接(Linking)
作用 :将所有.o文件链接为可执行程序
-
解决符号引用(函数调用、变量引用)
-
合并各个模块
-
添加启动代码
生成文件:可执行二进制文件
注意 :1-3阶段每个.c文件独立处理,n个.c文件会生成n个.o文件
六、文件编程
1. 两种IO接口
// 标准IO(有缓冲区,效率高)
#include <stdio.h>
FILE *fp = fopen("file.txt", "r");
fread(buffer, 1, size, fp);
fclose(fp);
// 系统IO(无缓冲区,效率低,仅Linux)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int fd = open("file.txt", O_RDONLY);
read(fd, buffer, size);
close(fd);
2. 缓冲类型
-
无缓冲:立即读写(如系统IO)
-
行缓冲:遇到换行符或缓冲区满时刷新(如终端输出)
-
全缓冲:缓冲区满时刷新(如文件操作)
3. 性能测试
// 测试磁盘IO效率
// 方法1:每次写1字节,写1000次(效率最低)
// 方法2:每次写100字节,写1000次(效率中等)
// 方法3:每次写1000字节,写1次(效率最高)
// 文件空洞:文件中间有未写入数据的部分
lseek(fd, 1024, SEEK_SET); // 跳到1024字节位置
write(fd, "data", 4); // 创建文件空洞
七、进程与线程
1. 进程空间布局(4GB)
0xFFFFFFFF (4GB) ┌─────────────────┐
│ Kernel │
│ (内核空间) │
0xC0000000 (3GB) ├─────────────────┤ ← 内核空间与用户空间分界线
│ │
│ Stack │ ← 线程独享(每个线程有自己的栈)
│ (栈) │
├─────────────────┤
│ Memory Map │ ← 库函数地址映射区
│ (映射) │
├─────────────────┤
│ Heap │ ← 动态分配内存(需要手动管理)
│ (堆) │
├─────────────────┤
│ BSS │ ← 未初始化的全局/静态变量
│ │
├─────────────────┤
│ Data │ ← 初始化的全局/静态变量
│ (数据段) │
├─────────────────┤
│ Code │ ← 程序代码(二进制指令)
│ (代码段) │
0x00000000 (0GB) └─────────────────┘
2. 各段说明
-
Code段:存储程序二进制代码(只读)
-
Data段:存储初始化为非0的全局变量和静态变量
-
BSS段:存储未初始化或初始化为0的全局变量和静态变量
-
Heap段:用户自行管理的动态内存(malloc/free)
-
Stack段:线程独享,存储局部变量、函数参数、返回地址
-
Map段:存储共享库函数的地址映射
-
Kernel段:内核空间,用户程序不能直接访问
3. 进程 vs 线程
| 特性 | 进程 | 线程 |
|---|---|---|
| 空间大小 | 4GB(用户空间3GB) | 共享进程空间 |
| 栈 | 每个进程有自己的栈 | 每个线程有独立栈(8MB) |
| 创建效率 | 低(需要复制资源) | 高(共享资源) |
| 通信复杂度 | 复杂(需要IPC机制) | 简单(共享内存) |
| 稳定性 | 高(相互隔离) | 低(相互影响) |
| 资源开销 | 大 | 小 |
九、网络基础
1. 网络模型对比
OSI七层模型 TCP/IP四层模型
7. 应用层 应用层
6. 表示层
5. 会话层
4. 传输层 传输层
3. 网络层 网络层
2. 数据链路层 网络接口层
1. 物理层
2. TCP vs UDP
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 有连接 | 无连接 |
| 通信模式 | 一对一 | 一对多/多对多 |
| 数据传输 | 数据流 | 数据报(包) |
| 可靠性 | 高(有应答机制) | 低 |
| 效率 | 低 | 高 |
| 特性 | 拥塞控制、重传机制 | 无控制机制 |
3. 数据包封装过程
应用层数据
↓
+----------------+
| TCP/UDP头部 | ← 传输层
+----------------+
| IP头部 | ← 网络层
+----------------+
| 以太网头部 | ← 链路层
| 以太网校验 |
+----------------+
实际在以太网中传输的数据帧
4. 各层头部关键字段
IP头部(网络层):
-
32位源IP地址
-
32位目的IP地址
-
8位协议类型(TCP=6, UDP=17)
-
16位总长度
TCP头部(传输层):
-
16位源端口
-
16位目的端口
-
32位序列号(数据包序号)
-
32位确认号(应答序号)
-
6位标志位(SYN、ACK、FIN等)
-
16位窗口大小(接收端缓冲区大小)
以太网头部(链路层):
-
6字节目的MAC地址
-
6字节源MAC地址
-
2字节类型(0800=IP, 0806=ARP)
-
46-1500字节数据
-
4字节CRC校验