C/C++ 内存布局、程序构建与静态库动态库

从内存区域划分(.text / .rodata / .data / .bss / 栈 / 堆)讲到构建四阶段(预处理 → 编译 → 汇编 → 链接),再到静态库与动态库的创建、链接行为差异,末尾汇总易错点。 一、内存布局在构建阶段就确定 Determined at Build Time:.text / .rodata / .data / .bss 的内存布局、大小在编译链接构建阶段就确定,不是程序跑起来才决定。这几段都属于 static lifetime 静态生命周期,由编译器自动管理分配与释放,程序员不能手动干预申请释放。 按数据类型、生命周期和作用域,C/C++ 程序的内存通常划分为五大区域:代码区、全局/静态存储区、栈区、堆区、常量区。其中代码区对应 .text,常量区对应 .rodata,全局/静态存储区对应 .data 与 .bss,栈区与堆区则在运行时分配。 1. .text 代码段(文本段 / 代码区) 存储内容:编译之后的机器指令(machine code),即写出的函数、if/for 循环、main 函数等翻译成 CPU 能执行的指令。所有的函数定义、控制结构都会被转换成机器指令存放在这里。 大小:Fixed size,构建阶段确定大小。 权限:Read-only 只读,防止运行时篡改程序指令。这部分内存是共享的,在程序执行期间不会改变。 生命周期:静态生命周期,进程整个运行期间都存在,进程退出由操作系统回收。 管理:完全由编译器、链接器自动管理,程序员没有 API 手动分配释放本段内存。 编译时,main、func 等函数的代码被转换成机器指令存放在可执行文件的代码段中(此时还在磁盘上的可执行文件里);程序运行时,操作系统把这些指令加载到内存的代码段,CPU 再从内存的代码段中读取并执行。 2. .rodata 只读数据段(常量区) 存储内容:初始化后的常量,比如字符串字面量 "hello world"、const char *p = "abc" 这类常量字符串,以及全局 const 常量。 权限:只读 Read-only,程序运行不能修改,写操作会触发段错误。这部分内存同样通常在程序执行期间不会改变。 区分:const int a = 10; 声明在全局放 rodata;局部 const 变量本质还是在栈上。 ...

2026 年 10 月 13 日

C++ namespace、const 与 constexpr 知识点总结

命名空间、常量限定符与编译期常量三件套:语法、特点表、易混点(const 修饰指针的读法)、C++11/14/17/20 的演进,以及综合实战示例。 1. namespace 命名空间 namespace(命名空间)用于避免全局命名冲突,把相关的类型、函数、变量组织在一起,形成逻辑上的"作用域边界"。 核心要点:命名空间本质上是给标识符加了一个"姓氏",从而把不同的名字区分开。 1.1 基本声明与使用 #include <iostream> namespace Math { const double PI = 3.141592653589793; int add(int a, int b) { return a + b; } } int main() { // 使用 "::" 作用域限定符访问命名空间中的成员 std::cout << Math::PI << std::endl; // 3.14159... std::cout << Math::add(3, 4) << std::endl; // 7 return 0; } 1.2 命名空间的特点 特点 说明 可嵌套 命名空间内部可以再定义命名空间 可累加 同一作用域下多次声明同一命名空间会合并 可匿名 匿名命名空间相当于"文件内 static",仅本文件可见 可起别名 namespace M = Math; 简化访问 1.3 嵌套命名空间 namespace Outer { namespace Inner { void foo() { /* ... */ } } } // C++17 起可以简写为: namespace Outer::Inner { void bar() { /* ... */ } } // 访问方式 Outer::Inner::foo(); Outer::Inner::bar(); 1.4 匿名命名空间 匿名命名空间中的成员只在当前翻译单元(.cpp 文件)内可见,等价于 static 全局变量/函数,用于隔离实现细节。 ...

2026 年 10 月 13 日

C++ 并发编程:互斥锁、条件变量与原子操作

线程创建与 join/detach 之外的另一半问题:共享数据怎么同步。覆盖数据竞争、互斥锁的三种用法、条件变量生产者-消费者、原子操作与死锁。 核心概念速查 概念 说明 关键点 互斥锁 同一时刻只允许一个线程进入 std::mutex lock_guard RAII 自动加解锁 推荐,防遗漏 unique_lock 更灵活的 RAII 锁 可手动解锁、可条件变量 条件变量 线程间的等待/通知 std::condition_variable 原子操作 无锁的线程安全 std::atomic 数据竞争 多线程同时访问共享数据 未定义行为,必须防 为什么需要锁 // 无锁: 两个线程同时对 counter++ 可能丢失更新 // counter++ 实际是"读-改-写"三步,可能交错 int counter = 0; void add() { for (int i = 0; i < 100000; ++i) ++counter; // 数据竞争! } 互斥锁三种用法 #include <mutex> std::mutex g_mutex; // 方式1: 原始 lock/unlock(容易忘解锁,不推荐) void f1() { g_mutex.lock(); // ... 临界区 g_mutex.unlock(); // 忘写或中间 return 就死锁 } // 方式2: lock_guard (推荐,作用域结束自动解锁) void f2() { std::lock_guard<std::mutex> guard(g_mutex); // ... 临界区 } // 这里自动 unlock // 方式3: unique_lock (需要手动解锁/延迟加锁时) void f3() { std::unique_lock<std::mutex> lock(g_mutex); // ... 临界区 lock.unlock(); // ... 不需要锁的部分 } 条件变量:生产者-消费者 #include <mutex> #include <condition_variable> #include <queue> std::mutex mtx; std::condition_variable cv; std::queue<int> q; // 生产者 void producer() { for (int i = 0; i < 10; ++i) { std::lock_guard<std::mutex> lock(mtx); q.push(i); cv.notify_one(); // 通知一个等待的消费者 } } // 消费者 void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); // wait 会: 释放锁等待 → 被唤醒 → 重新加锁 cv.wait(lock, []{ return !q.empty(); }); int val = q.front(); q.pop(); lock.unlock(); // 处理 val... } } 原子操作 #include <atomic> std::atomic<int> counter{0}; // counter++ 是原子操作,无需加锁 // 适用场景: 计数器、标志位、自旋 // 对比: // 普通 int ++ → 需 mutex // atomic ++ → 无锁,更快 死锁 死锁四个必要条件: 1. 互斥 资源只能一个线程占用 2. 持有并等待 拿着一个锁还想要另一个 3. 不可剥夺 别人不能抢走已持有的锁 4. 循环等待 两个线程彼此持有对方要的锁 例子: 线程A持锁1等锁2 + 线程B持锁2等锁1 → 死锁 避免: 全局加锁顺序一致 / 用 std::lock 一次锁多个 常见坑 问题 原因 解决 死锁 加锁顺序不一 / 忘解锁 固定顺序 / std::lock / RAII 假唤醒 条件变量被无故唤醒 wait 用谓词条件,循环检查 性能差 锁粒度太粗 缩小临界区 / 用原子 数据竞争 忘记加锁 一律 RAII 锁,变量尽量用 atomic 忙等耗 CPU 自旋锁滥用 用条件变量阻塞等待 顿悟 锁保护的是「数据」,不是「代码」:所有访问同一个共享变量的地方都要用同一把锁,漏一处就出 bug。 RAII 是防呆设计:lock_guard 把"忘解锁"从可能性变成不可能,这是 C++ 现代写法核心。 原子 ≠ 万能:只能处理单个变量的简单操作;涉及多个变量的一致性仍需锁。 条件变量的 wait 里一定有循环:防止假唤醒,这是教科书写法,别省。 自测清单 理解数据竞争为什么会丢更新 会用 std::mutex + std::lock_guard 区分 lock_guard 与 unique_lock 的适用场景 能写生产者-消费者(条件变量) 理解死锁四条件,知道如何避免 知道什么时候用 std::atomic

2026 年 10 月 13 日

C++ 函数指针、std::function 与 lambda

“把函数当值用"的三种写法:函数指针、std::function、lambda。包含语法拆解、三者对比、捕获方式速记与常见坑。 核心概念速查 概念 说明 关键点 函数指针 存函数地址的变量 C 风格,语法绕 std::function 通用函数包装器 C++11,可存 lambda/仿函数 lambda 匿名函数对象 C++11,现代首选 回调 函数指针的典型用途 事件、排序、遍历 捕获 lambda 使用外部变量 [ ] [&] [=] [x] 为什么需要函数指针 把"行为"作为参数传递,让代码更灵活。 // 没有函数指针: 每有一种排序规则就要写一个函数 void sort_ascending(int* arr, int n); void sort_descending(int* arr, int n); // 有函数指针: 一个排序函数,规则由调用方决定 void sort(int* arr, int n, bool (*compare)(int, int)); 函数指针基础语法 #include <iostream> // 定义两个比较函数 bool ascending(int a, int b) { return a < b; } bool descending(int a, int b) { return a > b; } // 参数是函数指针: bool (*compare)(int, int) void sort(int* arr, int n, bool (*compare)(int, int)) { for (int i = 0; i < n - 1; ++i) for (int j = i + 1; j < n; ++j) if (compare(arr[j], arr[i])) // 调用比较函数 std::swap(arr[i], arr[j]); } int main() { int arr[] = {3, 1, 4, 1, 5}; sort(arr, 5, ascending); // 传函数名(自动转成指针) // sort(arr, 5, &ascending); // 显式取地址也可以 sort(arr, 5, descending); for (int x : arr) std::cout << x << " "; return 0; } 语法记忆:bool (*compare)(int, int) —— *compare 是函数名,(int,int) 是参数,bool 是返回类型。不加括号 bool *compare(...) 就变成返回指针的函数了。 ...

2026 年 10 月 13 日

C++ 基础遗漏知识点:bool、sizeof、auto、nullptr 与 static

容易被忽略但常出问题的基础语法点:bool 与 boolalpha、sizeof 的指针陷阱、auto 的 const 剥离、nullptr 与 static。 1. bool 类型与 boolalpha 核心规则 场景 结果 说明 bool b = true 输出 1 默认输出数字 bool b = false 输出 0 默认输出数字 bool b = -8 输出 1 非零即为 true bool b = 0 输出 0 零即为 false boolalpha 操纵符 使用 std::boolalpha 可以让 cout 输出 true/false 而不是 1/0。 #include <iostream> using namespace std; int main() { bool isTrue = true; // 默认输出(数字) cout << "默认: " << isTrue << endl; // 输出: 1 // 使用 boolalpha(文字) cout << boolalpha; cout << "boolalpha: " << isTrue << endl; // 输出: true // 非零值也为 true bool b = -8; cout << "-8 转 bool: " << b << endl; // 输出: true return 0; } 常见陷阱 // 陷阱1:非零即为 true bool b1 = 42; // true bool b2 = -1; // true bool b3 = 0.001; // true bool b4 = nullptr; // false (C++11) // 陷阱2:bool 与整数混合运算 int a = true + true; // a = 2(true 被提升为 1) int b = true * 10; // b = 10 2. sizeof 运算符 基本用法 #include <iostream> using namespace std; int main() { cout << "char: " << sizeof(char) << endl; // 1 cout << "short: " << sizeof(short) << endl; // 2 cout << "int: " << sizeof(int) << endl; // 4 cout << "long: " << sizeof(long) << endl; // 4 或 8 cout << "long long: " << sizeof(long long) << endl; // 8 cout << "float: " << sizeof(float) << endl; // 4 cout << "double: " << sizeof(double) << endl; // 8 // 数组 int arr[10]; cout << "int[10]: " << sizeof(arr) << endl; // 40 // 指针 int* p; cout << "int*: " << sizeof(p) << endl; // 4 或 8 return 0; } 动态数组的 sizeof 陷阱 int* arr = new int[10]; cout << sizeof(arr) << endl; // 输出 4 或 8(指针大小,不是数组大小!) // 正确获取动态数组大小:需要额外传参或用 vector 3. auto 关键字(C++11) 基本用法 auto x = 42; // int auto y = 3.14; // double auto z = "hello"; // const char* auto flag = true; // bool // 推荐用于复杂类型 #include <vector> #include <map> std::vector<int> v = {1, 2, 3}; auto it = v.begin(); // std::vector<int>::iterator std::map<std::string, int> m; auto pair = std::make_pair("key", 1); 注意事项 // auto 会忽略顶层 const const int a = 10; auto b = a; // b 是 int,不是 const int const auto c = a; // c 是 const int // 引用形式的 auto 才保留底层 const const int& ref = a; auto ref2 = ref; // ref2 是 int 拷贝——普通 auto 剥离引用,连同底层 const 一起丢 auto& ref3 = ref; // ref3 是 const int&——auto& 保留底层 const(不能改 *ref3) 4. nullptr(C++11) 与 NULL 的区别 // NULL 在 C++ 中可能是 0 或 (void*)0 void func(int); void func(char*); func(NULL); // 编译错误!歧义 func(nullptr); // OK,调用 func(char*) 最佳实践 // 初始化指针 int* p = nullptr; // 判断指针 if (p != nullptr) { // 使用 p } // 函数参数 void foo(int* ptr) { if (ptr == nullptr) { return; } } 5. const 与 constexpr(简记) 特性 const constexpr 初始化时机 运行时 编译时 可用于函数返回值 是 是 编译期常量 否 是 const int x = 10; // OK constexpr int y = 20; // OK,编译期常量 // constexpr 函数 constexpr int square(int n) { return n * n; } constexpr int val = square(5); // 编译期计算 const 与 constexpr 的完整对比、constexpr 函数、if constexpr、C++20 的 consteval/constinit,见另一篇《C++ namespace、const 与 constexpr 知识点总结》。 ...

2026 年 10 月 13 日

C++ 网络编程基础:Socket API 与 TCP 通信

围绕 Socket API 与 TCP/UDP 通信的核心概念、函数对照、示例代码与常见坑。 核心概念速查 概念 说明 关键点 Socket 网络通信端点 文件描述符抽象 TCP 面向连接、可靠 三次握手、四次挥手 UDP 无连接、不可靠 快速、轻量 sockaddr 通用地址结构 IPv4/IPv6 统一接口 字节序 大端/小端转换 htons() htonl() TCP 三次握手与四次挥手 三次握手(建立连接): Client ──SYN────▶ Server Client ◀─SYN+Ack─ Server Client ──Ack────▶ Server 四次挥手(关闭连接): Client ──FIN────▶ Server Client ◀─Ack───── Server Client ◀─FIN───── Server Client ──Ack────▶ Server TCP 客户端-服务器模型 服务器: socket() → bind() → listen() → accept() → recv/send → close() 客户端: socket() → connect() → send/recv → close() 核心函数对照表 函数 用途 参数要点 socket() 创建套接字 AF_INET, SOCK_STREAM, 0 bind() 绑定地址 套接字 + sockaddr_in listen() 监听连接 backlog 队列长度 accept() 接受连接 返回新套接字描述符 connect() 发起连接 服务器地址 send/recv() 发送/接收数据 缓冲区 + 长度 htons/htonl() 主机→网络字节序 short/long ntohs/ntohl() 网络→主机字节序 short/long 从 Qt 到原生 Socket Qt 写法 原生等价物 差异要点 server.listen(QHostAddress::Any, 8080) socket() + bind() + listen() 三步 Qt 一行帮你做三件事 newConnection 信号 → nextPendingConnection() accept() Qt 信号通知;原生阻塞死等 socket->connectToHost("ip", port) inet_pton() + connect() IP 解析要自己做 socket->write(bytes) send(fd, buf, len, 0) 返回值要检查,可能只发一半 readyRead 信号 → readAll() recv(fd, buf, size, 0) 没有"就绪通知",直接阻塞读 errorOccurred / stateChanged 检查返回值 + errno 错误要手动判断 QUdpSocket::writeDatagram/readDatagram sendto()/recvfrom() 多一个对端地址参数 QHostAddress("127.0.0.1") sockaddr_in + inet_pton() 字符串 IP 手动转结构体 端口字节序自动处理 必须自己 htons() Qt 内部帮你转了 核心差异:Qt 信号槽背后是「事件循环 + 就绪通知」(reactor 模式)。原生编程没有这个循环——要么阻塞死等,要么用线程,要么用 select/epoll 自己搭事件循环。学原生网络的本质,就是把 Qt 隐藏的三件事(fd 管理、字节序、事件分发)亲手做一遍。 ...

2026 年 10 月 13 日

C++ 线程基础:std::thread、join/detach 与传参陷阱

std::thread 的创建方式、join 与 detach 的语义、传参陷阱与常见坑。同步机制(互斥锁、条件变量、原子操作)见另一篇并发笔记。 核心概念速查 概念 说明 关键点 进程 独立运行的程序实例 有独立内存空间 线程 进程内的执行单元 共享进程内存 std::thread C++11 线程库 #include <thread> join 等待线程结束 阻塞当前线程 detach 分离线程 后台运行,不再可 join 进程 vs 线程 维度 进程 线程 内存空间 独立 共享 通信 管道/队列/文件 直接共享变量(需同步) 切换开销 大 小 创建开销 大 小 崩溃影响 只影响自身 可能带崩整个进程 适用场景 隔离、分布式 高并发、I/O 密集 创建线程 #include <iostream> #include <thread> void hello() { std::cout << "Hello from thread\n"; } int main() { // 方式1: 无参函数 std::thread t1(hello); // 方式2: lambda std::thread t2([]{ std::cout << "Hello from lambda\n"; }); // 方式3: 带参函数 auto print_num = [](int n) { std::cout << "Number: " << n << "\n"; }; std::thread t3(print_num, 42); t1.join(); t2.join(); t3.join(); return 0; } join 与 detach 操作 行为 何时用 join() 等待线程结束才继续 需要线程结果时 detach() 立即返回,线程后台跑 结果不关心时(慎用) 陷阱:线程要么 join 要么 detach,都不做 → 析构时 std::terminate() 崩溃。detach 的线程访问已销毁的对象 → 未定义行为。 ...

2026 年 10 月 13 日