我很好奇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%可移植的解决方案,但我只希望在一小部分平台上使用它,我知道它可以工作(即未对齐的内存访问和指针别名的编译器假设)。你会推荐什么?
答案 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
的案例书示例,如果没有使用语言构造供其正式使用的显式性,其他任何内容都会给你相同的效果。