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

Tom*_*Tom 22 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;
 }
Run Code Online (Sandbox Code Playgroud)

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

C风格演员?

udata = (const uint32_t *)data
Run Code Online (Sandbox Code Playgroud)

假设所有指针都是可转换的C++强制转换?

udata = reinterpret_cast<const uint32_t *>(data)
Run Code Online (Sandbox Code Playgroud)

C++在任意指针类型之间使用中间转换void*

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))
Run Code Online (Sandbox Code Playgroud)

通过工会铸造?

union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata
Run Code Online (Sandbox Code Playgroud)

我完全意识到这不是一个100%可移植的解决方案,但我只希望在一小部分平台上使用它,我知道它可以工作(即未对齐的内存访问和指针别名的编译器假设).你会推荐什么?

Ada*_*eld 13

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

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

  • 我不认为这个特定的例子确实打破了严格的别名.char*是严格别名规则下的特殊情况 - char*可能永远不会被假定为指向其他类型的指针的别名.但在我的回答中,我仍然保持安全:与其他类似的案例不同,它不值得做char*. (2认同)
  • onebyone,类型惩罚不是问题,它已经由工会解决了.但问题是工会成员的阅读,尽管我们之前没有写过.标准似乎并不禁止它.但这是我们不确定的问题:/ (2认同)
  • @SteveJessop,强制别名允许转换为const char*,但严格别名不允许转换为uint32_t*.http://en.cppreference.com/w/cpp/language/reinterpret_cast中的解释点5以T2为目标显示.下面的类型别名部分说T2必须是char或unsigned char.因此,严格别名确实禁止转换为unsigned int*. (2认同)

Ste*_*sop 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);
}
Run Code Online (Sandbox Code Playgroud)

我也喜欢litb的最后一个答案,即依次移动每个char的那个,除了因为char可能会被签名,我认为它需要一个额外的掩码:

checksum += ((data[i] && 0xFF) << shift[i % 4]);
Run Code Online (Sandbox Code Playgroud)

当打字类型是一个潜在的问题时,我宁愿不输入双关语而不是安全地尝试这样做.如果您不首先创建任何不同类型的别名指针,那么您不必担心编译器可能对别名做什么,维护程序员也不会通过联合看到您的多个static_cast.

如果你不想分配这么多额外的内存,那么:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 但我同意,因为我提到memcpy是低成本的,它阻止矢量化的事实可能是某些应用程序的显示阻止. (2认同)

归档时间:

查看次数:

8572 次

最近记录:

9 年,7 月 前