分区内存时严格别名和对齐问题

时间:2017-08-10 14:55:43

标签: c++ pointers memory-management c++14

我的目标是分配一块内存,然后将其分成不同类型的较小数组。关于我在这里写的代码,我有几个问题:

#include <iostream>
#include <cstdint>
#include <cstdlib>

int main() {
    constexpr std::size_t array_count   = 5;
    constexpr std::size_t elements_size = sizeof(std::uint32_t) + sizeof(std::uint16_t);

    void* const pv = std::calloc(array_count, elements_size);

    //Partition the memory. p32 array starts at pv, p16 array starts after the 20 byte buffer for the p32 array.
    std::uint32_t* const p32 = (std::uint32_t *) pv;
    std::uint16_t* const p16 = (std::uint16_t *)((char *) pv + sizeof(std::uint32_t) * array_count);

    //Initialize values.
    for(std::size_t i = 0; i < array_count; ++i) {
        p32[i] = i;
        p16[i] = i * 2;
    }

    //Read them back.
    for(std::size_t i = 0; i < array_count; ++i) {
        std::cout << p32[i] << std::endl;
        std::cout << p16[i] << std::endl;
        std::cout << std::endl;
    }

    std::free(pv);
}
  1. 此代码是否违反了c ++严格的别名规则?从malloc或calloc调用转换指针时,我在查找别名资源时遇到问题。 p32和p16指针永远不应重叠。
  2. 如果我反转p16从pv开始的两个数组的定位,并且p32与pv有10个字节的偏移,这可能会导致段错误,因为uint32_t与4字节边界对齐pv + 10可能在2字节上边界,对吗?
  3. 这个程序是不安全的,还是引入了我一般都缺少的任何未定义的行为?我在本地计算机上获得了预期的输出,但当然这并不意味着我的代码是正确的。

4 个答案:

答案 0 :(得分:4)

是的,该计划是UB。当你这样做时:

for(std::size_t i = 0; i < array_count; ++i) {
    p32[i] = i;
    p16[i] = i * 2;
}

uint32_tuint16_t没有p32p16个对象。 calloc只给你字节,而不是对象。你不能只有reinterpret_cast个对象存在。最重要的是,索引仅为数组定义,p32不指向数组。

为了明确定义,您必须创建一个数组对象。但是,数组的展示新位置为broken,因此您可以手动初始化一堆uint32_t,如:

auto p32 = reinterpret_cast<uint32_t*>(pv);
for (int i = 0; i < array_count; ++i) {
    new (p32+i) uint32_t; // NB: this does no initialization, but it does satisfy
                          // [intro.object] in actually creating an object
}

然后会遇到一个单独的问题:CWG 2182。现在我们有array_count uint32_t个,但我们没有uint32_t[array_count]所以索引仍然是UB。基本上,完全没有办法用纯粹的标准C ++编写这个。另请参阅my similar question on the topic

也就是说,在野外执行此操作的代码量非常大,每个实现都允许您执行此操作。

答案 1 :(得分:2)

我只会解决问题的严格别名部分。

C ++标准对malloc的讨论很少 - 大多数提到它在C中有语义定义。严格阅读C ++标准,没有别名规则违规,因为没有对象别名 - 在C ++中,对象的生命周期在构造之后开始,并且malloc调用没有构造任何对象。

因此,这是标准未指定的内容(与未定义相反)。

答案 2 :(得分:0)

  1. 不,它只是malloc返回值的正常转换并处理已分配的内存。您可以将这些字节重新解释为您想要的任何数据类型。直到你触摸分配块边界后面的内存。

  2. 没有

  3. 我知道你只是在询问和尝试,但你应该通常分配内存。

    std::uint32_t* const p32 = std::calloc(array_count, sizeof(std::uint32_t));

    std::uint16_t* const p16 = std::calloc(array_count, sizeof(std::uint16_t));

答案 3 :(得分:0)

这个程序不安全吗?

在您的示例中,我认为异常完全没有问题,但我想您打算在另一个上下文中使用它。如果您的代码抛出异常,您可能会泄漏一些内存。

你真的需要使用calloc / free吗?在cppcore指南中,您可以找到有关C ++中资源管理的一些指导原则: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#S-resource

例如,R10和R11说“不要明确调用malloc / calloc / free,new / delete”:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rr-mallocfree

只有当你正在做一些真正需要非常低级别代码的东西时,你需要显式地调用new和delete,但C ++方法是将所有这些调用封装在一个类中,因此该类的用户不需要明确调用new并删除。

但是如果你没有做一些低级别的东西,你考虑使用std :: array&lt;&gt ;?或者std :: vector?