Float64位问题fir取消引用类型化指针将破坏严格混叠规则[-Wstrict-aliasing]

问题描述

我正在尝试下面的代码获取Float64位值的低32位部分。

#define LSL_HI(x) *(1+(sInt32*)&x)
#define LSL_LO(x) *(sInt32*)&x

//warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] 
sInt32 lx = LSL_LO(1111.2222); 

[-Wstrict-aliasing]选项是-O2优化的一部分,我不必禁用此选项。

解决此问题的解决方案是什么?

我从GCC数学库本身获取了此宏。

解决方法

如果您想要浮点数64的文字字节,则可以执行以下操作:

#include <cstdint>
static_assert(double == 8,"non 64-bit double!");

int 
main()
{
    double x = 1111.2222;
    uint32_t lo = static_cast<uint32_t>(reinterpret_cast<uint64_t>(x) & 0x00000000FFFFFFFF);
    uint32_t hi = static_cast<uint32_t>((reinterpret_cast<uint64_t>(x) & 0xFFFFFFFF00000000) >> (8*4));
    return 0;
}

我不认为这是您要尝试做的。相反,如果您想要double的小数部分和整个部分,则需要尝试其他操作。

,

与Dylan所说的一样,我不确定您要完成什么。

如果您想撕裂一个双精度字(假设是IEEE 754的双精度字),则可以这样做(我正在使用ANSI转义序列,这可能不适合您的环境)。我既“撕成十六进制字节”又“撕成符号/指数/分数”:

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>

using std::cout;
using std::fpclassify;
using std::memcpy;
using std::nan;
using std::numeric_limits;
using std::reverse;
using std::setw;
using std::size_t;
using std::string;
using std::stringstream;
using std::uint32_t;
using std::uint64_t;

namespace {

uint32_t low32_from(double d) {
    char const* p = reinterpret_cast<char const*>(&d);
    uint32_t result;
    memcpy(&result,p,sizeof result);
    return result;
}

uint32_t high32_from(double d) {
    char const* p = reinterpret_cast<char const*>(&d);
    p += 4;
    uint32_t result;
    memcpy(&result,sizeof result);
    return result;
}

string hexstr(uint32_t value) {
    char hex[] = "0123456789ABCDEF";
    unsigned char buffer[4];
    memcpy(buffer,&value,sizeof buffer);
    auto p = &buffer[0];
    stringstream ss;
    char const* sep = "";
    for (size_t i = 0; i < sizeof buffer; ++i) {
        ss << sep << hex[(*p >> 4) & 0xF] << hex[*p & 0xF];
        sep = " ";
        ++p;
    }

    return ss.str();
}

string bits(uint64_t v,size_t len) {
    string s;
    int group = 0;
    while (len--) {
        if (group == 4) { s.push_back('\''); group = 0; }
        s.push_back(v & 1 ? '1' : '0');
        v >>= 1;
        ++group;
    }
    reverse(s.begin(),s.end());
    return s;
}

string doublebits(double d) {
    auto dx = fpclassify(d);
    unsigned char buffer[8];
    memcpy(buffer,&d,sizeof buffer);
    stringstream ss;
    uint64_t s = (buffer[7] >> 7) & 0x1;
    uint64_t e = ((buffer[7] & 0x7FU) << 4) | ((buffer[6] >> 4) & 0xFU);
    uint64_t f = buffer[6] & 0xFU;
    f = (f << 8) + (buffer[5] & 0xFFU);
    f = (f << 8) + (buffer[4] & 0xFFU);
    f = (f << 8) + (buffer[3] & 0xFFU);
    f = (f << 8) + (buffer[2] & 0xFFU);
    f = (f << 8) + (buffer[1] & 0xFFU);
    f = (f << 8) + (buffer[0] & 0xFFU);

    ss << "sign:\033[0;32m" << bits(s,1) << "\033[0m ";
    if (s) ss << "(-) ";
    else ss << "(+) ";

    ss << "exp:\033[0;33m" << bits(e,11) << "\033[0m ";
    ss << "(" << setw(5) << (static_cast<int>(e) - 1023) << ") ";


    ss << "frac:";

    // 'i' for implied 1 bit,'.' for not applicable (so things align correctly).
    if (dx == FP_NORMAL) ss << "\033[0;34mi";
    else ss << "\033[0;37m.\033[34m";

    ss << bits(f,52) << "\033[0m";

    if (dx == FP_INFINITE) ss << " \033[35mInfinite\033[0m";
    else if (dx == FP_NAN) ss << " \033[35mNot-A-Number\033[0m";
    else if (dx == FP_NORMAL) ss << " \033[35mNormal\033[0m";
    else if (dx == FP_SUBNORMAL) ss << " \033[35mDenormalized\033[0m";
    else if (dx == FP_ZERO) ss << " \033[35mZero\033[0m";

    ss << " " << d;

    return ss.str();
}

} // anon

int main() {
    auto lo = low32_from(1111.2222);
    auto hi = high32_from(1111.2222);
    cout << hexstr(lo) << "\n";
    cout << hexstr(hi) << "\n";
    cout << doublebits(1111.2222) << "\n";
    cout << doublebits(1.0) << "\n";
    cout << doublebits(-1.0) << "\n";
    cout << doublebits(+0.0) << "\n";
    cout << doublebits(-0.0) << "\n";
    cout << doublebits(numeric_limits<double>::infinity()) << "\n";
    cout << doublebits(-numeric_limits<double>::infinity()) << "\n";
    cout << doublebits(nan("")) << "\n";

    double x = 1.0;
    while (x > 0.0) {
        cout << doublebits(x) << "\n";
        x = x / 2.0;
    }
}
,

“以环境的书面形式的特征”转换指针并使用新铸造的指针执行类型修剪的能力是标准委员会称为“流行扩展”的一种功能[请参见基本原理第11页]。该标准故意避免要求不适合低级编程任务(例如将64位浮点值直接分解为32位块)的编译器也必须支持该标准,但是它允许编译器执行以下操作:基于客户的需求或编译器作者认为合适的任何其他条件,是否支持此类构造,并且允许将此类构造用于仅寻求“符合”而不是“严格符合”的程序中的程序。>

尽最大努力使之最适合于低级编程任务的实现将通过“以环境的已记录方式特征”处理此类构造来支持该扩展。 GCC发出警告,因为它没有配置为适合这种用法;添加编译标志-fno-strict-aliasing将对其进行正确配置,从而消除警告。

尽管为低级编程而设计的编译器不能保证将所有可能涉及类型修饰的情况都按照对象表示所隐含的方式进行处理,但它们应该毫不困难地支持强制转换指针并立即将其取消引用为新指针的情况类型,用于访问原始类型的对象。但是,不寻求最大程度地适合于低级编程的实现可能需要使用笨拙的构造,具体取决于目标平台,该构造可能会或可能不会得到有效处理。如果在不使用-fno-strict-aliasing标志的情况下调用clang和gcc的优化器,则属于后者。

作为一般原则,在不需要执行X的情况下,假定程序不会执行X的优化可能会很有用,但对于其目的最好使用X描述的代码却适得其反。能够使用低级类型的修剪构造将受益匪浅,证明它仅适用于支持它们的编译器配置,比尝试解决它们的缺失要好,尤其是考虑到clang和gcc不能可靠地支持试图解决缺乏底层编程支持的情况时可能会出现的极端情况。