【问题标题】:How can I use a #define statement to perform byte swapping on a type `float` variable in C?如何使用#define 语句对 C 中的“float”类型变量执行字节交换?
【发布时间】:2014-10-27 21:09:07
【问题描述】:

我正在修改从字节交换例程库1 获得的一段代码,并学习按位操作、#define 的用法、指针和类型转换:

#define __bswap_constant_32(x) \
  ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |               \
  (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

在函数中使用 sn-p 可以按预期工作:

void swapbytes32(void *pt)
{
    *((unsigned int *)pt) = (((  *((unsigned int *)pt) & 0xff000000) >> 24) | ((  *((unsigned int *)pt) & 0x00ff0000) >>  8) |   \
     ((  *((unsigned int *)pt) & 0x0000ff00) <<  8) | ((  *((unsigned int *)pt) & 0x000000ff) << 24));
}

例如调用

float x = 1.0;
swapbytes32((void*)(&x)); 

给出 x = 0x0 0 80 3f 并交换了 x = 0x3f 80 0 0

但请注意所有尴尬的类型转换。类型转换的正确用法让我感到困惑。在上面的函数中,我用它来抑制错误,例如:

在函数“swapbytes32”中:hex_testi_3.c:200:31:警告: 取消引用 'void *' 指针 [默认启用] *((unsigned int *)pt) = ((( *pt) & 0xff000000) >> 24) | (( *pt) & 0x00ff0000) >> 8) | \

这是我认为到目前为止我所理解的:

  • 无法取消引用 void 指针,因为您不知道隐含类型的字节长度或有关如何在操作中使用数据的信息。
  • 上面的类型转换告诉编译器将地址中的数据类型视为unsigned int,但不会更改存储数据的按位结构。

我不完全确定的是为什么在某些情况下编译器会根据我执行类型转换的位置而犹豫,或者没有按预期执行转换。

如何使用上述#define 语句(或类似语句)对float 类型变量执行字节交换?

【问题讨论】:

  • 我看不出宏版本应该如何工作;在floatint 之间执行&amp; 不会访问浮点数的表示! x 必须是 *(unsigned int *)y,这会重新引入别名问题。
  • @MattMcNabb 不,如果您尝试将float 传递给宏,则该宏根本不起作用,这就是问题所在。请参阅我的答案,了解似乎是使用广泛类型转换的解决方法,就像在上面的函数中一样。
  • 更具体地说,您不能使用bswap_constant_32(&amp;x),而必须使用bswap_constant_32((unsigned int*)(&amp;x))之类的东西

标签: c bit-manipulation c-preprocessor


【解决方案1】:

在浮点数和它的位表示形式之间转换为 int 的一种简单方法是使用联合而不是强制转换指针。如果可以保证 int 的大小与 float 的大小相同,则一个应该保存另一个的整个位模式,而不需要 any 中间类型转换。在stdint.hfloat 的IEEE754 单一标准化(几乎无处不在,但您可以使用#ifdef __STDC_IEC_559__ 对其进行测试)之间,这在大多数系统上应该是安全的。

您可以编写一个简单的包装宏,将浮点数转换为 int,然后再将其传递给上面给出的交换实现:

#define BSWAP_FLOAT(F) \
  __bswap_constant_32((union { uint32_t i; float f; }){ .f = (F) }.i)

这会就地创建一个匿名联合,用浮点值初始化其f 字段,并通过i 字段读回其位模式(然后将其传递给交换宏)。

要将宏 back 的返回值转换为另一个浮点数,请将其包装在另一个相反的联合文字表达式中,使用 int 进行初始化并提取浮点数(显示为第二个包装器为简单起见):

#define BSWAP_FLOAT2(F) \
  ((union { uint32_t i; float f; }){ .i = BSWAP_FLOAT(F) }.f)

就地联合文字对于这类事情非常有用。

查看实际操作:

#include <stdio.h>
#include <stdint.h>

#define __bswap_constant_32(x) \
  ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |               \
  (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))

#define BSWAP_FLOAT(F) \
  __bswap_constant_32((union { uint32_t i; float f; }){ .f = (F) }.i)

#define FLOAT_BITS(F) ((union { uint32_t i; float f; }){ .f = (F) }.i)

int main(void) {
    float f = 4.0f;
    printf("%x %x\n", FLOAT_BITS(f), BSWAP_FLOAT(f));
}

【讨论】:

    【解决方案2】:

    此代码调用未定义的行为,因为它通过不兼容的类型(floatunsigned int)访问对象,从而严重违反了严格的别名规则。您可以利用此规则的一个例外:允许使用指向字符类型的指针检查对象的表示。

    另外,你真的不需要每次都拼出演员表;为什么不只声明一个指针一次?像这样:

    void swap_byte_order(void *ptr, size_t n)
    {
        unsigned char *p = ptr, *q = p + n - 1;
        for (; p < q; p++, q--) {
            unsigned char tmp = *p;
            *p = *q;
            *q = tmp;
        }
    }
    

    用法:

    float f = 3.14;
    swap_byte_order(&f, sizeof f);
    

    更干净,严格符合,整体更好。

    【讨论】:

    • 通过严格的别名,我想你的意思是数据应该在具有相同(或嵌套,即 int 到 long int)数据类型的变量之间传递?
    • 我承认,我的函数中的多指针类型转换看起来并不漂亮,我还编写了(另一个)函数,很像你的(使用 char 指针)。但是我想看看我是否无法实现按位操作例程。特别是我引用的库似乎表明使用带位操作的定义比函数调用快得多(如果不太通用)。
    • @TryHard 根据特定的硬件,按位运算或内存访问可能更快。因此,“位操作更快”的断言根本不是真实的。另外,我的函数很小——它将被任何适当优化的编译器内联,因此不会有调用开销。
    【解决方案3】:

    当我起草这个问题时,我意识到我比我意识到的更接近答案。到目前为止,这是我所得到的:

    尝试在定义语句中使用指针。

    例如,如果我定义以下内容

    #define bswap_constant_32_(px) \
                        *(px)=  ( ((  *(px) & 0xff000000) >> 24) \
                                | ((  *(px) & 0x00ff0000) >>  8) \
                                | ((  *(px) & 0x0000ff00) <<  8) \
                                | ((  *(px) & 0x000000ff) << 24));
    

    如果

    unsigned int n = 123456;
    

    以下任一作品:

    n = bswap_constant_32(&n);
    bswap_constant_32(&n);
    
    // n before: 0x40       e2        1        0
    //    after: 0x 0        1       e2       40
    

    因为数据类型(以及位结构)匹配。

    但是,这是可行的:

     float x = 1.0;
     bswap_constant_32((unsigned int*)(&x));
    

    但这不是:

     x=bswap_constant_32((unsigned int*)(&x));
    

    结果:0x0 3f 0 47

    编译器似乎会执行数据类型转换并在将数据传递给x 之前重新排列底层位。

    【讨论】:

      【解决方案4】:

      你们都非常正确,你们很接近,并且指针提供了答案。您可以使用简单的强制转换对特定地址的float 值进行操作,并将结果分配给unsigned integer。您可以完全消除下面的swapped,并使用printf (" __bswap_constant_32 : %02x\n\n", __bswap_constant_32(*ai)); 产生相同的输出。看看:

      #include <stdio.h>
      
      #define __bswap_constant_32(x) \
      ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |               \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
      
      int main (void) {
      
          float f = 123.45;
          unsigned int *ai = (unsigned int *)&f;
          unsigned int swapped;
      
          printf ("\nSwapping bytes of float: '%.2f'\n\n", f);
          printf (" %.2f as unsigned int: %02x\n\n", f, *ai);
      
          swapped = __bswap_constant_32(*ai);
      
          printf ("  __bswap_constant_32  : %02x\n\n", swapped);
      
          return 0;
      }
      

      输出:

      $ ./bin/bswc
      
      Swapping bytes of float: '123.45'
      
       123.45 as unsigned int: 42f6e666
      
        __bswap_constant_32  : 66e6f642
      

      【讨论】:

      • 谢谢,从 float 到 unsigned int 交换的效果很好。但是,当我尝试将 swapped 的指针重铸为 float 时,我得到了一些令人惊讶的结果,例如 float *pf; pf = (float*)&amp;swapped; printf (" and converted back to float : (%02x) %0.2e\n\n", *pf, *pf);
      • 事实上,在您的代码中附加以下内容的输出让我感到非常惊讶:`float *pf = (float*)&swapped; printf (" 并转换回 float : %0.2g (%02x)\n\n", swapped, swapped);´
      • 显然,仅使用 printf 的行为就足以转换变量并弄乱底层位(作为一个相对新手,这真是一个惊喜!)
      • 当您分配给swapped 时,它是以unsigned int 形式存储的byte swapped不是IEEE-754 FP 格式编码的原始浮点数。跨度>
      • 是的,我在以前的几个 cmets 中混淆了指针和赋值的使用 - 这一切都在你的回答中解决了,谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-24
      • 2016-11-04
      • 2019-09-27
      • 2018-07-19
      • 1970-01-01
      相关资源
      最近更新 更多