【问题标题】:How to manage endianess of double from network如何管理来自网络的双重字节序
【发布时间】:2013-02-11 08:01:09
【问题描述】:

我对这个问题Swap bits in c++ for a double的答案有很大的疑问

然而,这个问题或多或少是我要寻找的: 我从网络收到一个 double,我想在我的机器中正确编码它。


在我收到int 的情况下,我使用ntohl 执行此代码:

int * piData = reinterpret_cast<int*>((void*)pData);

//manage endianness of incomming network data 
unsigned long ulValue = ntohl(*piData);
int iValue = static_cast<int>(ulValue);

但如果我收到double,我不知道该怎么办。

问题的答案建议这样做:

template <typename T>
void swap_endian(T& pX)
{
    char& raw = reinterpret_cast<char&>(pX);
    std::reverse(&raw, &raw + sizeof(T));
}

但是,如果我引用this site

The ntohl() function converts the unsigned integer netlong from network byte order to host byte order. When the two byte orders are different, this means the endian-ness of the data will be changed. When the two byte orders are the same, the data will not be changed.

相反,@GManNickG 对问题的回答总是用std::reverse 进行反转

考虑到这个答案是错误的,我错了吗? (在使用ntohl 建议的字节序网络管理范围内,尽管在OP 问题的标题中没有准确说明)。

最后:我应该将我的double 分成两部分,每部分 4 个字节,然后在这两部分上应用ntohl 函数吗?有没有更规范的解决方案?

C 中还有一个有趣的问题,host to network double?,但它限制为 32 位值。答案说由于架构差异,双打应该转换为字符串......我也将使用音频样本,我真的应该考虑将所有样本转换为我的数据库中的字符串吗? (双打来自我通过网络查询的数据库)

【问题讨论】:

  • 数据在本地数据库中的存储方式与网络协议的数据序列化方式不同的情况并不少见。是否要求数据序列化是等价的?
  • 你不能结合你链接到的两个解决方案吗? uint32_t foo = 1; if( htonl(foo) != foo ) { /* GMan's solution goes here */ } else { /* return unmodified argument */ }。此外,关于浮点数表示是不可移植的,第二个答案是正确的,即使字节顺序不是问题。那么您应该转换为字符串,还是为此使用一些专门的库?答案取决于您希望此代码的可移植性。
  • @Praetorian ,所以我必须考虑一下......
  • "双打来自我通过网络查询的数据库" + libpq 标签暗示标准的 postgresql 访问库,它应该已经在为你处理转换......你有麻烦吗?您是否只是假设您需要进行转换,而不是查看检索到的值是否已经可用?
  • @StephaneRolland:哎呀。谷歌搜索显示这是一个常见问题 - 多么好的图书馆!您可以在doxygen.postgresql.org/pqformat_8c.html 看到编码源代码 - 参见pq_sendfloat8()(也有一个pq_getmsgfloat8(),但不明显如何使用它)。祝你好运。

标签: c++ c network-programming endianness libpq


【解决方案1】:

如果你的双打是 IEEE 754 格式,那你应该还可以。现在你必须将它们的 64 位分成两个 32 位的一半,然后以大端顺序(即网络顺序)传输它们;

怎么样:

void send_double(double d) {
    long int i64 = *((reinterpret_cast<int *>)(&d)); /* Ugly, but works */
    int hiword = htonl(static_cast<int>(i64 >> 32));
    send(hiword);
    int loword = htonl(static_cast<int>(i64));
    send(loword);
}

double recv_double() {
    int hiword = ntohl(recv_int());
    int loword = ntohl(recv_int());
    long int i64 = (((static_cast<long int>) hiword) << 32) | loword;
    return *((reinterpret_cast<double *>(&i64));
}

【讨论】:

  • 如何知道双精度是否为 IEEE-754 格式?是 CPU、linux 版本还是数据库限制了编码?
  • 以上所有,但最终由数据库决定将使用何种序列化格式。 IEEE 754 已经存在很多年了,但是一些数据库开发人员可以正确地选择谨慎并将双精度数序列化为字符串,以免它们被微妙的不兼容所困扰。
  • reinterpret_cast 行违反了结构别名规则。此外,如果sizeof(int) &lt; sizeof(double)send_double 会丢失信息,这是一种常见情况。
  • 我建议使用&lt;stdint.h&gt;uint64_t(我认为是C99 起)或等价的&lt;cstdint&gt;(C++11 起)作为这种字节的64 位整数类型操纵。正如 Setepenre 在其他答案之一中指出的那样,intlong int 等标准类型可能具有不同的字节大小,具体取决于编译器。
  • 我不得不在 recv_double 中使用static_cast&lt;long int&gt;(hiword)(不能在演员表周围加上括号),send_double 可能需要进行类似的更改。
【解决方案2】:

假设您有一个编译时选项来确定字节顺序:

#if BIG_ENDIAN
template <typename T>
void swap_endian(T& pX)
{
   // Don't need to do anything here... 
}
#else
template <typename T>
void swap_endian(T& pX)
{
    char& raw = reinterpret_cast<char&>(pX);
    std::reverse(&raw, &raw + sizeof(T));
}
#endif

当然,另一种选择是根本不通过网络发送double - 考虑到它不能保证与 IEEE-754 兼容 - 有些机器使用其他浮点格式......例如使用一个字符串会更好......

【讨论】:

  • 包含 8 字节浮点数的数据库是一个 postgresql 数据库,运行在 debian 或 ubuntu 32 位系统上。是数据库、Linux 版本还是 Cpu 可以判断双打是否兼容 IEEE-754?
  • 我的意思是,如果您从另一台计算机接收double 数据,那么它使用与您相同类型的double 是什么意思?是的,x86 处理器确实使用 IEEE-754 二进制格式。但是其他一些具有不同浮点模型的机器可能不会这样做。如果所有数据都只是在您可以控制的本地机器之间传输,那么您首先不需要担心字节顺序 - 但如果数据来自“您不知道它是什么”,您还需要担心“位是什么意思”以及字节的顺序。
【解决方案3】:

我无法让 John Källén 代码在我的机器上运行。此外,将双精度转换为字节(8 位,1 个字符)可能更有用:

template<typename T>
string to_byte_string(const T& v)
{
    char* begin_ = reinterpret_cast<char*>(v);
    return string(begin_, begin_ + sizeof(T));
}

template<typename T>
T from_byte_string(std::string& s)
{
    assert(s.size() == sizeof(T) && "Wrong Type Cast");
    return *(reinterpret_cast<T*>(&s[0]));
}

此代码也适用于使用 POD 类型的结构。

如果你真的想要双整数作为两个整数

double d;
int* data = reinterpret_cast<int*>(&d);

int first = data[0];
int second = data[1];

最后,long int 并不总是一个 64 位整数(我必须使用 long long int 在我的机器上创建一个 64 位整数)。

【讨论】:

  • 注意:使用这些函数的代码应该小心,如果字符串包含空字节,它不会中断。
【解决方案4】:

如果你想了解系统字节序

仅限#if __cplusplus &gt; 201703L

#include <bit>
#include <iostream>

using namespace std;

int main()
{
    if constexpr (endian::native == endian::big)
        cout << "big-endian";
    else if constexpr (endian::native == endian::little)
        cout << "little-endian";
    else
        cout << "mixed-endian";
}

欲了解更多信息:https://en.cppreference.com/w/cpp/types/endian

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 2012-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多