HoRain 云小助手个人主页⛺️生活的理想就是为了理想的生活!⛳️ 推荐前些天发现了一个超棒的服务器购买网站性价比超高大内存超划算忍不住分享一下给大家。点击跳转到网站。目录⛳️ 推荐一、语法骨架跟 Java 长得像但细节松很多二、与 Java 异常对照面试高频三、栈展开Stack Unwinding RAII —— C 异常的灵魂栈展开过程RAII 怎么搭上这趟车两个容易踩的坑四、异常安全四级C 独有概念面试加分五、noexcept—— C11 起的异常规格六、工程现实不是所有 C 项目都用异常七、几句面试能加分的话C 异常跟 Java 那套表面像try/catch/throw底子完全不同——Java 是 checked finally 栈轨迹完备C 是任意类型可抛 无 checked 无finally靠 RAII 零开销原则 析构不能抛还多了异常安全等级这套独有概念。加上不少团队游戏、嵌入式、高频交易直接-fno-exceptions禁用异常整体比 Java 那侧野得多。下面按语法骨架 → 与 Java 对照 → 栈展开与 RAII → 异常安全四级 →noexcept→ 工程取舍走。一、语法骨架跟 Java 长得像但细节松很多#include stdexcept #include iostream using namespace std; void divide(int a, int b) { if (b 0) throw invalid_argument(divisor is zero); // 标准异常stdexcept cout a / b endl; } int main() { try { divide(10, 0); } catch (const invalid_argument e) { // 推荐 const 引用接 cerr e.what() endl; // what() 取错误信息 } catch (const exception e) { // 基类兜底多态 cerr std exception: e.what() endl; } catch (...) { // 万能兜底抓一切 cerr unknown exception endl; } }几个跟 Java 立刻能感受到的差异throw任意类型throw 42、throw oops、throw std::string(err)都合法但工程上推荐继承std::exception标准库那一票runtime_error/logic_error/out_of_range都在stdexcept里catch匹配规则精确匹配或派生→基类允许const转换catch(...)是万能兜底但拿不到异常内容重新抛出catch里写throw;不带表达式把原异常继续往上传保留类型没有finally清理靠析构 RAII下面讲二、与 Java 异常对照面试高频维度JavaC可抛类型必须Throwable子类任意类型int、指针、类都行Checked 异常有throws声明 编译器强制无函数签名不透露会抛什么finally有清资源无靠 RAII 析构栈轨迹每个异常自带调试友好只有what()字符串栈轨迹 C23 才有std::stacktrace性能即使不抛也有少量开销fillInStackTrace贵零开销原则——不抛时零额外成本抛时展开成本高析构约束finally里手动清析构函数默认不应抛否则std::terminate来源 。 零开销原则是 C 异常设计的命门编译器用表.eh_frame记录展开信息正常路径不产生任何额外指令只有真的throw了才查表 展开栈。所以 C 圈共识——异常只用于真异常别拿来控制正常流程否则性能被打脸。三、栈展开Stack Unwinding RAII —— C 异常的灵魂栈展开过程throw后运行时沿调用栈逐帧回退当前函数找try/catch→ 没匹配退出当前栈帧析构当前作用域所有已构造的栈对象逆序跟构造相反上一层继续找catch找到 → 进catch找不到 →std::terminate()RAII 怎么搭上这趟车RAIIResource Acquisition Is Initialization资源生命周期绑到对象构造/析构上栈展开保证析构必调异常安全就有了根基。class File { FILE* fp; public: File(const char* name) : fp(fopen(name, r)) { if (!fp) throw runtime_error(open failed); } ~File() { if (fp) fclose(fp); } // 栈展开时自动调 }; void read() { File f(data.txt); // 构造打开 throw runtime_error(oops); // 抛异常 → 栈展开 → ~File() → fclose() }哪怕throw在中间文件也关了——这就是 C 敢没finally的底气。两个容易踩的坑坑 1构造函数里抛异常对象没构造成功析构不会被调因为对象根本没活过但已构造的子成员会逆序析构。所以成员变量按声明顺序初始化依赖关系要小心。坑 2析构函数再抛异常栈展开过程中如果析构又抛 →立刻std::terminate。所以 C11 起析构函数默认noexcept你不该在~T()里抛任何东西。四、异常安全四级C 独有概念面试加分C 标准库对函数遇到异常后程序处于什么状态定义了四级保证等级含义例子No Guarantee异常后状态不可预测可能泄漏裸new/delete中途抛Basic Guarantee基本不泄资源程序仍合法可析构大多数普通函数目标Strong Guarantee强失败如没调用过原状态不变事务式std::vector::insert承诺No-throw Guarantee不抛绝不抛异常析构函数、swap、移动操作 工程目标新代码至少 Basic重要操作争取 Strong析构/移动必须 No-throw。std::vector搬迁元素时就会检查 move ctor 是否noexcept——是就移动否就拷贝因为拷贝失败还能回滚保 Strong移动失败了回不来。五、noexcept—— C11 起的异常规格C98 那个throw(std::bad_alloc)动态异常规格C17 弃用、C20 删了现在用noexcept。void foo() noexcept; // 等价于 noexcept(true)绝不抛 void bar() noexcept(false); // 可能抛默认行为写不写一样 void baz() noexcept(noexcept(expr)); // 条件式模板里常见语义标记noexcept(true)的函数如果还是抛了包括内部调用链逃出来 → 直接std::terminate不展开栈编译器可以更激进优化是函数类型的一部分C17 起影响重载、模板推导std::vector搬迁时看T(T)是否noexcept决定 move 还是 copy析构函数默认noexcept你显式标noexcept(false)是自找麻烦✅ 哪些该标noexcept析构函数、移动构造/移动赋值、swap、工具函数纯计算不抛的。⚠️ 别乱标一旦标了noexcept里面调用链任何一个函数抛出来都是terminate标之前确认整条调用链都 safe。六、工程现实不是所有 C 项目都用异常这点跟 Java 完全不一样——Java 异常是基础设施C 异常是可选基础设施。禁用的场景游戏引擎Unity/Unreal 部分模块、嵌入式、高频交易——异常展开成本 确定性差宁可返回std::optional/std::expected(C23) / 错误码Google C Style 早年禁异常后来松动但仍有约束编译加-fno-exceptionsthrow直接terminate用异常的场合应用层 C桌面、工具、业务系统构造函数失败这是 C 里少数必须用异常的场景——因为构造函数没返回值跨多层调用栈传错误错误码一层层if (err) return err太脏 现代折中应用层用异常 RAII 爽写底层库给 C 也能用的那种返回std::expected或错误码边界处转一道。七、几句面试能加分的话C 异常零开销原则 不抛时无成本抛时查表展开所以别拿异常控流程Java 靠finallyC 靠析构 RAII这是两语言资源观的根本差异C 确定性析构 vs Java GC析构函数默认noexcept栈展开中析构再抛 →terminate这是 C 独有的坑std::vector搬迁策略取决于T(T)是否noexcept——这题能把异常 模板 STL三线串起来构造函数失败只能抛异常没返回值所以禁用异常的项目构造失败一般走init()二段式或工厂返回optional如果想再往下挖可以聊std::expected怎么替代异常做错误传递、std::uncaught_exceptions()在析构里判断是否在展开中少数库作者才碰、或者C20 coroutine 里异常怎么穿过co_await——挑一个❤️❤️❤️本人水平有限如有纰漏欢迎各位大佬评论批评指正如果觉得这篇文对你有帮助的话也请给个点赞、收藏下吧非常感谢! Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧