问题描述
我正在做一个简单的测试,以数字的二进制表示形式计算1的数量:
int x;
while (cin >> x) {
bitset<32> xBitmap = {0};
xBitmap = static_cast<bitset<32>>(x);
std::cout << xBitmap.count() << std::endl;
}
上面的代码创建了正确的结果,但是当我使用一个指向位集的指针时,会发生意外情况:
bitset<32>* xBitmap = nullptr;
xBitmap = static_cast<bitset<32>*>((void*)&x);
std::cout << xBitmap->count() << std::endl;
此代码创建随机结果,每次使用“ count()”都会创建不同的结果。我猜这是内存泄漏吗?但是为什么会导致内存泄漏呢?
解决方法
您的第一个示例中发生了什么?
您有一个类型为int
的变量,并且执行了static_cast
将int
转换为std::bitset<32>
。根据{{1}}(link)的规范:
static_cast
- 如果存在从
static_cast<new_type>(expression)
到expression
的隐式转换序列,或者如果从new_type
直接初始化对象或类型为new_type
的引用的重载解析将找到至少一个可行的函数,然后expression
返回假想变量static_cast<new_type>(expression)
,好像由Temp
初始化,可能涉及隐式转换,对new_type Temp(expression);
的构造函数的调用或调用用户定义的转化运算符。...
请考虑以下示例:
new_type
运行该程序将打印#include <iostream>
class A {
public:
A(int x) { std::cout << "A " << x << std::endl; }
};
int main(void) {
int y = 13;
A a = static_cast<A>(y);
}
。这意味着在这种情况下,A 13
等效于A a = static_cast<A>(y)
。这是因为A a = A(y)
的类型为y
,并且int
的构造函数使用A
。
如果我们更改示例以使int
的构造函数采用A
,则程序将不再编译:
std::string
编译器会抱怨无法将#include <iostream>
#include <string>
class A {
public:
A(std::string x) { std::cout << "A " << x << std::endl; }
};
int main(void) {
int y = 13;
A a = static_cast<A>(y);
}
转换为int
。
考虑第三个示例:
A
此示例编译并打印:
#include <iostream>
class A {
public:
A(int x) { std::cout << "A " << x << std::endl; }
};
class B {
public:
B(A a) { std::cout << "B" << std::endl; }
};
int main(void) {
int y = 13;
B b = static_cast<B>(y);
}
因此,这就是规范所称的“隐式转换序列”。尽管A 13
B
的构造函数没有B
,但是int
的构造函数却没有B
,而A
的构造函数却没有需要A
。因此int
将解析为static_cast<B>(y)
。如果我们将B(A(x))
关键字添加到explicit
的构造函数中,则该示例将不再编译:
A
这是因为构造函数上的 explicit A(int x) { std::cout << "A " << x << std::endl; }
关键字禁止在隐式转换序列中使用该构造函数。
这些示例使我们能够了解调用explicit
时发生的情况。 static_cast<std::bitset<32>>(x)
类的构造函数采用std::bitset<N>
(reference)。构造函数没有用unsigned long
关键字标记,因此它可以参与隐式转换序列。 explicit
可以隐式转换为int
。因此,unsigned long
解析为static_cast<std::bitset<32>>(x)
,因此它创建了一个std::bitset<32>((unsigned long)x))
的新实例,并将值std::bitset<32>
传递给了构造函数。
这就是第一个示例起作用的原因。
您的第二个示例中发生了什么?
您有一个类型为x
的变量。您创建一个指向该变量(int
)的指针,然后将该指针转换为&x
指针。然后,您将void
的{{1}}指针指向static_cast
指针。根据{{1}}(link)的规范:
- 指向
void
的指针类型(可能是经过cv限定)的prvalue可以转换为指向任何对象类型的指针。
因此,与第一个示例不同,第二个示例将不会创建std::bitset<32>
的新实例。相反,static_cast
指向void
的内存地址,但是将此内存解释为std::bitmap<32>
。但是,存在一个问题:xBitmap
的内存大小可能不等于x
的内存大小。这是特定于实现的,因此对于std::bitmap<32>
,C ++标准库的不同实现可能具有不同的大小。
在我的系统上,使用GCC随附的C ++标准库,以下代码将打印std::bitmap<32>
:
int
这意味着std::bitmap<32>
占用8个字节的内存。虽然32位当然只能用4个字节表示,但看来在我的系统上8
总是会分配8个字节的倍数(即无符号长)。因此,例如std::cout << sizeof(std::bitset<32>) << std::endl;
也是std::bitset<32>
,std::bitset
也是如此,但是sizeof(std::bitset<1>)
是16,8
也是如此,sizeof(std::bitset<64>)
是24,依此类推。
(在我的系统上)sizeof(std::bitset<65>)
仅占用四个字节。因此,当我们使用sizeof(std::bitset<128>)
的内存但将其解释为sizeof(std::bitset<129>)
时,我们将从大小仅为4个字节的内存分配中读取8个字节(int
的大小) 。因此,我们将在int
的存储器之后再读取四个字节。该内存中可能有任何东西,因此读取会导致未定义的行为。这就是为什么在调用std::bitmap<32>
时获得随机值的原因。它将计算std::bitmap<32>
中的位数,也将计算其后四个字节中的位数。
诸如GCC和Clang之类的现代编译器具有称为“地址清理”(ASan)的功能,可以帮助您调试此类内存问题。对于GCC,可以使用int
标志启用它:
count()
因此,在这种情况下,地址清理会检测到您的程序试图读取超出分配大小的数据。
因此,关于您的有关内存泄漏的问题,这不是内存泄漏,而是缓冲区溢出。当您分配内存然后忘记释放内存时,就会发生内存泄漏。