【问题标题】:How to Advance void * pointer?如何推进 void * 指针?
【发布时间】:2021-09-02 06:14:16
【问题描述】:

在 C++ 中我有:

MallocMetadata *tmp = static_cast<MallocMetadata *> (p);

但现在我希望 tmp 在内存中为 5 个字节,所以我尝试了:

MallocMetadata *tmp = static_cast<MallocMetadata *> (p-5);

但这没有编译,我读了一些建议这个的文章(也没有工作):

MallocMetadata *tmp = static_cast<MallocMetadata *> (static_cast<char *> (p) - 5);

如何解决这个问题,请注意:我确定内存中的位置是合法的,而且我希望 tmp 的类型为 MallocMetadata* 以便以后使用。

【问题讨论】:

  • 如果您想弄清楚这一点,为什么要尝试在一行代码中完成所有事情?首先分解代码,看看 2 或 3 行的代码有什么用,然后根据可行的方法将其压缩为一行。此外,使用多行,您有更好的机会确定任何故障点(鉴于代码,这很有可能)。
  • FWIW,创建一个实际上并不指向 MallocMetadata 的 MallocMetadata * 是灾难的根源。您需要非常小心,因为这是在您自己的脚下拍摄的。
  • @NathanOliver 我已经提到它受到保护:)
  • @PaulMcKenzie 进行优化
  • 这是XY problem 请解释这段代码应该做什么(什么是业务逻辑/价值)。放弃思考如何解决您的解决方案。您的解决方案很奇怪并且在防御上无效 - 很可能无法修复。

标签: c++ pointers memory casting void-pointers


【解决方案1】:

您可以使用reinterpret_cast 将void* 以外的指针转换为另一个指针。

MallocMetadata *tmp = reinterpret_cast<MallocMetadata *> (static_cast<char *> (p) - 5);

另一种选择是在再次对void* 减去一些内容后,再转换char*。

MallocMetadata *tmp = static_cast<MallocMetadata *> (static_cast<void *> (static_cast<char *> (p) - 5));

【讨论】:

  • 抱歉,为什么我们需要这个。我从来没有听说过这个并且不想使用 reinterpret_cast
  • @pow 添加了另一个选择。
  • @pow 您尝试将指向char 的指针转换为完全不相关的指针类型。这是一个危险的操作,可以通过reinterpret_cast 来完成。引用 Straustrup:“ reinterpret_cast(p) 是丑陋操作的丑陋符号”。
  • @pow 为什么你不喜欢使用 reinterpret_cast?
  • @eerorika 这不安全
【解决方案2】:

C++如何推进void *指针?

无法推进void*。

将指针前移一位将修改指针以指向对象数组中先前指向的对象的下一个兄弟。数组的两个元素之间的距离在不同类型的对象之间是不同的。距离与物体的大小完全一致。

因此,要使指针前进,就必须知道所指向对象的大小。 void* 可以指向任意大小的对象,并且无法从指针中获取有关该大小的信息。

您可以做的是将void* 静态转换为指向对象的动态类型。然后通过知道指针的类型来知道指向对象的大小,只要类型是完整的。然后,您可以使用指针算法将转换后的指针推进到指向对象的兄弟对象。

但现在我希望 tmp 在内存中之前是 5 个字节

在我们继续之前,我想澄清一下,这是一个不安全的尝试,您必须详细了解语言规则才能正确执行此操作。我敦促您考虑是否有必要这样做。

要获得指向前 5 个字节的内存地址的指针,可以将 void* 静态转换为 unsigned char* 并对转换后的指针进行指针运算:

static_cast<unsigned char*>(p) - 5

MallocMetadata *tmp = static_cast<MallocMetadata *> (static_cast<char *> (p) - 5);

char* 不能静态转换为任意对象指针类型。 如果内存地址正确对齐并且((地址包含类似类型的对象)或(MallocMetadata是一个普通类型并且地址不包含另一种类型的对象,您将写入地址而不是读取,从而创建一个新对象)),那么您可以使用 reinterpret_cast 代替:

MallocMetadata *tmp = reinterpret_cast<MallocMetadata*>(
    static_cast<char*>(p) - 5
);

一个完整的例子:

// preparation
int offset = 5;
std::size_t padding = sizeof(MallocMetadata) >= offset
    ? 0
    : sizeof(MallocMetadata) - offset;
auto align = static_cast<std::align_val_t>(alignof(MallocMetadata));
void* p_storage = ::operator new(sizeof(MallocMetadata) + padding, align);
MallocMetadata* p_mm = new (p_storage) MallocMetadata{};
void* p = reinterpret_cast<char*>(p_mm) + offset;

// same as above
MallocMetadata *tmp = reinterpret_cast<MallocMetadata*>(
    static_cast<char*>(p) - offset
);

// cleanup
tmp->~MallocMetadata();
::operator delete(tmp);

【讨论】:

  • 从技术上讲,void* 只能指向 void 类型的对象。 void 类型的对象的大小未定义。任何将指针前进未定义值的尝试也是未定义的。这只是一个未定义的操作。
  • @StephenM.Webb 无法创建void 类型的对象,因此void* 无法指向此类对象。
  • 对void* 唯一能做的就是通过它传递指向其他对象的指针,这意味着将指针转换为其他类型的对象,然后将其转换回原始指针类型。其他任何东西都是未定义的,包括转换为指向与原始对象不同类型的指针。不明确的。它可能会起作用。它可能不会。它可能会引起鼻猴。推进 void* 是未定义的,因为 void 类型的对象的大小未定义。干净利落。通过 void 转换为别名类型是未定义的。简单明了。
  • @StephenM.Webb 更具体地说,推进void* 的格式不正确。
【解决方案3】:

我不知道你会怎么看,但我会尝试:

标准中有一个要求,即void * 和字符指针具有与 C 语言相同的表示和对齐方式,以及在历史上你拥有 void * 的字符指针类型。

如果你有一个void *,实际上是一个void *,而不是其他类型的指针,并且你想一次推进一个字节,你应该能够创建一个指向指针的引用-unsigned-character 绑定到指向空的指针,如:

auto &ucp = reinterpret_cast<unsigned char *&>(void_pointer);

现在应该可以通过对ucp 引用的操作来操作void_pointer。

所以++ucp 将推进它,因此void_pointer 推进一个。

【讨论】:

    猜你喜欢
    • 2014-05-30
    • 2012-03-19
    • 2011-02-05
    • 2013-12-18
    • 1970-01-01
    • 2016-09-07
    • 2019-02-03
    • 1970-01-01
    • 2020-04-01
    相关资源
    最近更新 更多