我很好奇有关公约的类型punning指针/阵列在C++。这里使用的情况下,我们的时刻:

计算出一个简单的32位校验和二blob的数据把它作为一系列的32位整数(我们知道它的总长度是一个多4个),然后总结了所有的价值观和忽视的溢出。

我希望这样的一个功能是这样的:

uint32_t compute_checksum(const char *data, size_t size)
{
    const uint32_t *udata = /* ??? */;
    uint32_t checksum = 0;
    for (size_t i = 0; i != size / 4; ++i)
        checksum += udata[i];
    return udata;
 }

现在的问题,我们是,你怎么考虑的"最佳"方式转换 dataudata?

C式铸的?

udata = (const uint32_t *)data

C++铸就承担所有的指针是敞篷车?

udata = reinterpret_cast<const uint32_t *>(data)

C++铸之间的任意指使用中间类型 void*?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))

铸通过一种联盟?

union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata

我完全意识到,这将不会是一个100%的携带式的解决方案,但我只是希望使用它的一个小组的平台,在那里我知道它的作品(即不对齐存储器访问和编译器的假设上的指针aliasing)。你有什么建议?

有帮助吗?

解决方案

尽C++标准感到关切的是, litb's的回答是完全正确和最便携式。铸造 const char *data 来一个 const uint3_t *, 无论是通过C-风格投, static_cast, 或 reinterpret_cast, ,打破了严格的混淆规则(见 理解严格的锯齿).如果你汇编有充分优化,有一个很好的机会,代码不正确的事情。

铸造过一个联盟(如litb的 my_reint)是可能的最佳解决方案,尽管它并在技术上违反的规则,如果你写一联盟的一个成员通过并读通过的另一,结果未定义的行为。然而,几乎所有支持这个编译器,以及它的结果在预期的结果。如果你绝对的愿望符合标准的100%,去与移位的方法。否则,我建议将与铸造过一个联盟,这是有可能给你更好的业绩。

其他提示

忽略效率,为了简单起码我会做的:

#include <numeric>
#include <vector>
#include <cstring>

uint32_t compute_checksum(const char *data, size_t size) {
    std::vector<uint32_t> intdata(size/sizeof(uint32_t));
    std::memcpy(&intdata[0], data, size);
    return std::accumulate(intdata.begin(), intdata.end(), 0);
}

我也喜欢litb的最后的答案,一个轮班的每个char反过来,除了因为char可能签名,我认为它需要一个额外的面具:

checksum += ((data[i] && 0xFF) << shift[i % 4]);

当类型punning是一个潜在的问题,我不喜欢的类型双关语,而不是试图这样做的安全。如果你不创造任何化名的指针的不同类型的第一个地方,然后你不必担心什么的编译器可能做别名,既没有维护程序谁看到了你多static_casts通过一个联盟。

如果你不想拨出这么多的额外存储器,则:

uint32_t compute_checksum(const char *data, size_t size) {
    uint32_t total = 0;
    for (size_t i = 0; i < size; i += sizeof(uint32_t)) {
        uint32_t thisone;
        std::memcpy(&thisone, &data[i], sizeof(uint32_t));
        total += thisone;
    }
    return total;
}

足够优化将摆脱memcpy和额外的uint32_t变量完全在海湾合作委员会,只需读一整数值不对齐,在什么最有效的方式这样做是你的平台,直接从该来源阵列。我希望是这样的其他"严重的"编译器。但是,这代码现在比litb的,因此没有太多可以说他比我更容易变成一个功能的模板,将会很好的工作与uint64_t,排雷工作作为本地endian性而不是挑选little-endian.

这当然是不完全的便携式。它假定的存储代表sizeof(uint32_t)字相当于储存表示uin32_t在我们想要的方式。这是默示的问题,因为它指出,一个可被"视为"其他。Endian性,是否char是8位,以及是否uint32_t使用的所有位于其储存表示显然可以介入,但这个问题意味着他们不会。

有我的五十分不同的方式来做到这一点。

#include <iostream>
#include <string>
#include <cstring>

    uint32_t compute_checksum_memcpy(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            // memcpy may be slow, unneeded allocation
            uint32_t dest; 
            memcpy(&dest,data+i,4);
            checksum += dest;
        }
        return checksum;
    }

    uint32_t compute_checksum_address_recast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //classic old type punning
            checksum +=  *(uint32_t*)(data+i);
        }
        return checksum;
    }

    uint32_t compute_checksum_union(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //Syntax hell
            checksum +=  *((union{const char* c;uint32_t* i;}){.c=data+i}).i;
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_deref(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *&data[i];
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_cast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *(data+i);
        }
        return checksum;
    }


int main()
{
    const char* data = "ABCDEFGH";
    std::cout << compute_checksum_memcpy(data, 8) << " OK\n";
    std::cout << compute_checksum_address_recast(data, 8) << " OK\n";
    std::cout << compute_checksum_union(data, 8) << " OK\n";
    std::cout << compute_checksum_deref(data, 8) << " Fail\n";
    std::cout << compute_checksum_cast(data, 8) << " Fail\n";
}

我知道这个线程已不活跃的一段时间,但想到我会发布一个简单的通用铸造程序对于这种事情:

// safely cast between types without breaking strict aliasing rules
template<typename ReturnType, typename OriginalType>
ReturnType Cast( OriginalType Variable )
{
    union
    {
        OriginalType    In;
        ReturnType      Out;
    };

    In = Variable;
    return Out;
}

// example usage
int i = 0x3f800000;
float f = Cast<float>( i );

希望这可以帮助别人!

这看起来像一个案例书的时候使用 reinterpret_cast, ,任何其他会给你同样的效果不明确性你从使用一个语言结构对于其官方使用。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top