关于C ++中类型惩罚的观点?

时间:2008-12-06 19:13:30

标签: c++ casting type-punning

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

通过将其视为32位整数数组(我们知道它的总长度是4的倍数),然后将所有值相加并忽略溢出,计算二进制blob数据的简单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%可移植的解决方案,但我只希望在一小部分平台上使用它,我知道它可以工作(即未对齐的内存访问和指针别名的编译器假设)。你会推荐什么?

5 个答案:

答案 0 :(得分:13)

就C ++标准而言,litb的答案是完全正确且最便携的。将const char *data投射到const uint3_t *,无论是通过C风格演员,static_cast还是reinterpret_cast,都会违反严格的别名规则(请参阅Understanding Strict Aliasing) 。如果您使用完全优化进行编译,那么代码很可能不会正确。

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

答案 1 :(得分:5)

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

#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可能会被签名,我认为它需要一个额外的掩码:

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

足够的优化将完全取消内存中的memcpy和额外的uint32_t变量,只需读取一个未对齐的整数值,无论以何种最有效的方式在您的平台上执行,直接从源数组中取出。我希望其他“严肃”的编译器也是如此。但是这个代码现在比litb更大了,所以没有什么可说的,除了我之外更容易变成一个与uint64_t一样好用的函数模板,并且我的工作作为本地字节序而不是选择一点-endian。

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

答案 2 :(得分:1)

我有五十美分 - 不同的方法。

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

答案 3 :(得分:-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 );

希望它有所帮助!

答案 4 :(得分:-4)

这看起来像是何时使用reinterpret_cast的案例书示例,如果没有使用语言构造供其正式使用的显式性,其他任何内容都会给你相同的效果。