C++ 中有 4 种类型转换操作符:static_cast、dynamic_cast、const_cast、reinterpret_cast。
今天我们深入聊聊用得最多的 static_cast。
很多人对 static_cast 有个误解:它会不会影响性能?
这篇文章会解答两个问题:它到底消不消耗性能,以及它底层是怎么实现的。
一、性能损耗从哪来?
如果你听说过"类型转换影响性能",那多半是把 static_cast 和 dynamic_cast 搞混了。
这两个东西完全不一样:
static_cast 是编译期干的活,运行时零开销。
而 dynamic_cast 需要在运行时查类型信息(RTTI),这个确实有开销。
二、static_cast 的本质是做规则检查
static_cast 本质上就是「在编译期,做类型转换规则检查」,它不会产生任何运行时指令。
说白了,static_cast 是给编译器看的,不是给 CPU 看的,不产生 CPU 运行的指令。
它的作用就是告诉编译器:"我确定这样转是合法的,你帮我检查一下语法"。
只要通过了编译期检查,生成的机器码跟不用 static_cast 时完全一样。
static_cast 会在编译的语义分析阶段和类型检查阶段触发一套规则:检查这两个类型能不能互相转换、验证语法是不是合法的(比如继承关系在不在)、生成跟隐式转换一样的代码。
三、汇编代码验证:真的零成本
我们用三个函数来证明,它们生成的汇编代码完全一样:

汇编输出(x86-64)完全相同:
asm
func1(int*):
mov rax, rdi
ret
三个函数的汇编一模一样,证明 static_cast 运行时没有任何额外开销。
四、几个典型场景:static_cast 到底在干啥
我们看几个实际例子,看看 static_cast 到底做了什么。
场景 1:数值类型转换

当你要把一个大的数值类型(比如 double,占8字节)塞进一个小的类型(比如 int,占4字节)时,static_cast 很有用。
这时显式转换就是在告诉编译器和其他读代码的人:我知道会丢精度,但我就是要这样做。
正常情况下,编译器看到你把 double 直接赋值给 int,会给你警告。
但你用了 static_cast,编译器就知道这是你有意为之,就不警告了。
隐式转换可能是你不小心写错了,所以编译器提醒你;显式用 static_cast 就表明你清楚自己在干啥。
从性能角度看,这个转换本身也是零成本的。
编译器只是截取 double 的整数部分放进 int,CPU 一条指令就搞定了。
场景 2:void* 转回具体类型

从 void* 转成 int* 是 C++ 标准明确允许的。
编译期只改类型系统里的指针类型,不改地址值。运行期啥也没发生,地址没变,CPU 完全不知道你转了类型。
这本质上等价于老式 C 风格的 int* pp = (int*)q;,但 static_cast 更安全。
虽然两种写法生成的代码一样,但 static_cast 有编译期约束:你不能把完全不相关的类型瞎转(比如把 int* 转成 string*),代码维护时更容易发现问题,代码意图也更清楚。
场景 3:类继承里的指针转换

编译器在编译期确认 Msg 是 AlarmMsg 的基类,转换关系没问题。
如果 Msg 是第一个基类(最常见),啥也不做。
如果有多继承,可能会生成固定偏移量 add rax, 8,但这个偏移量是编译期算好的常量,不用查虚函数表,也不用 RTTI。
关键在于,这是编译期确定的指针算术,不是运行时类型检查。
五、static_cast 和 dynamic_cast 有啥区别
两个东西本质不一样。
static_cast 编译期检查,零开销,适合你确定类型关系的时候用,出错直接编译失败。
dynamic_cast 运行期检查,要查 RTTI,适合你不确定的向下转型(从Msg* 转 AlarmMsg*),失败了返回 nullptr 或抛异常。
简单说,static_cast 靠你自己保证类型安全,dynamic_cast 帮你在运行时验证。
六、啥时候需要关心性能?
如果你真关心性能,static_cast 不该是你的重点。
真正该担心的是 dynamic_cast(要查运行时 RTTI)、隐式构造和拷贝(可能分配内存)、虚函数调用(要通过虚函数表间接调)、还有不连续的内存访问导致的 cache miss 等等等等等~
七、static_cast 的源码在哪?
这个问题挺有意思:static_cast 没有源码。
它不是函数,也不是能找到的一段代码。
static_cast 是 C++ 语言的一部分,定义在 C++ 标准里,是编译器的内置功能,编译器在语义分析阶段处理,生成中间代码时就已经被"消化"掉了。
如果你想理解它的"实现",得去看 C++ 标准文档(ISO C++ Standard)或者编译器源码,比如 Clang 的类型检查模块、GCC 的语义分析部分。
八、实用建议
1、推荐用 static_cast 的场景
明确的数值类型转换是最常见的:
cpp
double d = 3.14;
int i = static_cast<int>(d);
void* 转回具体类型也很常见,特别是用 C 风格 API 时:
cpp
void* ptr = malloc(sizeof(int));
int* i = static_cast<int*>(ptr);
确定的向上转型(派生类转基类)肯定安全:
cpp
Derived* d = new Derived;
Base* b = static_cast<Base*>(d); // 肯定安全
2、要小心的场景
向下转型(基类转派生类)得格外小心:
cpp
Base* b = ...;
// 只有当你 100% 确定 b 指向 Derived 对象时才安全
Derived* d = static_cast<Derived*>(b);
更安全的做法是用 dynamic_cast,它会在运行时帮你验证:
cpp
Derived* d = dynamic_cast<Derived*>(b);
if (d) {
// 转换成功
}
别用 static_cast 的场景
去掉 const 属性得用 const_cast:
cpp
const int* cp = ...;
// int* p = static_cast<int*>(cp); // 错了!得用 const_cast
不相关类型之间转换得用 reinterpret_cast:
cpp
int* ip = ...;
// char* cp = static_cast<char*>(ip); // 错了!得用 reinterpret_cast
八、总结
核心就这么几点:static_cast 是零成本抽象,编译期搞定所有事,运行时没开销。它不是函数调用,是编译器的语言特性,不产生额外指令。比 C 风格转换更好,类型检查更严格,代码更好读。
性能问题通常来自 dynamic_cast、虚函数、内存分配等,不是 static_cast。
最好的做法是:优先用 static_cast 别用 C 风格转换,向下转型时优先考虑 dynamic_cast,同时关注真正的性能瓶颈:算法复杂度、内存访问模式、不必要的拷贝。
当然,实际写代码时还得看团队习惯和项目风格,有些团队代码可能更倾向于用隐式转换。
理解 static_cast 的原理,才能真正用好它。