C/C++ 与系统底层编程
Bjarne Stroustrup — C++ 之父
C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.
C 让你轻松地朝自己的脚开枪;C++ 让这变得更难,但一旦开枪,整条腿都没了。
疾旋鼬觉得这句话太可怕了……可是为了搞安全,必须直面枪林弹雨!
第一部分:C 语言基础
程序结构与编译
// jxy.c — 疾旋鼬的第一个 C 程序
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库(malloc, free, exit 等)
// 程序入口
int main(int argc, char *argv[]) {
// argc: 命令行参数个数
// argv: 参数字符串数组
printf("疾旋鼬登场!\n");
printf("传入了 %d 个参数\n", argc);
for (int i = 0; i < argc; i++) {
printf(" argv[%d] = %s\n", i, argv[i]);
}
return 0; // 返回 0 表示正常退出
}
# 编译与运行
gcc -o jxy jxy.c -Wall -Wextra -g # -Wall: 警告, -g: 调试信息
./jxy hello world
# 输出:
# 疾旋鼬登场!
# 传入了 3 个参数
# argv[0] = ./jxy
# argv[1] = hello
# argv[2] = world
编译流程
源代码到可执行文件经历四个阶段:
基本数据类型
#include <stdio.h>
#include <limits.h>
#include <float.h>
#include <stdint.h>
int main(void) {
// ===== 整数类型 =====
char c = 'A'; // 1 字节, 范围: -128 ~ 127
unsigned char uc = 255; // 1 字节, 范围: 0 ~ 255
short s = -32768; // 2 字节
unsigned short us = 65535; // 2 字节
int i = 2147483647; // 4 字节(典型)
unsigned int ui = 4294967295U; // 4 字节
long l = 2147483647L; // 4 或 8 字节(取决于平台)
long long ll = 9223372036854775807LL; // 8 字节
// 定长整数(推荐在安全编程中使用)
int8_t i8 = -128; // 精确 1 字节
int16_t i16 = -32768; // 精确 2 字节
int32_t i32 = -2147483648; // 精确 4 字节
int64_t i64 = -9223372036854775807LL - 1; // 精确 8 字节
uint32_t u32 = 4294967295U; // 无符号 4 字节
size_t sz = 0; // sizeof 返回值类型,平台相关的无符号整数
// ===== 浮点类型 =====
float f = 3.14f; // 4 字节, 约 6-7 位有效数字
double d = 3.141592653589793; // 8 字节, 约 15-16 位有效数字
long double ld = 3.141592653589793238L; // 12 或 16 字节
// ===== 布尔类型(C99) =====
#include <stdbool.h>
bool flag = true; // 实际上是 _Bool,true=1, false=0
// ===== 各类型的大小 =====
printf("sizeof(char) = %zu\n", sizeof(char)); // 1
printf("sizeof(int) = %zu\n", sizeof(int)); // 4
printf("sizeof(long) = %zu\n", sizeof(long)); // 4 (Win64) 或 8 (Linux64)
printf("sizeof(long long) = %zu\n", sizeof(long long)); // 8
printf("sizeof(float) = %zu\n", sizeof(float)); // 4
printf("sizeof(double) = %zu\n", sizeof(double)); // 8
printf("sizeof(void *) = %zu\n", sizeof(void *)); // 4 (32位) 或 8 (64位)
// ===== 整数溢出(安全大敌) =====
int max_int = INT_MAX; // 2147483647
printf("INT_MAX = %d\n", max_int);
printf("INT_MAX+1 = %d\n", max_int + 1); // 未定义行为(通常变为 INT_MIN)
// 疾旋鼬提醒:整数溢出是 C 程序中最常见的安全漏洞之一!
return 0;
}
变量、常量与作用域
#include <stdio.h>
// 全局变量(存储在 .data 或 .bss 段)
int g_pal_count = 0;
int g_uninit; // 未初始化的全局变量,自动为 0(.bss 段)
// const 常量
const int MAX_PAL = 1024;
// 宏常量(预处理阶段替换,无类型)
#define PAL_NAME_LEN 64
#define PI 3.14159265358979
// 枚举(有名字的整数常量)
enum ElementType {
ELEM_DRAGON = 0,
ELEM_FIRE = 1,
ELEM_WATER = 2,
ELEM_GRASS = 3,
ELEM_ELECTRIC = 4,
ELEM_COUNT // 自动为 5
};
// typedef:给类型起别名
typedef unsigned char u8;
typedef unsigned int u32;
typedef struct Pal Pal; // 前向声明
int main(void) {
// 局部变量(存储在栈上)
int hp = 100;
// static 局部变量:生命周期为整个程序运行期间,但只在本函数内可见
static int call_count = 0; // 只初始化一次
call_count++;
printf("函数被调用 %d 次\n", call_count);
// 作用域与遮蔽(shadowing)
int x = 10;
{
int x = 20; // 遮蔽外层的 x
printf("%d\n", x); // 20
}
printf("%d\n", x); // 10
// 使用枚举
enum ElementType elem = ELEM_DRAGON;
if (elem == ELEM_DRAGON) {
printf("疾旋鼬是龙属性帕鲁!\n");
}
// 使用 typedef
u8 level = 50;
u32 exp = 1234567;
return 0;
}
控制流
#include <stdio.h>
int main(void) {
int hp = 75;
// ===== if-else =====
if (hp >= 80) {
printf("疾旋鼬状态良好!\n");
} else if (hp >= 50) {
printf("疾旋鼬有点累了……\n");
} else {
printf("疾旋鼬危险了!快治疗!\n");
}
// switch-case(注意 fall-through)
enum ElementType elem = ELEM_DRAGON;
switch (elem) {
case ELEM_DRAGON:
printf("龙属性\n");
break; // 必须 break,否则 fall-through
case ELEM_FIRE:
printf("火属性\n");
break;
case ELEM_WATER:
printf("水属性\n");
break;
default:
printf("未知属性\n");
break;
}
// ===== for 循环 =====
for (int i = 0; i < 5; i++) {
printf("第 %d 回合\n", i + 1);
}
// ===== while 循环 =====
int countdown = 5;
while (countdown > 0) {
printf("%d...\n", countdown--);
}
printf("疾旋鼬发射!\n");
// ===== do-while(至少执行一次) =====
int input;
do {
printf("请输入指令 (0=退出): ");
// scanf("%d", &input);
input = 0; // 模拟
} while (input != 0);
// ===== break 与 continue =====
for (int i = 0; i < 100; i++) {
if (i == 10) break; // 立即退出循环
if (i % 3 == 0) continue; // 跳过本次迭代
printf("%d ", i); // 1 2 4 5 7 8
}
printf("\n");
// ===== goto(慎用,但在错误处理中有用) =====
int *buffer = NULL;
buffer = malloc(1024);
if (!buffer) goto cleanup;
// ... 使用 buffer ...
cleanup:
free(buffer);
return 0;
}
函数
#include <stdio.h>
#include <stdarg.h>
// ===== 基本函数 =====
// 函数声明(原型)
int add(int a, int b);
void greet(const char *name);
// 函数定义
int add(int a, int b) {
return a + b;
}
void greet(const char *name) {
printf("你好,%s!疾旋鼬向你问好!\n", name);
}
// ===== 值传递 vs 指针传递 =====
void try_modify(int x) {
x = 999; // 只修改了副本,原值不变
}
void real_modify(int *x) {
*x = 999; // 通过指针修改原值
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// ===== 数组参数退化为指针 =====
// arr[] 等价于 *arr,丢失了长度信息
void print_array(int arr[], int len) {
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 更明确的写法
void print_array_ptr(int *arr, int len) {
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]); // arr[i] 等价于 *(arr + i)
}
printf("\n");
}
// ===== 可变参数函数 =====
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// ===== 函数指针 =====
// 函数指针类型定义
typedef int (*MathFunc)(int, int);
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b ? a / b : 0; }
// 使用函数指针实现策略模式
int apply_op(int a, int b, MathFunc op) {
return op(a, b);
}
int main(void) {
greet("桃旋鼬");
// 值传递 vs 指针传递
int val = 42;
try_modify(val);
printf("try_modify 后: %d\n", val); // 42(不变)
real_modify(&val);
printf("real_modify 后: %d\n", val); // 999(被修改)
// swap
int x = 10, y = 20;
swap(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y); // x=20, y=10
// 可变参数
printf("sum(3, 10, 20, 30) = %d\n", sum(3, 10, 20, 30)); // 60
// 函数指针
MathFunc ops[] = {add, multiply, divide};
const char *names[] = {"加", "乘", "除"};
for (int i = 0; i < 3; i++) {
printf("%s: %d\n", names[i], apply_op(12, 4, ops[i]));
}
// 回调函数(qsort 使用函数指针进行比较)
#include <stdlib.h>
int nums[] = {5, 2, 8, 1, 9, 3};
int n = sizeof(nums) / sizeof(nums[0]);
// 比较函数:升序
int cmp_asc(const void *a, const void *b) {
return (*(int *)a) - (*(int *)b);
}
qsort(nums, n, sizeof(int), cmp_asc);
print_array(nums, n); // 1 2 3 5 8 9
return 0;
}
数组与字符串
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
// ===== 数组 =====
int scores[5] = {95, 87, 100, 76, 88};
// int auto_size[] = {1, 2, 3}; // 编译器自动推断大小为 3
// 遍历
for (int i = 0; i < 5; i++) {
printf("疾旋鼬第 %d 战得分: %d\n", i + 1, scores[i]);
}
// 二维数组
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("matrix[1][2] = %d\n", matrix[1][2]); // 7
// ===== 字符串(以 '\0' 结尾的字符数组)=====
char name[20] = "疾旋鼬"; // 自动添加 '\0'
char greeting[] = "Hello"; // 编译器自动计算大小(6,含 '\0')
char buf[64];
// 字符串长度 vs 数组大小
printf("strlen(name) = %zu\n", strlen(name)); // 字符串长度(不含 '\0')
printf("sizeof(name) = %zu\n", sizeof(name)); // 数组大小(含 '\0',20)
// 常用字符串函数(<string.h>)
strcpy(buf, name); // 拷贝(危险:无长度检查!)
strncpy(buf, name, sizeof(buf) - 1); // 安全拷贝
buf[sizeof(buf) - 1] = '\0'; // 确保 null 结尾
strcat(buf, " 最强!"); // 拼接(危险:无长度检查!)
strncat(buf, "!!", sizeof(buf) - strlen(buf) - 1); // 安全拼接
int cmp = strcmp("疾旋鼬", "桃旋鼬"); // 比较: <0, 0, >0
char *found = strstr("疾旋鼬love工管", "love"); // 子串搜索
// 格式化字符串
sprintf(buf, "疾旋鼬 HP=%d ATK=%d", 120, 85);
snprintf(buf, sizeof(buf), "疾旋鼬 HP=%d", 120); // 安全版本
printf("%s\n", buf);
// ===== 字符串转数字 =====
int n = atoi("42"); // 字符串 → int
long l = strtol("12345", NULL, 10); // 更安全的转换
double d = atof("3.14"); // 字符串 → double
return 0;
}
缓冲区溢出——C 语言最经典的安全漏洞
指针
指针是 C 语言最强大也最危险的特性,理解指针是理解系统底层和安全漏洞的基础。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// ===== 指针基础 =====
int hp = 120;
int *p_hp = &hp; // p_hp 存储 hp 的地址
printf("hp 的值: %d\n", hp); // 120
printf("hp 的地址: %p\n", (void *)&hp); // 0x7ffd...
printf("p_hp 的值: %p\n", (void *)p_hp); // 同上
printf("*p_hp 的值: %d\n", *p_hp); // 120(解引用)
// 通过指针修改值
*p_hp = 200;
printf("修改后 hp = %d\n", hp); // 200
// ===== 指针算术 =====
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // 数组名退化为指向首元素的指针
printf("p[0] = %d, *(p+1) = %d, *(p+2) = %d\n",
p[0], *(p + 1), *(p + 2)); // 10, 20, 30
// p + i 等价于 &arr[i]
// *(p + i) 等价于 arr[i]
// ===== 指针与数组的区别 =====
int a[5] = {1, 2, 3, 4, 5};
int *ptr = a;
printf("sizeof(a) = %zu\n", sizeof(a)); // 20(整个数组的大小)
printf("sizeof(ptr) = %zu\n", sizeof(ptr)); // 8(指针本身的大小)
// ===== void 指针(通用指针)=====
void *vp = &hp;
// void * 可以指向任何类型,但不能直接解引用
printf("hp = %d\n", *(int *)vp); // 需要先转换类型
// malloc 就返回 void *
void *mem = malloc(1024);
memset(mem, 0, 1024);
free(mem);
// ===== 多级指针 =====
int val = 42;
int *p1 = &val; // 一级指针
int **p2 = &p1; // 二级指针
int ***p3 = &p2; // 三级指针
printf("***p3 = %d\n", ***p3); // 42
// ===== 函数指针 =====
int (*operation)(int, int);
int add(int a, int b) { return a + b; }
operation = add;
printf("result = %d\n", operation(3, 4)); // 7
// ===== NULL 指针 =====
int *np = NULL;
// *np = 42; // 段错误!解引用 NULL 是未定义行为
if (np != NULL) {
printf("安全访问\n");
}
// ===== 指针与 const =====
int x = 10, y = 20;
const int *pc = &x; // 指向常量的指针:不能通过 pc 修改 *pc
// *pc = 99; // 编译错误
pc = &y; // 但可以改变 pc 本身
int *const cp = &x; // 常量指针:不能改变 cp 本身
*cp = 99; // 但可以修改 *cp
// cp = &y; // 编译错误
const int *const cpc = &x; // 都不能改
return 0;
}
内存管理
理解 C 的内存布局是理解安全漏洞(堆溢出、UAF、double free)的关键。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 全局变量
int g_init = 42; // .data 段(已初始化)
int g_uninit; // .bss 段(未初始化,自动为 0)
const char *g_msg = "疾旋鼬"; // .rodata 段(只读)
int main(void) {
/*
C 程序内存布局(从低地址到高地址):
┌─────────────┐ 低地址
│ .text │ 代码段(只读,存放机器指令)
├─────────────┤
│ .rodata │ 只读数据(字符串字面量等)
├─────────────┤
│ .data │ 已初始化的全局/静态变量
├─────────────┤
│ .bss │ 未初始化的全局/静态变量(零初始化)
├─────────────┤
│ Heap ↑ │ 堆(向高地址增长,malloc/free 管理)
│ ... │
│ ↓ Stack │ 栈(向低地址增长,函数调用帧)
├─────────────┤
│ 内核空间 │
└─────────────┘ 高地址
*/
// ===== 栈内存 =====
int local = 100; // 栈上的局部变量
char stack_buf[64]; // 栈上的数组
// 栈上的变量在函数返回时自动释放
// ===== 堆内存(手动管理)=====
// malloc: 分配指定字节数的内存(不初始化)
int *arr = (int *)malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "内存分配失败!\n");
return 1;
}
// calloc: 分配并初始化为零
int *zeros = (int *)calloc(10, sizeof(int));
// realloc: 重新分配大小
arr = (int *)realloc(arr, 20 * sizeof(int));
// 注意:realloc 可能返回新地址,原内存被释放
// 使用堆内存
for (int i = 0; i < 10; i++) {
arr[i] = i * 10;
}
// free: 释放堆内存
free(arr);
free(zeros);
// ===== 悬垂指针(Dangling Pointer) =====
int *dangle = (int *)malloc(sizeof(int));
*dangle = 42;
free(dangle);
// dangle 仍然指向已被释放的内存!
// *dangle = 99; // 未定义行为(UAF: Use After Free)
dangle = NULL; // 好习惯:释放后置 NULL
// ===== Double Free =====
int *p = (int *)malloc(sizeof(int));
free(p);
// free(p); // 未定义行为:Double Free(可被利用)
// ===== 内存泄漏 =====
int *leak = (int *)malloc(1024);
// 如果忘记 free(leak),内存就泄漏了
// 程序结束时 OS 会回收,但长期运行的程序会耗尽内存
free(leak);
// ===== 堆溢出 =====
char *buf = (char *)malloc(16);
// strcpy(buf, "疾旋鼬AAAABBBBCCCCDDDD"); // 写入超过 16 字节 → 堆溢出!
// 堆溢出可以覆盖相邻的堆元数据,导致任意代码执行
free(buf);
return 0;
}
结构体与联合体
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// ===== 结构体定义 =====
#define MAX_NAME_LEN 64
#define MAX_SKILLS 4
typedef struct {
char name[MAX_NAME_LEN];
int element; // 属性:0=龙, 1=火, 2=水 ...
int hp;
int attack;
int defense;
int speed;
char *description; // 堆上的字符串
} Pal;
// 结构体内存对齐
struct AlignmentDemo {
char a; // 1 字节 + 3 字节填充
int b; // 4 字节
char c; // 1 字节 + 7 字节填充
long d; // 8 字节
}; // sizeof = 24,不是 14!
// pragma pack 可以取消对齐(但会影响性能)
#pragma pack(push, 1)
struct PackedStruct {
char a; // 1 字节
int b; // 4 字节
char c; // 1 字节
long d; // 8 字节
}; // sizeof = 14(紧凑排列)
#pragma pack(pop)
// ===== 位域(Bit Fields) =====
typedef struct {
unsigned int is_alive : 1; // 1 bit: 0 或 1
unsigned int is_tamed : 1; // 1 bit
unsigned int element : 4; // 4 bits: 0-15
unsigned int level : 7; // 7 bits: 0-127
unsigned int rarity : 3; // 3 bits: 0-7
} PalFlags;
// ===== 联合体(Union) =====
// 所有成员共享同一块内存,大小等于最大成员的大小
typedef union {
uint32_t raw; // 原始 32 位
struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} color; // RGBA 分量
} Color;
// ===== 链表 =====
typedef struct Node {
Pal *pal;
struct Node *next; // 自引用结构
} Node;
// 创建节点
Node *create_node(Pal *pal) {
Node *node = (Node *)malloc(sizeof(Node));
if (!node) return NULL;
node->pal = pal;
node->next = NULL;
return node;
}
// 插入链表头部
void list_prepend(Node **head, Pal *pal) {
Node *node = create_node(pal);
if (!node) return;
node->next = *head;
*head = node;
}
// 遍历链表
void list_print(Node *head) {
Node *cur = head;
while (cur) {
printf(" %s (HP=%d, ATK=%d)\n",
cur->pal->name, cur->pal->hp, cur->pal->attack);
cur = cur->next;
}
}
// 释放链表
void list_free(Node *head) {
while (head) {
Node *next = head->next;
free(head->pal->description);
free(head->pal);
free(head);
head = next;
}
}
int main(void) {
// 创建帕鲁
Pal *jxy = (Pal *)malloc(sizeof(Pal));
strncpy(jxy->name, "疾旋鼬", MAX_NAME_LEN - 1);
jxy->element = 0; // 龙
jxy->hp = 120;
jxy->attack = 85;
jxy->defense = 70;
jxy->speed = 95;
jxy->description = strdup("蜷缩身体高速旋转的龙属性帕鲁");
// 结构体访问
printf("名字: %s\n", jxy->name);
printf("HP: %d\n", jxy->hp);
// 结构体指针访问(箭头操作符)
Pal *p = jxy;
printf("ATK: %d\n", p->attack); // 等价于 (*p).attack
// 内存对齐
printf("sizeof(AlignmentDemo) = %zu\n", sizeof(struct AlignmentDemo)); // 24
printf("sizeof(PackedStruct) = %zu\n", sizeof(struct PackedStruct)); // 14
// 位域
PalFlags flags = {0};
flags.is_alive = 1;
flags.element = 0; // 龙
flags.level = 50;
flags.rarity = 4;
printf("sizeof(PalFlags) = %zu\n", sizeof(PalFlags)); // 4 字节
// 联合体
Color c;
c.color.r = 255;
c.color.g = 100;
c.color.b = 50;
c.color.a = 255;
printf("颜色值: 0x%08X\n", c.raw); // 可以用原始 32 位读取
// 链表
Node *team = NULL;
// 创建更多帕鲁...
Pal *tmd = (Pal *)malloc(sizeof(Pal));
strncpy(tmd->name, "捣蛋猫", MAX_NAME_LEN - 1);
tmd->hp = 80;
tmd->attack = 60;
tmd->description = strdup("调皮的猫型帕鲁");
list_prepend(&team, tmd);
list_prepend(&team, jxy);
printf("疾旋鼬的队伍:\n");
list_print(team);
list_free(team);
return 0;
}
预处理器
// ===== 宏定义 =====
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
// 带副作用的宏参数可能出错
// MAX(i++, j++) 会展开为 ((i++) > (j++) ? (i++) : (j++))
// 安全的宏(使用 GCC 语句表达式)
#define SAFE_MAX(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
// ===== 条件编译 =====
#define DEBUG 1
void log_msg(const char *msg) {
#if DEBUG
fprintf(stderr, "[DEBUG] %s\n", msg);
#endif
}
// 头文件保护(防止重复包含)
#ifndef PAL_H
#define PAL_H
typedef struct { /* ... */ } Pal;
#endif // PAL_H
// #pragma once // 更简洁的替代方案(非标准但广泛支持)
// ===== 字符串化与连接 =====
#define STR(x) #x // 将参数字符串化
#define CONCAT(a, b) a##b // 将两个 token 连接
int CONCAT(my_, var) = 42; // 展开为: int my_var = 42;
printf("%s\n", STR(疾旋鼬)); // 输出: "疾旋鼬"
// ===== 预定义宏 =====
printf("文件: %s\n", __FILE__); // 当前文件名
printf("行号: %d\n", __LINE__); // 当前行号
printf("函数: %s\n", __func__); // 当前函数名(C99)
printf("日期: %s\n", __DATE__); // 编译日期
printf("时间: %s\n", __TIME__); // 编译时间
// 自定义断言宏
#define ASSERT(cond) \
do { \
if (!(cond)) { \
fprintf(stderr, "断言失败: %s\n" \
" 文件: %s, 行: %d\n" \
" 函数: %s\n", \
#cond, __FILE__, __LINE__, __func__); \
abort(); \
} \
} while (0)
文件 I/O 与系统调用
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
int main(void) {
// ===== 标准 I/O(缓冲) =====
FILE *fp = fopen("pal_data.txt", "w");
if (!fp) {
perror("fopen 失败"); // 自动打印错误原因
return 1;
}
fprintf(fp, "疾旋鼬,龙,120,85\n");
fprintf(fp, "捣蛋猫,无,80,60\n");
fclose(fp);
// 读取
fp = fopen("pal_data.txt", "r");
char line[256];
while (fgets(line, sizeof(line), fp)) {
// 去除末尾换行符
line[strcspn(line, "\n")] = '\0';
printf("读取: %s\n", line);
}
fclose(fp);
// 二进制读写
typedef struct { int id; char name[32]; int hp; } PalRecord;
PalRecord records[] = {
{1, "疾旋鼬", 120},
{2, "捣蛋猫", 80},
};
int count = sizeof(records) / sizeof(records[0]);
// 写二进制文件
FILE *bin = fopen("pal.dat", "wb");
fwrite(&count, sizeof(int), 1, bin);
fwrite(records, sizeof(PalRecord), count, bin);
fclose(bin);
// 读二进制文件
bin = fopen("pal.dat", "rb");
fread(&count, sizeof(int), 1, bin);
PalRecord loaded[count];
fread(loaded, sizeof(PalRecord), count, bin);
fclose(bin);
// ===== 系统 I/O(无缓冲,直接系统调用)=====
int fd = open("raw_data.bin", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open 失败");
return 1;
}
const char *data = "疾旋鼬原始数据";
ssize_t written = write(fd, data, strlen(data));
close(fd);
// 读取
fd = open("raw_data.bin", O_RDONLY);
char buf[256];
ssize_t nread = read(fd, buf, sizeof(buf) - 1);
buf[nread] = '\0';
close(fd);
printf("读到: %s\n", buf);
// ===== 文件信息 =====
struct stat st;
if (stat("pal_data.txt", &st) == 0) {
printf("文件大小: %ld 字节\n", (long)st.st_size);
printf("权限: %o\n", st.st_mode & 0777);
printf("修改时间: %ld\n", (long)st.st_mtime);
}
// 删除文件
unlink("raw_data.bin");
return 0;
}
第二部分:C++ 面向对象程序设计
C++ 在 C 的基础上增加了类、继承、多态、模板等特性,同时保持了对底层内存的直接控制。
类与对象
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// ===== 基本类 =====
class Pal {
private:
// 私有成员:外部不可访问
std::string name_;
int hp_;
int max_hp_;
int attack_;
int level_;
protected:
// 保护成员:本类和子类可访问
int experience_ = 0;
public:
// 构造函数
Pal(const std::string& name, int hp, int attack)
: name_(name), hp_(hp), max_hp_(hp), attack_(attack), level_(1)
{
std::cout << name_ << " 诞生了!" << std::endl;
}
// 委托构造函数
Pal() : Pal("未知帕鲁", 100, 50) {}
// 拷贝构造函数
Pal(const Pal& other)
: name_(other.name_ + "(克隆体)"), hp_(other.hp_),
max_hp_(other.max_hp_), attack_(other.attack_), level_(other.level_)
{
std::cout << name_ << " 被克隆了!" << std::endl;
}
// 移动构造函数(C++11)
Pal(Pal&& other) noexcept
: name_(std::move(other.name_)), hp_(other.hp_),
max_hp_(other.max_hp_), attack_(other.attack_), level_(other.level_)
{
other.hp_ = 0;
std::cout << name_ << " 被移动了!" << std::endl;
}
// 析构函数
virtual ~Pal() {
std::cout << name_ << " 退出战斗……" << std::endl;
}
// 赋值运算符
Pal& operator=(const Pal& other) {
if (this != &other) { // 防止自赋值
name_ = other.name_;
hp_ = other.hp_;
max_hp_ = other.max_hp_;
attack_ = other.attack_;
level_ = other.level_;
}
return *this;
}
// getter 方法(const 成员函数:不修改对象状态)
const std::string& name() const { return name_; }
int hp() const { return hp_; }
int level() const { return level_; }
bool is_alive() const { return hp_ > 0; }
// 普通成员函数
void take_damage(int damage) {
hp_ -= damage;
if (hp_ < 0) hp_ = 0;
std::cout << name_ << " 受到 " << damage << " 点伤害!"
<< " (HP: " << hp_ << "/" << max_hp_ << ")" << std::endl;
}
int attack_target(Pal& target) {
int damage = attack_ + level_ * 2;
std::cout << name_ << " 对 " << target.name() << " 发动攻击!" << std::endl;
target.take_damage(damage);
return damage;
}
// 虚函数:允许子类重写
virtual std::string special_move() const {
return name_ + " 发动了普通攻击!";
}
// 纯虚函数(使 Pal 成为抽象类,不能直接实例化)
// virtual std::string element_name() const = 0;
// 友元函数:可以访问私有成员
friend std::ostream& operator<<(std::ostream& os, const Pal& pal);
// 静态成员
static int total_pals;
static int get_total() { return total_pals; }
};
int Pal::total_pals = 0;
// 运算符重载(友元函数)
std::ostream& operator<<(std::ostream& os, const Pal& pal) {
os << "[" << pal.name_ << "] HP:" << pal.hp_
<< " LV:" << pal.level_ << " ATK:" << pal.attack_;
return os;
}
继承与多态
// ===== 继承 =====
class DragonPal : public Pal {
private:
int dragon_power_;
public:
DragonPal(const std::string& name, int hp, int attack, int dragon_power)
: Pal(name, hp, attack), dragon_power_(dragon_power)
{
++total_pals;
}
~DragonPal() override {
--total_pals;
}
// 重写虚函数
std::string special_move() const override {
return name() + " 发动了龙之怒!(威力:" + std::to_string(dragon_power_) + ")";
}
// 子类独有方法
void dragon_roar() const {
std::cout << name() << ": 嗷呜——!龙威震慑全场!" << std::endl;
}
};
class FirePal : public Pal {
private:
int fire_power_;
public:
FirePal(const std::string& name, int hp, int attack, int fire_power)
: Pal(name, hp, attack), fire_power_(fire_power)
{
++total_pals;
}
std::string special_move() const override {
return name() + " 发动了烈焰风暴!(威力:" + std::to_string(fire_power_) + ")";
}
};
// ===== 多态的使用 =====
void battle(Pal& a, Pal& b) {
// 通过基类引用调用虚函数,运行时根据实际类型分派
std::cout << a.special_move() << std::endl;
std::cout << b.special_move() << std::endl;
a.attack_target(b);
b.attack_target(a);
}
// ===== 多重继承 =====
class Flyable {
public:
virtual ~Flyable() = default;
virtual void fly() const {
std::cout << "在天空中飞翔!" << std::endl;
}
};
class Swimmable {
public:
virtual ~Swimmable() = default;
virtual void swim() const {
std::cout << "在水中畅游!" << std::endl;
}
};
class WaterDragonPal : public DragonPal, public Flyable, public Swimmable {
public:
WaterDragonPal(const std::string& name, int hp, int attack, int dp)
: DragonPal(name, hp, attack, dp) {}
void fly() const override {
std::cout << name() << " 展开龙翼,腾空而起!" << std::endl;
}
void swim() const override {
std::cout << name() << " 潜入深海,水龙翻腾!" << std::endl;
}
};
int main() {
// 创建帕鲁
DragonPal jxy("疾旋鼬", 120, 85, 50);
FirePal fire("火绒狐", 90, 95, 60);
// 多态
battle(jxy, fire);
// 动态多态(通过指针)
std::vector<std::unique_ptr<Pal>> team;
team.push_back(std::make_unique<DragonPal>("疾旋鼬", 120, 85, 50));
team.push_back(std::make_unique<FirePal>("火绒狐", 90, 95, 60));
for (const auto& pal : team) {
// 运行时多态:根据实际类型调用对应的 special_move
std::cout << pal->special_move() << std::endl;
}
// 多重继承
WaterDragonPal wjxy("海龙疾旋鼬", 150, 100, 70);
wjxy.fly();
wjxy.swim();
wjxy.dragon_roar();
return 0;
}
模板与泛型编程
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <stdexcept>
// ===== 函数模板 =====
template<typename T>
T safe_max(T a, T b) {
return (a > b) ? a : b;
}
// 模板特化
template<>
const char* safe_max<const char*>(const char* a, const char* b) {
return (std::strcmp(a, b) > 0) ? a : b;
}
// ===== 类模板 =====
template<typename T, size_t N>
class FixedArray {
private:
T data_[N];
size_t size_ = 0;
public:
// 添加元素
void push_back(const T& value) {
if (size_ >= N) {
throw std::overflow_error("FixedArray 已满!疾旋鼬塞不下了!");
}
data_[size_++] = value;
}
// 访问元素(带边界检查)
T& at(size_t index) {
if (index >= size_) {
throw std::out_of_range("索引越界!疾旋鼬跑出地图了!");
}
return data_[index];
}
const T& at(size_t index) const {
if (index >= size_) {
throw std::out_of_range("索引越界!");
}
return data_[index];
}
// 下标访问(不检查边界,性能优先)
T& operator[](size_t index) { return data_[index]; }
const T& operator[](size_t index) const { return data_[index]; }
size_t size() const { return size_; }
size_t capacity() const { return N; }
// 迭代器支持
T* begin() { return data_; }
T* end() { return data_ + size_; }
const T* begin() const { return data_; }
const T* end() const { return data_ + size_; }
};
// ===== 可变参数模板(C++11)=====
template<typename T>
void print(const T& value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void print(const T& first, const Args&... rest) {
std::cout << first << " ";
print(rest...); // 递归展开
}
// ===== 模板元编程(编译期计算)=====
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
// 函数模板
std::cout << safe_max(3, 7) << std::endl; // 7
std::cout << safe_max(3.14, 2.71) << std::endl; // 3.14
// 类模板
FixedArray<std::string, 3> pals;
pals.push_back("疾旋鼬");
pals.push_back("捣蛋猫");
pals.push_back("桃旋鼬");
// pals.push_back("火绒狐"); // 抛出异常
for (const auto& pal : pals) {
std::cout << pal << " ";
}
std::cout << std::endl;
// 可变参数模板
print("疾旋鼬", "等级:", 50, "HP:", 120);
// 输出: 疾旋鼬 等级: 50 HP: 120
// 编译期计算
static_assert(Factorial<5>::value == 120, "5! 应该等于 120");
std::cout << "5! = " << Factorial<5>::value << std::endl;
return 0;
}
第三部分:现代 C++(C++11/14/17/20)
自动类型推导与范围 for
#include <iostream>
#include <vector>
#include <map>
#include <string>
int main() {
// auto:让编译器推导类型
auto x = 42; // int
auto pi = 3.14; // double
auto name = std::string("疾旋鼬");
auto nums = std::vector<int>{1, 2, 3, 4, 5};
// 范围 for(C++11)
for (const auto& n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;
// 结构化绑定(C++17)
std::map<std::string, int> pal_hp = {
{"疾旋鼬", 120},
{"捣蛋猫", 80},
{"桃旋鼬", 100},
};
for (const auto& [name, hp] : pal_hp) {
std::cout << name << " HP=" << hp << std::endl;
}
// decltype:获取表达式的类型
int a = 10;
decltype(a) b = 20; // b 的类型与 a 相同(int)
return 0;
}
智能指针(内存安全的关键)
#include <iostream>
#include <memory>
#include <vector>
#include <string>
class Pal {
public:
std::string name;
int hp;
Pal(const std::string& n, int h) : name(n), hp(h) {
std::cout << name << " 被创建" << std::endl;
}
~Pal() {
std::cout << name << " 被销毁" << std::endl;
}
void status() const {
std::cout << name << " HP=" << hp << std::endl;
}
};
int main() {
// ===== unique_ptr:独占所有权 =====
// 同一时刻只有一个 unique_ptr 指向对象
auto jxy = std::make_unique<Pal>("疾旋鼬", 120);
jxy->status();
// auto jxy2 = jxy; // 编译错误!不能拷贝
auto jxy2 = std::move(jxy); // 可以移动,jxy 变为 nullptr
// jxy->status(); // 未定义行为(jxy 已为空)
jxy2->status();
// unique_ptr 适合放在容器中
std::vector<std::unique_ptr<Pal>> team;
team.push_back(std::make_unique<Pal>("疾旋鼬", 120));
team.push_back(std::make_unique<Pal>("捣蛋猫", 80));
team.push_back(std::make_unique<Pal>("桃旋鼬", 100));
for (const auto& pal : team) {
pal->status();
}
// 离开作用域时自动释放所有帕鲁
// ===== shared_ptr:共享所有权 =====
// 多个 shared_ptr 可以指向同一对象,引用计数管理生命周期
auto shared_jxy = std::make_shared<Pal>("疾旋鼬", 120);
std::cout << "引用计数: " << shared_jxy.use_count() << std::endl; // 1
{
auto copy = shared_jxy; // 拷贝,引用计数 +1
std::cout << "引用计数: " << shared_jxy.use_count() << std::endl; // 2
copy->status();
} // copy 离开作用域,引用计数 -1
std::cout << "引用计数: " << shared_jxy.use_count() << std::endl; // 1
// 最后一个 shared_ptr 销毁时,对象被释放
// ===== weak_ptr:弱引用(不增加引用计数)=====
// 用于打破循环引用
std::weak_ptr<Pal> weak_ref = shared_jxy;
std::cout << "weak_ptr expired? " << weak_ref.expired() << std::endl; // 0 (false)
if (auto locked = weak_ref.lock()) {
locked->status(); // 安全访问
}
shared_jxy.reset(); // 释放对象
std::cout << "weak_ptr expired? " << weak_ref.expired() << std::endl; // 1 (true)
// ===== 自定义删除器 =====
auto file_deleter = [](FILE* fp) {
if (fp) {
std::cout << "关闭文件" << std::endl;
fclose(fp);
}
};
std::unique_ptr<FILE, decltype(file_deleter)> file(
fopen("data.txt", "w"), file_deleter);
if (file) {
fprintf(file.get(), "疾旋鼬写入的数据\n");
}
// 离开作用域时自动调用 file_deleter 关闭文件
return 0;
}
RAII 与智能指针
RAII(Resource Acquisition Is Initialization)是 C++ 的核心理念:资源的获取即初始化,资源的释放即析构。智能指针是 RAII 的典型应用——它将堆内存的生命周期绑定到栈上对象的生命周期,当对象离开作用域时自动释放内存,从而杜绝内存泄漏、悬垂指针和 double free。
在安全编程中,永远优先使用智能指针而不是裸 new/delete。
Lambda 表达式与函数式编程
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9, 3, 7, 4, 6};
// ===== Lambda 基础 =====
// [捕获列表](参数列表) -> 返回类型 { 函数体 }
auto add = [](int a, int b) -> int {
return a + b;
};
std::cout << add(3, 4) << std::endl; // 7
// 捕获方式
int base = 100;
auto by_value = [base](int x) { return base + x; }; // 值捕获(拷贝)
auto by_ref = [&base](int x) { base += x; }; // 引用捕获
auto by_all_val = [=](int x) { return base + x; }; // 所有变量值捕获
auto by_all_ref = [&]() { base++; }; // 所有变量引用捕获
auto mutable_lam = [base](int x) mutable {
base++; // 值捕获默认是 const,加 mutable 才能修改副本
return base + x;
};
// ===== 与 STL 算法配合 =====
// sort
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序
});
// for_each
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
// transform
std::vector<int> doubled(nums.size());
std::transform(nums.begin(), nums.end(), doubled.begin(),
[](int n) { return n * 2; });
// count_if
auto big_count = std::count_if(nums.begin(), nums.end(),
[](int n) { return n > 5; });
std::cout << "大于5的数: " << big_count << " 个" << std::endl;
// accumulate(带自定义操作)
int product = std::accumulate(nums.begin(), nums.end(), 1,
[](int a, int b) { return a * b; });
// any_of / all_of / none_of
bool has_even = std::any_of(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
bool all_positive = std::all_of(nums.begin(), nums.end(),
[](int n) { return n > 0; });
// ===== std::function(通用函数包装器)=====
std::function<int(int, int)> op;
op = [](int a, int b) { return a + b; };
std::cout << "加法: " << op(3, 4) << std::endl;
op = [](int a, int b) { return a * b; };
std::cout << "乘法: " << op(3, 4) << std::endl;
return 0;
}
移动语义与完美转发
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include <cstring>
// 移动语义的核心:避免不必要的拷贝
class Buffer {
private:
char* data_;
size_t size_;
public:
// 构造函数
explicit Buffer(size_t size) : data_(new char[size]), size_(size) {
std::memset(data_, 0, size);
std::cout << "构造: 分配 " << size << " 字节" << std::endl;
}
// 拷贝构造(深拷贝:昂贵)
Buffer(const Buffer& other) : data_(new char[other.size_]), size_(other.size_) {
std::memcpy(data_, other.data_, size_);
std::cout << "拷贝构造: 深拷贝 " << size_ << " 字节" << std::endl;
}
// 移动构造(窃取资源:廉价)
Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 将源对象置空,防止 double free
other.size_ = 0;
std::cout << "移动构造: 窃取资源" << std::endl;
}
// 拷贝赋值
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
std::cout << "拷贝赋值" << std::endl;
}
return *this;
}
// 移动赋值
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
std::cout << "移动赋值" << std::endl;
}
return *this;
}
~Buffer() {
delete[] data_;
}
size_t size() const { return size_; }
};
// std::move 将左值转为右值引用,启用移动语义
Buffer create_buffer() {
Buffer buf(1024);
return buf; // 编译器可能应用 NRVO(命名返回值优化),直接在调用者内存构造
}
// 完美转发:保持参数的左/右值属性
template<typename T>
void wrapper(T&& arg) {
// std::forward 保持 arg 的原始值类别
// 如果传入左值,forward 后仍为左值;如果传入右值,forward 后仍为右值
actual_function(std::forward<T>(arg));
}
int main() {
// 移动 vs 拷贝
Buffer a(1024);
Buffer b = a; // 拷贝构造
Buffer c = std::move(a); // 移动构造(a 不再拥有数据)
// a.size() 此时为 0
// vector 的移动优化
std::vector<std::string> names;
std::string long_name = "疾旋鼬AAAAAA很长很长的名字";
names.push_back(long_name); // 拷贝(long_name 之后还要用)
names.push_back(std::move(long_name)); // 移动(long_name 被掏空)
// long_name 此时为空
// emplace_back: 在容器内就地构造,避免临时对象
std::vector<Buffer> buffers;
buffers.push_back(Buffer(256)); // 构造临时对象 → 移动到 vector
buffers.emplace_back(256); // 直接在 vector 内存中构造(更高效)
return 0;
}
右值引用与引用折叠
#include <iostream>
#include <type_traits>
// C++ 中的引用类型:
// T& 左值引用(绑定到左值,即有名字的对象)
// T&& 右值引用(绑定到右值,即临时对象或将要销毁的对象)
void process(int& x) {
std::cout << "左值引用: " << x << std::endl;
}
void process(int&& x) {
std::cout << "右值引用: " << x << std::endl;
}
// 引用折叠规则(模板中 T&& 的推导):
// T& & → T&
// T& && → T&
// T&& & → T&
// T&& && → T&&
// 即:只要出现左值引用,结果就是左值引用
// 万能引用(Universal Reference / Forwarding Reference)
template<typename T>
void forward_example(T&& arg) {
// T&& 在模板中是万能引用,可以绑定到左值或右值
process(std::forward<T>(arg)); // 保持原始值类别
}
int main() {
int x = 42;
process(x); // 调用左值引用版本
process(42); // 调用右值引用版本
process(std::move(x)); // 调用右值引用版本
forward_example(x); // T = int&, arg = int&
forward_example(42); // T = int, arg = int&&
return 0;
}
并发编程基础
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>
#include <vector>
#include <queue>
#include <functional>
#include <chrono>
// ===== 基本线程 =====
void pal_worker(int id) {
std::cout << "帕鲁 " << id << " 开始工作……" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "帕鲁 " << id << " 完成工作!" << std::endl;
}
// ===== 互斥锁 =====
std::mutex g_print_mutex;
void safe_print(const std::string& msg) {
std::lock_guard<std::mutex> lock(g_print_mutex); // RAII 风格加锁
std::cout << msg << std::endl;
} // 离开作用域自动解锁
// ===== 数据竞争示例 =====
int g_shared_counter = 0;
std::mutex g_counter_mutex;
void increment_unsafe(int times) {
for (int i = 0; i < times; i++) {
g_shared_counter++; // 数据竞争!多线程同时读写
}
}
void increment_safe(int times) {
for (int i = 0; i < times; i++) {
std::lock_guard<std::mutex> lock(g_counter_mutex);
g_shared_counter++;
}
}
// ===== 原子变量(无锁线程安全)=====
std::atomic<int> g_atomic_counter{0};
void increment_atomic(int times) {
for (int i = 0; i < times; i++) {
g_atomic_counter++; // 原子操作,无需锁
}
}
// ===== 条件变量 =====
std::queue<int> g_task_queue;
std::mutex g_queue_mutex;
std::condition_variable g_cv;
bool g_done = false;
void producer() {
for (int i = 0; i < 10; i++) {
{
std::lock_guard<std::mutex> lock(g_queue_mutex);
g_task_queue.push(i);
std::cout << "生产任务: " << i << std::endl;
}
g_cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
{
std::lock_guard<std::mutex> lock(g_queue_mutex);
g_done = true;
}
g_cv.notify_all();
}
void consumer(const std::string& name) {
while (true) {
int task;
{
std::unique_lock<std::mutex> lock(g_queue_mutex);
// wait: 释放锁并等待,被唤醒后重新获取锁
g_cv.wait(lock, [] {
return !g_task_queue.empty() || g_done;
});
if (g_task_queue.empty() && g_done) break;
task = g_task_queue.front();
g_task_queue.pop();
}
std::cout << name << " 处理任务: " << task << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
// ===== async / future =====
int compute_damage(int base, int level) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return base + level * 2;
}
// ===== 简易线程池 =====
class ThreadPool {
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex mutex_;
std::condition_variable cv_;
bool stop_ = false;
public:
explicit ThreadPool(size_t num_threads) {
for (size_t i = 0; i < num_threads; i++) {
workers_.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] {
return stop_ || !tasks_.empty();
});
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::lock_guard<std::mutex> lock(mutex_);
stop_ = true;
}
cv_.notify_all();
for (auto& w : workers_) {
if (w.joinable()) w.join();
}
}
template<typename F>
void enqueue(F&& f) {
{
std::lock_guard<std::mutex> lock(mutex_);
tasks_.emplace(std::forward<F>(f));
}
cv_.notify_one();
}
};
int main() {
// 基本线程
std::thread t1(pal_worker, 1);
std::thread t2(pal_worker, 2);
t1.join(); // 等待线程结束
t2.join();
// 原子计数器
std::thread t3(increment_atomic, 10000);
std::thread t4(increment_atomic, 10000);
t3.join();
t4.join();
std::cout << "原子计数器: " << g_atomic_counter << std::endl; // 正确: 20000
// 生产者-消费者
std::thread prod(producer);
std::thread cons1(consumer, "疾旋鼬");
std::thread cons2(consumer, "桃旋鼬");
prod.join();
cons1.join();
cons2.join();
// async / future
auto future1 = std::async(std::launch::async, compute_damage, 85, 50);
auto future2 = std::async(std::launch::async, compute_damage, 60, 30);
// 主线程可以做其他事情……
int dmg1 = future1.get(); // 阻塞等待结果
int dmg2 = future2.get();
std::cout << "疾旋鼬伤害: " << dmg1 << ", 捣蛋猫伤害: " << dmg2 << std::endl;
// 线程池
{
ThreadPool pool(4);
for (int i = 0; i < 8; i++) {
pool.enqueue([i] {
safe_print("任务 " + std::to_string(i) + " 执行中……");
});
}
} // pool 析构时等待所有任务完成
return 0;
}
C++17/20 新特性
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string_view>
#include <filesystem>
#include <tuple>
#include <format> // C++20
// ===== std::optional(C++17):可能有值,可能没有 =====
std::optional<int> find_pal_hp(const std::string& name) {
if (name == "疾旋鼬") return 120;
if (name == "捣蛋猫") return 80;
return std::nullopt; // 未找到
}
// ===== std::variant(C++17):类型安全的 union =====
using PalValue = std::variant<int, double, std::string>;
void print_value(const PalValue& v) {
std::visit([](const auto& val) {
std::cout << val << std::endl;
}, v);
}
// ===== std::string_view(C++17):非拥有的字符串引用 =====
// 避免不必要的字符串拷贝
void greet(std::string_view name) {
std::cout << "你好," << name << "!" << std::endl;
}
// ===== 结构化绑定(C++17)=====
std::tuple<std::string, int, int> get_pal_info() {
return {"疾旋鼬", 120, 85};
}
// ===== constexpr if(C++17)=====
template<typename T>
auto get_value(T&& t) {
if constexpr (std::is_pointer_v<std::decay_t<T>>) {
return *t; // 解引用
} else {
return t; // 直接返回
}
}
// ===== Concepts(C++20)=====
#include <concepts>
// 定义概念:可以相加的类型
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
// 使用概念约束模板参数
template<Addable T>
T safe_add(T a, T b) {
return a + b;
}
// Ranges(C++20)
#include <ranges>
#include <vector>
int main() {
// optional
auto hp = find_pal_hp("疾旋鼬");
if (hp.has_value()) {
std::cout << "疾旋鼬 HP: " << hp.value() << std::endl;
}
// 或使用 value_or 提供默认值
int default_hp = find_pal_hp("未知帕鲁").value_or(50);
// variant
PalValue v1 = 42;
PalValue v2 = 3.14;
PalValue v3 = std::string("疾旋鼬");
print_value(v1); // 42
print_value(v3); // 疾旋鼬
// string_view
std::string long_name = "疾旋鼬AAAAAA";
greet(long_name); // 从 std::string 构造
greet("桃旋鼬"); // 从字面量构造(零拷贝)
// 结构化绑定
auto [name, hp_stat, atk] = get_pal_info();
std::cout << name << " HP=" << hp_stat << " ATK=" << atk << std::endl;
// Ranges (C++20)
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 管道操作:筛选偶数 → 平方 → 取前3个
auto result = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; })
| std::views::take(3);
for (int n : result) {
std::cout << n << " "; // 4 16 36
}
std::cout << std::endl;
// filesystem (C++17)
namespace fs = std::filesystem;
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
}
return 0;
}
第四部分:操作系统编程
进程与系统调用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
int main(void) {
// ===== 进程信息 =====
printf("PID: %d\n", getpid()); // 当前进程 ID
printf("PPID: %d\n", getppid()); // 父进程 ID
printf("UID: %d\n", getuid()); // 用户 ID
// ===== fork:创建子进程 =====
pid_t pid = fork();
if (pid < 0) {
// fork 失败
perror("fork 失败");
return 1;
} else if (pid == 0) {
// 子进程
printf("[子进程 %d] 疾旋鼬的分身!父进程是 %d\n", getpid(), getppid());
// exec:替换当前进程的映像
// 常见用法:子进程 fork 后 exec 执行新程序
char *args[] = {"ls", "-la", NULL};
// execvp("ls", args); // 搜索 PATH 执行 ls
// execl 也可以
// execl("/bin/ls", "ls", "-la", NULL);
exit(0); // 子进程退出
} else {
// 父进程
printf("[父进程 %d] 创建了子进程 %d\n", getpid(), pid);
// waitpid:等待子进程结束
int status;
pid_t child_pid = waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程正常退出,返回值: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号 %d 终止\n", WTERMSIG(status));
}
}
// ===== 管道(Pipe) =====
int pipefd[2]; // [0]=读端, [1]=写端
if (pipe(pipefd) < 0) {
perror("pipe 失败");
return 1;
}
pid = fork();
if (pid == 0) {
// 子进程:写入管道
close(pipefd[0]); // 关闭读端
const char *msg = "疾旋鼬发来消息!";
write(pipefd[1], msg, strlen(msg) + 1);
close(pipefd[1]);
exit(0);
} else {
// 父进程:从管道读取
close(pipefd[1]); // 关闭写端
char buf[256];
ssize_t n = read(pipefd[0], buf, sizeof(buf));
buf[n] = '\0';
printf("父进程收到: %s\n", buf);
close(pipefd[0]);
waitpid(pid, NULL, 0);
}
// ===== 环境变量 =====
const char *path = getenv("PATH");
printf("PATH = %s\n", path ? path : "(null)");
setenv("PAL_NAME", "疾旋鼬", 1);
printf("PAL_NAME = %s\n", getenv("PAL_NAME"));
return 0;
}
信号处理
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
volatile sig_atomic_t g_interrupted = 0;
void signal_handler(int signum) {
// 信号处理函数中只能使用 async-signal-safe 的函数
// write() 是安全的,printf() 不是(但实践中常用)
const char *msg;
switch (signum) {
case SIGINT:
msg = "\n疾旋鼬被 Ctrl+C 打断了!再按一次退出。\n";
g_interrupted = 1;
break;
case SIGSEGV:
msg = "\n疾旋鼬检测到段错误!可能是内存越界。\n";
_exit(1); // 不要用 exit()(不安全)
break;
case SIGTERM:
msg = "\n疾旋鼬收到终止信号,优雅退出……\n";
_exit(0);
break;
default:
msg = "\n收到未知信号\n";
break;
}
write(STDERR_FILENO, msg, strlen(msg));
}
int main(void) {
// 注册信号处理函数
signal(SIGINT, signal_handler); // Ctrl+C
signal(SIGSEGV, signal_handler); // 段错误
signal(SIGTERM, signal_handler); // kill 命令
// 忽略信号
signal(SIGPIPE, SIG_IGN); // 忽略管道断裂
printf("疾旋鼬正在运行……按 Ctrl+C 试试!\n");
while (!g_interrupted) {
sleep(1);
printf("疾旋鼬在旋转中……\n");
}
printf("疾旋鼬安全退出!\n");
return 0;
}
内存映射(mmap)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
int main(void) {
const char *filename = "pal_mmap.dat";
const char *data = "疾旋鼬通过 mmap 写入的数据!AAAABBBBCCCCDDDD";
size_t data_len = strlen(data) + 1;
// ===== 创建并写入文件 =====
int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
ftruncate(fd, data_len); // 设置文件大小
// 内存映射文件
void *mapped = mmap(NULL, data_len, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap 失败");
close(fd);
return 1;
}
// 通过内存操作写入文件(像操作数组一样)
memcpy(mapped, data, data_len);
printf("已写入: %s\n", (char *)mapped);
// 修改映射内存 → 直接修改文件
char *p = (char *)mapped;
p[0] = 'X'; // 修改第一个字节
printf("修改后: %s\n", (char *)mapped);
// 同步到磁盘
msync(mapped, data_len, MS_SYNC);
// 解除映射
munmap(mapped, data_len);
close(fd);
// ===== 只读映射(用于读取大文件)=====
fd = open(filename, O_RDONLY);
struct stat st;
fstat(fd, &st);
mapped = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped != MAP_FAILED) {
printf("读取: %s\n", (char *)mapped);
// 可以直接通过指针读取,无需 read() 系统调用
munmap(mapped, st.st_size);
}
close(fd);
unlink(filename); // 清理
return 0;
}
mmap 与安全
mmap 在安全领域有重要应用:
- Shellcode 执行:mmap 分配可读写可执行(RWX)内存,用于注入 shellcode
- 文件分析:大文件通过 mmap 映射后直接指针访问,避免内存拷贝
- 共享内存:进程间通信的基础机制
- 漏洞利用:ROP/ret2libc 等技术常涉及内存页权限操作(mprotect)
虚拟内存与页表操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
#include <signal.h>
// ===== 自定义信号处理器捕获 SIGSEGV =====
// 这可以实现"按需分配"的虚拟内存
void *fault_handler_addr = NULL;
void segv_handler(int sig, siginfo_t *info, void *context) {
(void)sig;
void *fault_addr = info->si_addr;
if (fault_addr == fault_handler_addr) {
// 按需分配物理页面
void *page = mmap(fault_addr, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1, 0);
if (page == MAP_FAILED) {
_exit(1);
}
// 初始化内容
memset(page, 0, 4096);
strcpy((char *)page, "疾旋鼬按需分配的页面!");
} else {
// 真正的段错误
write(STDERR_FILENO, "真正的段错误!\n", 15);
_exit(1);
}
}
int main(void) {
// ===== 查看虚拟地址空间 =====
printf("页面大小: %ld\n", sysconf(_SC_PAGESIZE)); // 通常 4096
// ===== 自定义页错误处理 =====
struct sigaction sa;
sa.sa_sigaction = segv_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
// 先不分配物理内存,只保留虚拟地址
fault_handler_addr = mmap(NULL, 4096,
PROT_NONE, // 无权限 → 访问必触发 SIGSEGV
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
printf("虚拟地址已保留: %p(尚无物理内存)\n", fault_handler_addr);
// 第一次访问 → 触发 SIGSEGV → 信号处理器分配物理页面
printf("内容: %s\n", (char *)fault_handler_addr);
// 后续访问正常(物理页面已分配)
printf("再次访问: %s\n", (char *)fault_handler_addr);
// ===== mprotect:修改页面权限 =====
void *code_page = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
// 写入机器码
// mov eax, 42; ret
unsigned char shellcode[] = {0xb8, 0x2a, 0x00, 0x00, 0x00, 0xc3};
memcpy(code_page, shellcode, sizeof(shellcode));
// 将页面设为可读可执行(去掉可写权限)
mprotect(code_page, 4096, PROT_READ | PROT_EXEC);
// 将内存中的机器码转为函数指针并调用
typedef int (*func_t)(void);
func_t func = (func_t)code_page;
int result = func();
printf("执行结果: %d\n", result); // 42
munmap(fault_handler_addr, 4096);
munmap(code_page, 4096);
return 0;
}
第五部分:安全应用实践
栈溢出原理演示
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
栈帧布局(x86-64,从高地址到低地址):
高地址
┌───────────────────┐
│ 返回地址 (8B) │ ← 覆盖这里可以劫持控制流
├───────────────────┤
│ 保存的 RBP (8B) │ ← 帧指针
├───────────────────┤
│ 局部变量 │ ← buffer[16]
│ buffer[0..15] │
├───────────────────┤
低地址
如果写入超过 buffer 的大小,会覆盖 RBP 和返回地址。
*/
// 疾旋鼬的安全检查程序
void secret_function(void) {
printf("🎉 疾旋鼬发现了隐藏关卡!\n");
printf("这就是栈溢出劫持控制流的原理!\n");
}
void vulnerable_function(const char *input) {
char buffer[16]; // 只有 16 字节的缓冲区
printf("buffer 地址: %p\n", (void *)buffer);
printf("返回地址应该在: %p 附近\n", __builtin_return_address(0));
// 危险!没有长度检查!
// 如果 input 超过 16 字节,会溢出 buffer
strcpy(buffer, input);
printf("你输入了: %s\n", buffer);
}
void safe_function(const char *input) {
char buffer[16];
// 安全版本:使用 strncpy 并确保 null 结尾
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
printf("安全输入: %s\n", buffer);
}
int main(int argc, char *argv[]) {
printf("=== 疾旋鼬的栈溢出演示 ===\n\n");
// 正常输入
printf("--- 正常输入 ---\n");
vulnerable_function("疾旋鼬");
printf("\n");
// 超长输入(会溢出,但在现代系统中会触发栈保护)
printf("--- 超长输入(演示溢出概念) ---\n");
// 为了安全,这里只是概念演示,不会真正执行溢出攻击
// 在没有栈保护(-fno-stack-protector)的程序中:
// vulnerable_function("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
// 安全输入
printf("--- 安全输入 ---\n");
safe_function("疾旋鼬AAAABBBBCCCCDDDD");
printf("\n");
// ===== 整数溢出演示 =====
printf("=== 整数溢出演示 ===\n");
int size = 2147483647; // INT_MAX
int new_size = size + 1; // 溢出!变为 INT_MIN(负数)
printf("原大小: %d\n", size);
printf("加1后: %d\n", new_size); // -2147483648
if (new_size > 0) {
// 如果没有检查溢出,会分配一个极小的缓冲区
printf("分配 %d 字节(太小了!)\n", new_size);
} else {
printf("检测到整数溢出!拒绝分配。\n");
}
// ===== 格式化字符串漏洞概念 =====
printf("\n=== 格式化字符串概念 ===\n");
char *user_input = "疾旋鼬";
// 正常用法
printf("正常: %s\n", user_input);
// 如果攻击者控制了格式化字符串:
// printf(user_input); // 危险!
// printf("%x %x %x %x"); // 可以读取栈上的数据
// printf("%n"); // 可以写入内存!
// 安全做法:始终使用格式化字符串
printf("安全: %s\n", user_input); // 格式化字符串是常量
return 0;
}
堆利用技术概念
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
堆利用技术概述(概念性,非实战代码):
1. Use After Free (UAF)
- free(ptr) 后继续使用 ptr
- 攻击者可抢先分配同一块内存,控制其内容
2. Double Free
- 对同一指针调用 free() 两次
- 可以破坏堆元数据,实现任意地址写
3. Heap Overflow
- 写入超过分配大小
- 可以覆盖相邻的堆块元数据或数据
4. House of Spirit
- 伪造堆块,让 free() 释放攻击者控制的内存
5. Unlink Exploit
- 利用双向链表的 unlink 操作实现任意地址写
*/
// ===== Use After Free 演示 =====
typedef struct {
char name[32];
int is_admin;
void (*action)(void);
} User;
void normal_action(void) {
printf("普通用户操作\n");
}
void admin_action(void) {
printf("🔥 管理员操作:获得 root 权限!\n");
}
void uaf_demo(void) {
printf("=== UAF 概念演示 ===\n");
// 步骤 1: 分配一个 User
User *user1 = (User *)malloc(sizeof(User));
strcpy(user1->name, "疾旋鼬");
user1->is_admin = 0;
user1->action = normal_action;
printf("user1: %s, admin=%d\n", user1->name, user1->is_admin);
user1->action();
// 步骤 2: 释放 user1
free(user1);
printf("user1 已被释放\n");
// 步骤 3: 攻击者分配一块相同大小的内存
// (在真实攻击中,攻击者会精心控制这次分配)
User *attacker = (User *)malloc(sizeof(User));
strcpy(attacker->name, "攻击者帕鲁");
attacker->is_admin = 1;
attacker->action = admin_action;
// 步骤 4: 如果程序继续使用已释放的 user1(UAF)
// 在某些分配器实现中,user1 和 attacker 可能指向同一内存
if ((void *)user1 == (void *)attacker) {
printf("user1 和 attacker 指向同一内存!\n");
user1->action(); // 触发了攻击者注入的函数!
} else {
printf("内存地址不同(现代分配器有防护)\n");
printf("user1: %p, attacker: %p\n", (void *)user1, (void *)attacker);
}
free(attacker);
}
// ===== 堆布局概念 =====
void heap_layout_demo(void) {
printf("\n=== 堆布局概念 ===\n");
// 连续分配会产生相邻的堆块
void *a = malloc(32);
void *b = malloc(32);
void *c = malloc(32);
printf("块 A: %p\n", a);
printf("块 B: %p\n", b);
printf("块 C: %p\n", c);
printf("A 和 B 的距离: %ld 字节\n", (char *)b - (char *)a);
printf("B 和 C 的距离: %ld 字节\n", (char *)c - (char *)b);
// 如果溢出块 A,可以覆盖块 B 的元数据或内容
// 这就是堆溢出攻击的基础
free(a);
free(b);
free(c);
}
int main(void) {
uaf_demo();
heap_layout_demo();
return 0;
}
加密算法实现
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
// ===== XOR 加密(最简单的加密)=====
void xor_encrypt(uint8_t *data, size_t len, const uint8_t *key, size_t key_len) {
for (size_t i = 0; i < len; i++) {
data[i] ^= key[i % key_len];
}
}
// ===== Base64 编码 =====
static const char base64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char *base64_encode(const uint8_t *data, size_t len) {
size_t out_len = 4 * ((len + 2) / 3);
char *out = (char *)malloc(out_len + 1);
if (!out) return NULL;
size_t i = 0, j = 0;
while (i < len) {
uint32_t octet_a = i < len ? data[i++] : 0;
uint32_t octet_b = i < len ? data[i++] : 0;
uint32_t octet_c = i < len ? data[i++] : 0;
uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c;
out[j++] = base64_table[(triple >> 18) & 0x3F];
out[j++] = base64_table[(triple >> 12) & 0x3F];
out[j++] = (i > len + 1) ? '=' : base64_table[(triple >> 6) & 0x3F];
out[j++] = (i > len) ? '=' : base64_table[triple & 0x3F];
}
out[j] = '\0';
return out;
}
// ===== SHA-256 概念结构 =====
// 完整实现太长,这里展示结构和核心操作
typedef struct {
uint32_t state[8]; // 哈希状态
uint64_t count; // 已处理的位数
uint8_t buffer[64]; // 缓冲区
} sha256_ctx;
// 循环右移
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
// SHA-256 基本函数
#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
#define EP1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
// ===== 简单 CRC32 =====
uint32_t crc32(const uint8_t *data, size_t len) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
}
}
return ~crc;
}
int main(void) {
// XOR 加密
const char *plaintext = "疾旋鼬的机密数据";
size_t len = strlen(plaintext);
uint8_t *data = (uint8_t *)malloc(len);
memcpy(data, plaintext, len);
const uint8_t key[] = {0x42, 0x37, 0xA5, 0x9F};
size_t key_len = sizeof(key);
printf("原文: %s\n", (char *)data);
xor_encrypt(data, len, key, key_len);
printf("加密: ");
for (size_t i = 0; i < len; i++) printf("%02X ", data[i]);
printf("\n");
xor_encrypt(data, len, key, key_len); // 再次 XOR 解密
printf("解密: %s\n", (char *)data);
// Base64
char *encoded = base64_encode((uint8_t *)"疾旋鼬", 9);
printf("Base64: %s\n", encoded);
free(encoded);
// CRC32
uint32_t hash = crc32((uint8_t *)"疾旋鼬", 9);
printf("CRC32: 0x%08X\n", hash);
free(data);
return 0;
}
网络编程(Socket)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
// ===== TCP 服务器 =====
int start_server(int port) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket 失败");
return -1;
}
// 允许端口复用
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = INADDR_ANY,
};
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind 失败");
close(server_fd);
return -1;
}
if (listen(server_fd, 5) < 0) {
perror("listen 失败");
close(server_fd);
return -1;
}
printf("疾旋鼬服务器监听端口 %d ……\n", port);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept 失败");
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("连接来自 %s:%d\n", client_ip, ntohs(client_addr.sin_port));
// 接收数据
char buf[1024];
ssize_t n = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (n > 0) {
buf[n] = '\0';
printf("收到: %s\n", buf);
// 发送响应
const char *response = "疾旋鼬已收到消息!";
send(client_fd, response, strlen(response), 0);
}
close(client_fd);
}
close(server_fd);
return 0;
}
// ===== TCP 客户端 =====
int connect_to_server(const char *host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket 失败");
return -1;
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
};
inet_pton(AF_INET, host, &addr.sin_addr);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect 失败");
close(sockfd);
return -1;
}
// 发送数据
const char *msg = "你好,疾旋鼬服务器!";
send(sockfd, msg, strlen(msg), 0);
// 接收响应
char buf[1024];
ssize_t n = recv(sockfd, buf, sizeof(buf) - 1, 0);
if (n > 0) {
buf[n] = '\0';
printf("服务器响应: %s\n", buf);
}
close(sockfd);
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法:\n");
printf(" %s server <port>\n", argv[0]);
printf(" %s client <host> <port>\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "server") == 0) {
int port = argc > 2 ? atoi(argv[2]) : 8080;
start_server(port);
} else if (strcmp(argv[1], "client") == 0) {
const char *host = argc > 2 ? argv[2] : "127.0.0.1";
int port = argc > 3 ? atoi(argv[3]) : 8080;
connect_to_server(host, port);
}
return 0;
}
ELF 文件解析
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
// ELF 魔数
#define ELFMAG "\x7fELF"
#define ELFMAG_LEN 4
// ELF 类型
#define ET_NONE 0
#define ET_REL 1 // 可重定位文件 (.o)
#define ET_EXEC 2 // 可执行文件
#define ET_DYN 3 // 共享目标文件 (.so)
// 机器类型
#define EM_386 3 // x86
#define EM_X86_64 62 // x86-64
#define EM_ARM 40 // ARM
#define EM_AARCH64 183 // ARM64
// 简化的 ELF64 文件头
typedef struct {
uint8_t e_ident[16]; // ELF 标识
uint16_t e_type; // 文件类型
uint16_t e_machine; // 目标架构
uint32_t e_version; // 版本
uint64_t e_entry; // 入口点地址
uint64_t e_phoff; // 程序头表偏移
uint64_t e_shoff; // 节头表偏移
uint32_t e_flags; // 标志
uint16_t e_ehsize; // 文件头大小
uint16_t e_phentsize; // 程序头条目大小
uint16_t e_phnum; // 程序头条目数
uint16_t e_shentsize; // 节头条目大小
uint16_t e_shnum; // 节头条目数
uint16_t e_shstrndx; // 节名字符串表索引
} Elf64_Ehdr;
// 简化的 ELF64 节头
typedef struct {
uint32_t sh_name; // 节名(字符串表索引)
uint32_t sh_type; // 节类型
uint64_t sh_flags; // 节标志
uint64_t sh_addr; // 虚拟地址
uint64_t sh_offset; // 文件偏移
uint64_t sh_size; // 节大小
uint32_t sh_link; // 链接
uint32_t sh_info; // 信息
uint64_t sh_addralign; // 对齐
uint64_t sh_entsize; // 条目大小
} Elf64_Shdr;
// 节类型名称
const char *section_type_name(uint32_t type) {
switch (type) {
case 0: return "NULL";
case 1: return "PROGBITS";
case 2: return "SYMTAB";
case 3: return "STRTAB";
case 4: return "RELA";
case 5: return "HASH";
case 6: return "DYNAMIC";
case 7: return "NOTE";
case 8: return "NOBITS";
case 9: return "REL";
case 11: return "DYNSYM";
default: return "UNKNOWN";
}
}
// 架构名称
const char *machine_name(uint16_t machine) {
switch (machine) {
case EM_386: return "x86 (IA-32)";
case EM_X86_64: return "x86-64";
case EM_ARM: return "ARM";
case EM_AARCH64: return "AArch64 (ARM64)";
default: return "Unknown";
}
}
// 文件类型名称
const char *elf_type_name(uint16_t type) {
switch (type) {
case ET_NONE: return "NONE (无类型)";
case ET_REL: return "REL (可重定位)";
case ET_EXEC: return "EXEC (可执行)";
case ET_DYN: return "DYN (共享库)";
default: return "Unknown";
}
}
int analyze_elf(const char *filepath) {
int fd = open(filepath, O_RDONLY);
if (fd < 0) {
perror("打开文件失败");
return 1;
}
struct stat st;
fstat(fd, &st);
size_t filesize = st.st_size;
void *mapped = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap 失败");
close(fd);
return 1;
}
const uint8_t *data = (const uint8_t *)mapped;
// 检查 ELF 魔数
if (memcmp(data, ELFMAG, ELFMAG_LEN) != 0) {
printf("不是有效的 ELF 文件!\n");
munmap(mapped, filesize);
close(fd);
return 1;
}
// 解析 ELF 类
uint8_t elf_class = data[4]; // 1=32位, 2=64位
uint8_t endian = data[5]; // 1=小端, 2=大端
printf("=== 疾旋鼬的 ELF 分析器 ===\n\n");
printf("文件: %s\n", filepath);
printf("大小: %zu 字节\n", filesize);
printf("ELF 类: %s\n", elf_class == 2 ? "ELF64" : "ELF32");
printf("字节序: %s\n", endian == 1 ? "小端" : "大端");
if (elf_class != 2) {
printf("仅支持 ELF64 分析\n");
munmap(mapped, filesize);
close(fd);
return 1;
}
// 解析文件头
const Elf64_Ehdr *ehdr = (const Elf64_Ehdr *)data;
printf("\n=== 文件头 ===\n");
printf("类型: %s\n", elf_type_name(ehdr->e_type));
printf("架构: %s\n", machine_name(ehdr->e_machine));
printf("入口点: 0x%016lx\n", ehdr->e_entry);
printf("程序头: 偏移 0x%lx, %d 个条目\n", ehdr->e_phoff, ehdr->e_phnum);
printf("节头: 偏移 0x%lx, %d 个条目\n", ehdr->e_shoff, ehdr->e_shnum);
// 解析节头表
if (ehdr->e_shoff > 0 && ehdr->e_shnum > 0) {
printf("\n=== 节头表 ===\n");
const Elf64_Shdr *shdr_table = (const Elf64_Shdr *)(data + ehdr->e_shoff);
// 获取节名字符串表
const Elf64_Shdr *shstrtab = &shdr_table[ehdr->e_shstrndx];
const char *shstrtab_data = (const char *)(data + shstrtab->sh_offset);
printf("%-20s %-12s %-18s %-18s %s\n",
"名称", "类型", "地址", "大小", "标志");
printf("%-20s %-12s %-18s %-18s %s\n",
"----", "----", "----", "----", "----");
for (int i = 0; i < ehdr->e_shnum; i++) {
const Elf64_Shdr *sh = &shdr_table[i];
const char *name = shstrtab_data + sh->sh_name;
// 构建标志字符串
char flags[16] = "";
int fi = 0;
if (sh->sh_flags & 0x1) flags[fi++] = 'W'; // 可写
if (sh->sh_flags & 0x2) flags[fi++] = 'A'; // 分配
if (sh->sh_flags & 0x4) flags[fi++] = 'X'; // 可执行
flags[fi] = '\0';
printf("%-20s %-12s 0x%016lx 0x%016lx %s\n",
name, section_type_name(sh->sh_type),
sh->sh_addr, sh->sh_size, flags);
}
// 查找并打印 .text 段大小(代码段)
for (int i = 0; i < ehdr->e_shnum; i++) {
const char *name = shstrtab_data + shdr_table[i].sh_name;
if (strcmp(name, ".text") == 0) {
printf("\n.text 段: 0x%lx 字节\n", shdr_table[i].sh_size);
break;
}
}
}
munmap(mapped, filesize);
close(fd);
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法: %s <elf-file>\n", argv[0]);
printf("示例: %s /bin/ls\n", argv[0]);
return 1;
}
return analyze_elf(argv[1]);
}
安全编码实践清单
/*
* 疾旋鼬的安全编码清单(C/C++)
*
* 1. 缓冲区安全
* ✗ strcpy, strcat, sprintf, gets
* ✓ strncpy, strncat, snprintf, fgets
* ✓ 始终检查边界,确保 null 结尾
*
* 2. 整数安全
* ✗ 有符号/无符号混用
* ✓ 使用 <stdint.h> 定长类型
* ✓ 加法/乘法前检查是否溢出
* ✓ size_t 是无符号的:size_t a - size_t b 当 a < b 时会下溢
*
* 3. 指针安全
* ✗ 解引用前不检查 NULL
* ✓ free 后置 NULL
* ✓ 使用智能指针(C++)
* ✓ 避免悬垂指针
*
* 4. 格式化字符串
* ✗ printf(user_input)
* ✓ printf("%s", user_input)
*
* 5. 内存管理
* ✗ malloc 不检查返回值
* ✓ 所有 malloc/calloc/realloc 都检查返回值
* ✓ 避免 double free
* ✓ 避免内存泄漏(使用 RAII / 智能指针)
*
* 6. 编译器保护
* -fstack-protector-all 栈保护(Canary)
* -D_FORTIFY_SOURCE=2 运行时缓冲区检查
* -fPIE -pie 地址无关可执行(ASLR)
* -Wl,-z,relro,-z,now RELRO(保护 GOT)
* -Wl,-z,noexecstack 不可执行栈(NX)
*/
// 安全的内存分配宏
#define SAFE_MALLOC(ptr, size) do { \
(ptr) = malloc(size); \
if (!(ptr)) { \
fprintf(stderr, "内存分配失败: %s:%d\n", \
__FILE__, __LINE__); \
abort(); \
} \
} while (0)
// 安全的字符串拷贝
#define SAFE_STRCPY(dst, src, size) do { \
strncpy(dst, src, (size) - 1); \
(dst)[(size) - 1] = '\0'; \
} while (0)
// 安全的字符串拼接
#define SAFE_STRCAT(dst, src, size) do { \
size_t _len = strlen(dst); \
if (_len < (size) - 1) { \
strncat(dst, src, (size) - _len - 1); \
} \
} while (0)
int main(void) {
// 使用安全宏
char name[32];
SAFE_STRCPY(name, "疾旋鼬AAAABBBBCCCCDDDD", sizeof(name));
printf("名字: %s\n", name);
int *nums;
SAFE_MALLOC(nums, 10 * sizeof(int));
for (int i = 0; i < 10; i++) nums[i] = i;
free(nums);
nums = NULL; // 置 NULL 防止悬垂指针
return 0;
}
附录:常用工具与编译选项
GCC 编译选项速查
# 基本编译
gcc -o output source.c -Wall -Wextra -std=c11
# C++ 编译
g++ -o output source.cpp -Wall -Wextra -std=c++20
# 调试编译(生成调试信息)
gcc -g -O0 -o output source.c
# 安全加固编译
gcc -o secure source.c \
-Wall -Wextra -Werror \
-fstack-protector-all \
-D_FORTIFY_SOURCE=2 \
-fPIE -pie \
-Wl,-z,relro,-z,now \
-Wl,-z,noexecstack \
-O2
# 查看安全特性是否启用
checksec --file=output # 需要 pwntools
# 反汇编
objdump -d output # 反汇编所有代码段
objdump -d -M intel output # Intel 格式
# 查看符号
nm output # 查看符号表
readelf -s output # ELF 符号信息
# 查看段信息
readelf -S output # 节头表
readelf -l output # 程序头表
# strip:去除调试信息和符号
strip output
GDB 调试速查
# 启动 GDB
gdb ./program
gdb -q ./program # 安静模式
# 运行
(gdb) run # 运行程序
(gdb) run arg1 arg2 # 带参数运行
# 断点
(gdb) break main # 在 main 函数设断点
(gdb) break file.c:42 # 在指定行设断点
(gdb) break *0x401234 # 在指定地址设断点
(gdb) delete 1 # 删除断点 1
(gdb) info breakpoints # 列出所有断点
# 执行
(gdb) step # 单步进入函数
(gdb) next # 单步跳过函数
(gdb) continue # 继续执行
(gdb) finish # 执行到当前函数返回
# 查看信息
(gdb) info registers # 查看所有寄存器
(gdb) info registers rax rip # 查看指定寄存器
(gdb) x/10x $rsp # 查看栈上 10 个字(十六进制)
(gdb) x/s 0x401234 # 查看字符串
(gdb) x/20i $rip # 查看接下来 20 条指令
(gdb) info stack # 查看调用栈
(gdb) backtrace # 同上
# 修改
(gdb) set $rax = 42 # 修改寄存器
(gdb) set *(int *)0x601000 = 1 # 修改内存
# 内存布局
(gdb) info proc mappings # 查看内存映射
(gdb) vmmap # 同上(需要 pwndbg/peda)
# pwndbg/peda 插件(推荐安装)
(gdb) pwndbg> heap # 查看堆信息
(gdb) pwndbg> bins # 查看 bins(tcache, fastbin 等)
2026.05.11
推荐阅读
- CSAPP — 深入理解计算机系统(必读经典)
- Hacking: The Art of Exploitation — 漏洞利用的艺术
- Pwn College — 系统安全在线课程
- How2Heap — 堆利用技术学习
- Compiler Explorer (godbolt.org) — 在线查看 C/C++ 编译后的汇编
- C++ Reference — C++ 标准库参考手册