23

我对 C++ 中类型双关语指针/数组的约定很好奇。这是我目前的用例:

通过将二进制数据块视为 32 位整数数组(我们知道它的总长度是 4 的倍数),然后对所有值求和并忽略溢出,计算一个简单的 32 位校验和。

我希望这样的函数看起来像这样:

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;
 }

现在我的问题是,您认为转换data为的“最佳”方式是udata什么?

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% 可移植的解决方案,但我只希望在我知道它可以工作的一小部分平台上使用它(即未对齐的内存访问和编译器对指针别名的假设)。你会推荐什么?

4

4 回答 4

12

就 C++ 标准而言,litb的答案是完全正确且最可移植的。转换const char *data为 a const uint3_t *,无论是通过 C 风格的转换static_cast,还是reinterpret_cast,都违反了严格的别名规则(请参阅了解严格别名)。如果您使用完全优化进行编译,那么代码很可能不会做正确的事情。

通过联合(例如 litb's my_reint)进行强制转换可能是最好的解决方案,尽管它在技术上确实违反了规则,即如果您通过一个成员写入联合并通过另一个成员读取它,则会导致未定义的行为。然而,几乎所有的编译器都支持这一点,它会产生预期的结果。如果您绝对希望 100% 符合标准,请使用位移方法。否则,我建议通过联合进行强制转换,这可能会给您带来更好的性能。

于 2008-12-06T20:57:26.813 回答
6

忽略效率,为了代码的简单性,我会这样做:

#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]);

当类型双关语是一个潜在问题时,我宁愿不键入双关语,也不愿尝试安全地这样做。如果您首先不创建任何不同类型的别名指针,那么您不必担心编译器可能会对别名做什么,也不必担心通过联合查看您的多个 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;
}

足够的优化将完全摆脱 gcc 上的 memcpy 和额外的 uint32_t 变量,并且只需读取一个未对齐的整数值,无论是在您的平台上最有效的方法,直接从源数组中读取。我希望其他“严肃的”编译器也是如此。但是这个代码现在比 litb 的大,所以没有什么可说的,除了我的更容易变成一个函数模板,它可以和 uint64_t 一样工作,我的工作是原生字节序而不是选择很少-字节序。

这当然不是完全可移植的。它假设 sizeof(uint32_t) chars 的存储表示以我们想要的方式对应于 uin32_t 的存储表示。问题暗示了这一点,因为它表明可以将一个“视为”另一个。字节序,char 是否为 8 位,以及 uint32_t 是否使用其存储表示中的所有位显然可以侵入,但问题暗示它们不会。

于 2008-12-07T13:41:05.477 回答
0

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

#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";
}
于 2015-11-29T00:58:05.787 回答
-3

我知道这个线程已经有一段时间不活动了,但我想我会为这种事情发布一个简单的通用转换例程:

// 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 );

希望它可以帮助某人!

于 2011-10-12T12:48:48.407 回答