将`int *`转换为指向位集的指针时会发生什么

问题描述

我正在做一个简单的测试,以数字的二进制表示形式计算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_castint转换为std::bitset<32>。根据{{​​1}}(link)的规范:

static_cast

  1. 如果存在从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)的规范:

  1. 指向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()

因此,在这种情况下,地址清理会检测到您的程序试图读取超出分配大小的数据。

因此,关于您的有关内存泄漏的问题,这不是内存泄漏,而是缓冲区溢出。当您分配内存然后忘记释放内存时,就会发生内存泄漏。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...