C++ 中获取对象地址的两种方式:std::addressof 函数和 & 取地址运算符,它们的区别和适用场景是什么?我会从基础到进阶帮你梳理清楚。
一、基础概念:& 运算符的常规用法
& 是 C++ 原生的取地址运算符。在绝大多数场景下,它能正确返回对象或变量在内存中的实际地址,也是最常用的取地址方式。
1. 基本使用示例
#include <iostream>
using namespace std;
int main() {
// 普通变量取地址
int a = 10;
int* p_a = &a;
cout << "变量a的地址: " << p_a << endl; // 输出a的内存地址
// 类对象取地址
class MyClass {
public:
int x = 5;
};
MyClass obj;
MyClass* p_obj = &obj;
cout << "对象obj的地址: " << p_obj << endl; // 输出obj的内存地址
return 0;
}
说明:
- 对于普通变量、未重载
operator& 的类对象,& 直接返回其真实内存地址。
- 语法简单,性能无额外开销,是日常开发的首选。
2. & 运算符的 “陷阱”:重载导致的失效
& 运算符可以被类重载,这会导致它不再返回对象的真实地址,而是返回重载函数指定的内容 —— 这是 & 最关键的局限性。
示例:重载 operator& 后的异常情况
#include <iostream>
using namespace std;
class BadClass {
public:
// 重载取地址运算符,返回固定值(非真实地址)
void* operator&() {
return (void*)0x12345678; // 伪造地址
}
// 甚至可以重载const版本
const void* operator&() const {
return (void*)0x87654321;
}
int val = 99;
};
int main() {
BadClass obj;
// 调用重载的operator&,返回伪造的0x12345678,而非obj的真实地址
cout << "重载&后的地址: " << &obj << endl;
// 尝试直接访问成员,地址是真实的(对比用)
cout << "obj.val的真实地址: " << &obj.val << endl;
return 0;
}
输出:
重载&后的地址: 0x12345678
obj.val的真实地址: 0x7ffeeabc08ac
可以看到:&obj 被重载函数篡改,返回了虚假地址,而非对象的真实内存地址。
二、std::addressof:获取真实地址
std::addressof 是 C++11 引入的标准库函数(定义在 <memory> 头文件)。它的核心作用是无视 operator& 的重载,强制返回对象的真实内存地址。
1. 基本使用示例
#include <iostream>
#include <memory> // 必须包含此头文件
using namespace std;
class BadClass {
public:
void* operator&() {
return (void*)0x12345678; // 重载&,返回伪造地址
}
int val = 99;
};
int main() {
BadClass obj;
// 用&取地址:返回重载后的伪造地址
cout << "&obj: " << &obj << endl;
// 用std::addressof取地址:返回真实地址
cout << "addressof(obj): " << addressof(obj) << endl;
// 验证:真实地址和成员val的地址前缀一致
cout << "&obj.val: " << &obj.val << endl;
return 0;
}
输出:
&obj: 0x12345678
addressof(obj): 0x7ffeeabc08a8
&obj.val: 0x7ffeeabc08ac
可以看到:std::addressof(obj) 成功绕过了重载的 operator&,返回了对象的真实地址(和 obj.val 的地址属于同一内存区域)。
2. std::addressof 的底层原理(简化理解)
std::addressof 之所以能无视重载,是因为它不调用 operator&,而是通过编译器内置的机制直接获取对象的内存地址。其核心逻辑可简化理解为:
template <typename T>
T* addressof(T& obj) noexcept {
// 通过指针操作绕过operator&重载,直接取真实地址
return reinterpret_cast<T*>(
&const_cast<char&>(reinterpret_cast<const volatile char&>(obj))
);
}
简单说:它通过巧妙的类型转换绕开了用户自定义的 operator&,直接访问对象的原始内存地址。这对于编写可靠的通用库代码至关重要。
三、& 和 std::addressof 的对比与适用场景
| 特性 |
& 运算符 |
std::addressof |
| 语法 |
简洁,原生支持 |
需要包含 <memory>,语法稍繁琐 |
| 性能 |
无额外开销(编译期直接解析) |
几乎无开销(模板函数,编译器会内联) |
| 重载影响 |
会被 operator& 重载篡改结果 |
无视 operator& 重载,返回真实地址 |
| 适用场景 |
普通变量、未重载 operator& 的对象 |
重载了 operator& 的对象、泛型编程 |
适用场景说明:
- 日常开发(无重载 operator&): 优先用
&,语法简洁,无学习成本。
- 泛型编程(模板): 推荐用
std::addressof —— 你无法保证模板参数类型是否重载了 operator&,用 std::addressof 能避免意外错误。
示例(泛型函数取地址):
template <typename T>
void printRealAddress(T& obj) {
// 无论T是否重载operator&,都能获取真实地址
cout << "真实地址: " << addressof(obj) << endl;
}
- 需要绝对真实地址的场景: 比如底层内存管理、调试、序列化等,必须确保地址是对象的实际内存位置时,使用
std::addressof。
四、std::addressof 的注意事项
- 仅支持左值:
std::addressof 只能接收左值引用(T&),无法直接获取右值的地址(右值本身无稳定内存地址)。错误示例:std::addressof(10);(编译报错)。
- C++11 及以上支持: 如果你的项目需要兼容 C++03,无法使用
std::addressof,需自行规避重载 operator& 的类。
- 对函数/数组的处理: 和
& 一致,std::addressof 对函数或数组取地址时,会返回其首地址(函数名/数组名本身就是地址标识)。
总结
& 运算符: C++ 原生取地址方式,简洁高效。但如果类重载了 operator&,它会返回重载后的结果(可能非真实地址)。
std::addressof: C++11 引入的标准库函数(需包含 <memory>),核心作用是无视 operator& 重载,强制返回对象的真实内存地址。
- 使用原则: 日常场景用
&,泛型编程或需要获取绝对真实地址的场景用 std::addressof,这样可以有效避免因重载 operator& 而导致的意外错误,写出更健壮的代码。
如果你想了解更多关于 C++ 底层机制和现代特性的深入讨论,欢迎访问 云栈社区。