【问题标题】:How to treat a struct with two unsigned shorts as if it were an unsigned int? (in C)如何将具有两个无符号短裤的结构视为无符号整数? (在 C 中)
【发布时间】:2010-10-26 11:35:31
【问题描述】:

我创建了一个结构来表示一个定点正数。我希望小数点两边的数字由 2 个字节组成。

typedef struct Fixed_t {
    unsigned short floor; //left side of the decimal point
    unsigned short fraction; //right side of the decimal point
} Fixed;

现在我想添加两个定点数,Fixed xFixed y。为此,我将它们视为整数并相加。

(Fixed) ( (int)x + (int)y );

但正如我的 Visual Studio 2010 编译器所说,我无法在 Fixedint 之间进行转换。

这样做的正确方法是什么?

编辑:我不致力于固定的{short floor, short fraction} 实现。

【问题讨论】:

  • 更好地使用 int 来存储您的固定值(可能 typedef'd 到其他东西)并使用移位和按位运算来提取值的整数和小数部分。这并不完全符合标准,但比将 struct 转换为 int 更便携。
  • @Vova:谢谢,这就是我最终所做的。

标签: c struct fixed-point


【解决方案1】:

你可以尝试一个讨厌的 hack,但是这里有一个字节序问题。无论您做什么转换,编译器应该如何知道您希望 floor 成为结果中最重要的部分,而 fraction 是不太重要的部分?任何依赖于重新解释内存的解决方案都适用于一种字节序,但不适用于另一种。

您应该:

(1) 明确定义转换。假设short 是 16 位:

unsigned int val = (x.floor << 16) + x.fraction;

(2) 更改Fixed 使其具有int 成员而不是两个短裤,然后在需要时分解,而不是在需要时组合

如果你想快速加法,那么 (2) 就是要做的事情。如果你有 64 位类型,那么你也可以做乘法而不分解:unsigned int result = (((uint64_t)x) * y) &gt;&gt; 16

顺便说一句,令人讨厌的 hack 是这样的:

unsigned int val;
assert(sizeof(Fixed) == sizeof(unsigned int))              // could be a static test
assert(2 * sizeof(unsigned short) == sizeof(unsigned int)) // could be a static test
memcpy(&val, &x, sizeof(unsigned int));

这适用于大端系统,其中 Fixed 没有填充(并且整数类型没有填充位)。在 little-endian 系统上,您需要 Fixed 的成员处于另一个顺序,这就是它讨厌的原因。有时通过 memcpy 进行投射是正确的做法(在这种情况下,它是一个“技巧”而不是“讨厌的黑客”)。这不是那种时代之一。

【讨论】:

  • 查看我的答案,它应该可以移植到任何具有固定大小整数类型且没有混合字节序的系统。
  • 如果 x.floor 是 16 位,那么左移会溢出所有位,因此您需要先转换为 32 位无符号整数。
  • @CashCow:不,移位运算符对其操作数执行整数提升。尝试一下。仅当int 为 16 位时才会发生溢出,但问题隐含地假设它不是。
【解决方案2】:

如果必须,您可以使用联合,但要注意字节序问题。您可能会发现算术不起作用,而且肯定是不可移植的。

typedef struct Fixed_t {
   union { 
        struct { unsigned short floor; unsigned short fraction }; 
        unsigned int whole;
         };
} Fixed;

这更有可能(我认为)大端工作(Windows/Intel 不是)。

【讨论】:

  • __attribute__ ((__packed__)) 应该添加到结构中,这样编译器就不会尝试优化字节对齐。
  • @Till:谢谢。我应该在结构中的哪个位置添加 attribute__((__packed)) 东西?
  • @snakile struct { unsigned short floor; unsigned short fraction }__attribute__ ((__packed__));
  • 你不应该使用__attribute__((__packed__))。此代码在任何实际 ABI 上都运行良好,无需非标准打包选项。
  • @R.. 没有编译器可以在 4 字节边界上对齐结构成员吗?也许当优化开启时?
【解决方案3】:

一些魔法:

typedef union Fixed {
    uint16_t w[2];
    uint32_t d;
} Fixed;
#define Floor w[((Fixed){1}).d==1]
#define Fraction w[((Fixed){1}).d!=1]

要点:

  • 我使用固定大小的整数类型,因此您不必依赖 short 是 16 位和 int 是 32 位。
  • FloorFraction 的宏(大写以避免与floor() 函数冲突)以独立于字节序的方式访问这两个部分,如foo.Floorfoo.Fraction

编辑:应OP的要求,宏的解释:

联合是一种声明由几种不同重叠类型组成的对象的方法。在这里,uint16_t w[2];uint32_t d; 重叠,因此可以将值作为 2 个 16 位单元或 1 个 32 位单元访问。

(Fixed){1} 是一个复合文字,可以写成更详细的(Fixed){{1,0}}。它的第一个元素 (uint16_t w[2];) 被初始化为 {1,0}。表达式 ((Fixed){1}).d 然后计算为 32 位整数,其前 16 位一半为 1,后 16 位一半为 0。在 little-endian 系统上,此值为 1,因此 ((Fixed){1}).d==1 计算为 1 (true) 和 ((Fixed){1}).d!=1 的计算结果为 0 (false)。在大端系统上,情况正好相反。

因此,在 little-endian 系统上,Floorw[1]Fractionw[0]。在大端系统上,Floorw[0]Fractionw[1]。无论哪种方式,您最终都会为您的平台的字节序存储/访问正确的一半 32 位值。

理论上,假设系统可以对 16 位和 32 位值使用完全不同的表示(例如,将两半的位交错),从而破坏这些宏。在实践中,这不会发生。 :-)

【讨论】:

  • 我知道这很愚蠢,但 AFAIK 标准中没有任何内容阻止 uint32_t 成为中端。我个人见过中端 64 位类型,但从未见过中端 32 位类型。即便如此,您仍然非常希望 uint32_t 的一半内的字节序与 uint16_t 的字节序匹配,所以我不认为这适用于任何合理的实现。
  • @Steve:是的,这正是我的想法。
  • @R.. : 你能解释一下 Floor 和 Fraction 宏是如何工作的吗?我不明白里面发生了什么。
  • 顺便说一句,如果您遇到不支持复合文字的旧版编译器,您可以简单地将 FloorFraction 定义为 w[1]w[0] 或反之亦然,具体取决于在编译器损坏的平台的字节序上。
  • @R.我不确定 C89 是否“损坏”,但它肯定是遗留问题。
【解决方案4】:

这是不可移植的,因为编译器不保证Fixed 将使用与int 相同的空间量。正确的做法是定义一个函数Fixed add(Fixed a, Fixed b)

【讨论】:

  • 我同意。但我实际上是在询问 add 函数的实现。我希望固定加法函数与 int 加法一样快。这就是为什么我决定将定点数转换为整数进行加法。
【解决方案5】:

只需单独添加片段即可。您需要知道表示“1”的分数的值 - 我在这里称其为 FRAC_MAX

 // c = a + b
 void fixed_add( Fixed* a, Fixed* b, Fixed* c){
     unsigned short carry = 0;
     if((int)(a->floor) + (int)(b->floor) > FRAC_MAX){
         carry = 1;
         c->fraction = a->floor + b->floor - FRAC_MAX; 
     }
     c->floor = a->floor + b->floor + carry;
 }

或者,如果您只是将固定点设置为 2 字节边界,您可以执行以下操作:

void fixed_add( Fixed* a, Fixed *b, Fixed *c){
    int ia = a->floor << 16 + a->fraction;
    int ib = b->floor << 16 + b->fraction;
    int ic = ia + ib;
    c->floor = ic >> 16;
    c->fraction = ic - c->floor;
}

【讨论】:

    【解决方案6】:

    试试这个:

    typedef union {
        struct Fixed_t {
            unsigned short floor; //left side of the decimal point
            unsigned short fraction; //right side of the decimal point
        } Fixed;
        int Fixed_int;
    }
    

    【讨论】:

    • 查看我的答案,它消除了字节序依赖性。
    【解决方案7】:

    如果您的编译器将这两个 short 放在 4 个字节上,那么您可以使用 memcpy 将您的 int 复制到您的结构中,但正如在另一个答案中所说,这不是可移植的......而且非常难看。

    您真的关心在单独的方法中分别添加每个字段吗? 出于性能原因是否要保留整数?

    【讨论】:

      【解决方案8】:
      // add two Fixed 
      Fixed operator+( Fixed a, Fixed b ) 
      {   
      ...
      }
      
      //add Fixed and int
      Fixed operator+( Fixed a, int b ) 
      {   
      ...
      }
      

      【讨论】:

        【解决方案9】:

        您可以使用以下方法将任何可寻址类型转换为另一种类型:

        *(newtype *)&var
        

        【讨论】:

        • 其实你做不到。根据别名规则,这是明确未定义的行为。
        • 这是未定义的行为,但没有人可以限制您这样做。 :) 标准未定义的行为可能由实现定义。
        猜你喜欢
        • 1970-01-01
        • 2016-02-17
        • 2013-02-16
        • 2012-08-13
        • 2015-12-18
        • 2012-01-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多