CppCon 2015 学习:The dangers of C-style casts
C风格类型转换的危险
Joshua Gerrard 的文章讨论了 C风格类型转换(C-style cast)的危险性,并通过几个示例来演示它的潜在问题。C风格转换有时候会让程序看起来没有错误,但实际上可能会引入潜在的类型不匹配问题,这些问题可能在编译时没有警告,也可能在运行时导致错误。以下是对文章的中文理解:
示例 1:
float x = 1.0f;
double* y = (double*) &x;
你认为这段代码能通过编译吗?
- GCC 5.2.0 和 MSVC 19 甚至不会发出警告。C风格转换
(double*)
会让编译器接受将float*
强制转换为double*
,但是这种转换是非法的,因为float
和double
类型在内存布局上是不同的。- 问题: 即使没有警告,它仍然是不安全的,因为这样做会破坏类型系统,可能导致不可预期的行为。
改成static_cast
之后:
- 问题: 即使没有警告,它仍然是不安全的,因为这样做会破坏类型系统,可能导致不可预期的行为。
float x = 1.0f;
double* y = static_cast<double*>(&x);
你认为这会编译通过吗?
- GCC 5.2.0 会报错:
invalid static_cast from type 'float*' to type 'double*'
- MSVC 19 会报错:
C2440 'static_cast': cannot convert from 'float *' to 'double *'
分析: static_cast
是类型转换的一个更安全的版本。它在编译时会进行类型检查,如果转换无效,则会发出错误。而 C风格转换则没有这种检查,可能导致程序在运行时出现潜在的类型问题。
示例 2:
struct T;
const float x = 1.0f;
T* y = (T*) &x;
你认为这段代码能通过编译吗?
- GCC 5.2.0 和 MSVC 19 不会发出任何警告,直接编译通过。但它实际上是错误的。
改成static_cast
之后:
T* y = static_cast<T*>(&x);
你认为这会编译通过吗?
- GCC 5.2.0 会报错:
invalid static_cast from type 'const float*' to type 'T*'
- MSVC 19 会报错:
C2440: 'static_cast': cannot convert from 'const float *' to 'T *'
分析: static_cast
在这里无法进行类型转换,因为x
是一个const
类型的对象,而T*
是一个非const
类型的指针。static_cast
不能在这种情况下去除const
限定符。- C风格转换 不会做这种检查,它会将
const
移除,这会导致潜在的未定义行为。
发生了什么?
在 C++ 中,C风格转换是 “隐式的多重类型转换”。根据 N4296 - 5.4 Explicit type conversion 规范,C风格转换会尝试以下几个步骤,直到有一个成功:
- const_cast:移除或添加
const
限定符。 - static_cast:执行类型之间的转换(如从基类到派生类)。
- reinterpret_cast:执行不同类型之间的转换(通常用于低级内存操作)。
- reinterpret_cast + const_cast:同时进行
reinterpret_cast
和const_cast
。
问题:
C风格转换会进行多个步骤的类型转换,而且不会检查是否符合类型系统的规则,这就导致了它的危险性。对于复杂的类型转换,C风格转换会绕过编译器的类型检查,从而可能导致程序出现难以发现的错误。
为什么 static_cast
会出问题?
static_cast
是一种安全的类型转换方式,它会进行编译时检查。对于不合法的类型转换,它会直接报错,避免出现运行时错误。
- 比如,在上面的例子中,
static_cast
不会允许将const float*
转换为T*
,因为这违反了 C++ 的类型系统。 static_cast
可以在一些合法的情况下进行转换,例如从基类指针转换为派生类指针,但必须保证转换是合法的、符合类型层次结构的。
总结
C风格类型转换非常灵活,但也非常危险。它不会进行类型检查,可能导致未定义的行为。使用 static_cast
、dynamic_cast
、const_cast
和 reinterpret_cast
等现代 C++ 类型转换工具,能够帮助我们更加安全地进行类型转换,确保类型匹配,并且提高代码的可读性和可维护性。
总之,建议尽量避免使用 C风格的类型转换,转而使用 C++ 提供的类型转换方式,避免潜在的风险。